Compare commits

..

32 Commits

Author SHA1 Message Date
yutent 449353ce84 修改代码目录 2024-01-29 18:24:33 +08:00
yutent fa38bcb985 fixed icon 2024-01-26 13:22:54 +08:00
yutent da94875407 修复一处语法错误 2023-09-11 14:55:19 +08:00
yutent 842793ff79 修复在新设备上死循环的bug 2023-09-11 14:30:27 +08:00
yutent 1760fbd212 设置应用的窗口名和标题 2023-09-07 13:37:32 +08:00
yutent ff2be3b55f 3.0.1 2023-09-06 17:42:35 +08:00
yutent d91bd574f0 修复初始化异常的bug 2023-09-06 17:40:28 +08:00
yutent 26a340ac56 移除调试代码 2023-09-06 12:20:42 +08:00
yutent 409e5f1dde 修复一处异步逻辑 2023-09-05 18:52:29 +08:00
yutent 80d328eab4 update 2023-09-05 15:11:37 +08:00
yutent b15acfd5fe fixed 2023-09-05 14:53:10 +08:00
yutent ea5cc8f5c0 remove deb 2023-09-05 14:52:49 +08:00
yutent cd14bb788b 增加打包配置 2023-09-05 14:47:35 +08:00
yutent 8bde4b078c 完成重构 2023-09-05 14:17:37 +08:00
yutent 419a13e4e6 一大波更新 2023-09-04 19:21:47 +08:00
yutent 74f4cd22d4 一大波更新 2023-09-01 18:39:40 +08:00
yutent 4a80a18cc3 修改为python + gtk开发 2023-08-31 18:57:09 +08:00
yutent 1f8f16fb36 update readme 2022-07-14 16:09:10 +08:00
yutent 654ef90926 2.0.0 2022-07-14 16:05:30 +08:00
yutent 101a9c778b update 2022-07-14 15:43:55 +08:00
yutent 93ba1c6cb9 update 2022-07-13 20:22:24 +08:00
yutent 70ed3b60f3 重构 2022-03-31 11:25:41 +08:00
宇天 a3eee4352a 修复依赖 2021-06-29 10:16:48 +08:00
宇天 4710f6223c 常规更新 2021-06-29 09:57:41 +08:00
宇天 1e676357e5 更新依赖 2020-06-01 15:50:48 +08:00
宇天 51cf8ca399 修复权限检测 2020-06-01 15:38:52 +08:00
宇天 6d17123b33 更新版本号 2020-03-27 21:02:15 +08:00
宇天 add83ce230 更新组件库;修复域名正则 2020-03-27 20:56:20 +08:00
宇天 2aa37c4483 兼容mac;优化克隆 2020-01-16 15:58:22 +08:00
宇天 24c8ecde76 更新UI库 2019-12-20 17:54:27 +08:00
宇天 ee88b4fc4a 更新组件 2019-12-17 14:01:43 +08:00
宇天 7b9b9f281a 记录排序 2019-12-13 17:00:01 +08:00
67 changed files with 1454 additions and 1093 deletions

13
.gitignore vendored
View File

@ -3,14 +3,11 @@
.LSOverride .LSOverride
.idea .idea
._* ._*
*.deb
.Spotlight-V100 .Spotlight-V100
.Trashes .Trashes
we
build __pycache__
build/** *.txt
node_modules
node_modules/**
package-lock.json

16
.vscode/launch.json vendored
View File

@ -1,16 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"args": ["."]
}
]
}

View File

@ -1,11 +1,26 @@
# 伪域名解析工具 # Hosts切换器
> macos & linux专用。windows自己找别的。 > linux专用。 macos/windows自己找别的。
![preview](./preview/image.png)
## 安装
添加[`repo.debianx.in`](https://repo.debianx.in)源之后, 可直接使用`apt install hosts-switch`直接安装.
## Update Logs ## Update Logs
### v3.0.0 - 2023-09-05
- 放弃electron, 改用`gtk3+webkit2`开发
# v1.1.0 - 2019-12-11
### v2.0.0 - 2022-03-31
- 分离electron, 独立打包
- 优化hosts文件检测
- 优化域名操作
### v1.1.0 - 2019-12-11
- 支持域名删除、编辑 - 支持域名删除、编辑
- 支持克隆记录 - 支持克隆记录
- 优化记录编辑、UI - 优化记录编辑、UI

32
build.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
if [ -d unpack ]; then
sudo rm -rf unpack
fi
version="3.0.6"
mkdir -p unpack/DEBIAN
cp debian/control unpack/DEBIAN/
cp -r usr unpack/
mkdir -p unpack/usr/share/icons/hicolor/128x128/apps
mkdir -p unpack/usr/share/icons/hicolor/256x256/apps
cp icons/128x128.png unpack/usr/share/icons/hicolor/128x128/apps/hosts-switch.png
cp icons/256x256.png unpack/usr/share/icons/hicolor/256x256/apps/hosts-switch.png
cd unpack
find usr -type f | xargs md5sum > DEBIAN/md5sums
_size=$(du -d 0 usr | cut -f1)
sed -i "s/{{size}}/${_size}/" DEBIAN/control
sed -i "s/{{version}}/${version}/" DEBIAN/control
cd ..
sudo chown -R root:root unpack/
dpkg-deb -b unpack/ "hosts-switch-${version}.deb"
sudo rm -rf unpack

12
debian/control vendored Normal file
View File

@ -0,0 +1,12 @@
Package: hosts-switch
Version: {{version}}
Section: develop
Architecture: all
Priority: optional
Maintainer: Yutent <yutent.io@gmail.com>
Installed-Size: {{size}}
Depends: python3-webengine-gtk3 (>=0.4.0)
Homepage: https://git.wkit.fun/appcat/hosts-switch/
Author: yutent
Description: 一个hosts绑定切换工具
提供了一个类似域名DNS解析的操作界面.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1 +0,0 @@
<?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="1567136629282" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2211" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M544.8762021 123.11097545c-136.98417543 0-263.00961683 71.23177122-328.76202103 186.2984786-5.47936702 10.95873403 0 21.91746807 5.47936701 27.39683508 10.95873403 5.47936702 21.91746807 5.47936702 27.39683509-5.47936701 60.27303719-104.10797332 175.33974455-169.86037754 295.88581893-169.86037753 191.7778456 0 345.20012208 153.42227648 345.20012209 345.20012208 0 115.06670737-60.27303719 219.17468069-147.94290946 279.44771788 0-43.83493614-32.8762021-76.71113825-76.71113825-76.71113824-43.83493614 0-76.71113825 32.8762021-76.71113823 76.71113824 0 21.91746807 10.95873403 43.83493614 27.39683507 54.79367017-21.91746807 5.47936702-43.83493614 5.47936702-71.23177122 5.47936702-98.62860631 0-191.7778456-43.83493614-257.53024979-115.06670738-5.47936702-10.95873403-21.91746807-10.95873403-27.39683511 0-10.95873403 5.47936702-10.95873403 21.91746807 0 27.39683511C331.18088843 840.90805472 435.28886175 890.22235789 544.8762021 890.22235789c213.69531367 0 383.55569122-169.86037754 383.55569122-383.55569122s-169.86037754-383.55569122-383.55569122-383.55569122z" fill="#7dc5eb" p-id="2212"></path><path d="M298.30468633 594.33653895c5.47936702-16.43810105 5.47936702-32.8762021 5.47936702-54.79367018s0-43.83493614-10.95873404-60.27303718c0-10.95873403-10.95873403-27.39683508-21.91746808-38.35556912-10.95873403-10.95873403-21.91746807-16.43810105-38.35556911-21.91746808-10.95873403-5.47936702-21.91746807-5.47936702-43.83493614-5.47936702H95.56810668v252.0508828h93.1492393c16.43810105 0 32.8762021 0 43.83493614-5.47936702 16.43810105-5.47936702 27.39683508-10.95873403 38.35556911-21.91746806 10.95873403-10.95873403 21.91746807-27.39683508 27.3968351-43.83493614z m-49.31430316-10.95873404c-5.47936702 10.95873403-10.95873403 21.91746807-16.43810105 27.39683508-5.47936702 5.47936702-10.95873403 5.47936702-16.43810105 10.95873405H144.88240984v-164.38101052H210.63481405c10.95873403 0 16.43810105 5.47936702 21.91746807 10.95873403 5.47936702 5.47936702 10.95873403 16.43810105 16.43810105 27.39683509 0 10.95873403 5.47936702 21.91746807 5.47936701 43.83493613s-5.47936702 32.8762021-5.47936701 43.83493614zM495.56189895 665.56831017h54.79367017V413.51742737h-49.31430315v169.86037755L396.93329263 413.51742737H347.61898949v252.0508828h49.31430314v-164.38101052zM692.81911157 627.21274105c-16.43810105 0-27.39683508-5.47936702-38.35556912-10.95873403s-16.43810105-21.91746807-16.43810105-38.35556913l-49.31430315 5.47936703c5.47936702 27.39683508 10.95873403 49.31430315 32.8762021 65.7524042 16.43810105 16.43810105 43.83493614 21.91746807 71.23177122 21.91746806 21.91746807 0 38.35556912-5.47936702 54.79367018-10.95873403s27.39683508-16.43810105 32.8762021-27.39683508c5.47936702-10.95873403 10.95873403-27.39683508 10.95873403-38.35556912 0-16.43810105-5.47936702-27.39683508-10.95873403-38.35556913-5.47936702-10.95873403-16.43810105-21.91746807-27.39683508-27.39683508-10.95873403-5.47936702-27.39683508-10.95873403-54.79367018-16.43810105s-38.35556912-10.95873403-49.31430316-16.43810105c-5.47936702-5.47936702-5.47936702-10.95873403-5.47936701-16.43810105 0-5.47936702 0-10.95873403 5.47936701-16.43810106 16.43810105-10.95873403 27.39683508-10.95873403 38.35556912-10.95873403 16.43810105 0 27.39683508 5.47936702 32.8762021 10.95873403 10.95873403 0 10.95873403 10.95873403 16.43810105 27.39683509h49.31430317c0-21.91746807-10.95873403-43.83493614-27.39683509-54.79367017-16.43810105-16.43810105-38.35556912-21.91746807-71.23177123-21.91746808-21.91746807 0-38.35556912 5.47936702-49.31430315 10.95873404-16.43810105 5.47936702-21.91746807 16.43810105-32.8762021 27.39683509-5.47936702 0-5.47936702 16.43810105-5.47936702 27.39683509 0 21.91746807 5.47936702 38.35556912 21.91746807 49.31430315 10.95873403 10.95873403 27.39683508 16.43810105 54.79367016 27.39683508 21.91746807 5.47936702 38.35556912 5.47936702 43.83493614 10.95873404 10.95873403 5.47936702 16.43810105 5.47936702 16.43810105 10.95873403 5.47936702 5.47936702 5.47936702 10.95873403 5.47936703 16.43810106 0 10.95873403-5.47936702 16.43810105-10.95873404 21.91746807-10.95873403 5.47936702-21.91746807 10.95873403-38.35556912 10.95873403z" fill="#7dc5eb" p-id="2213"></path></svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,51 +0,0 @@
{
"name": "dns-host",
"version": "1.1.0",
"description": "伪域名解析",
"main": "src/main.js",
"scripts": {
"start": "electron .",
"pack": "electron-builder",
"pack:mac": "electron-builder --mac",
"pack:linux": "electron-builder --linux"
},
"author": {
"name": "yutent",
"email": "yutent@doui.cc"
},
"homepage": "https://yutent.me",
"license": "MIT",
"dependencies": {
"iofs": "^1.2.1"
},
"build": {
"appId": "dns.host",
"productName": "dns-host",
"copyright": "Copyright © 2019 ${author}",
"directories": {
"buildResources": "icons",
"output": "build"
},
"files": ["src/**/*", "node_modules/iofs/*"],
"mac": {
"category": "public.app-category.developer-tools",
"target": "dmg",
"icon": "icons/app.icns",
"darkModeSupport": false
},
"linux": {
"category": "Development",
"target": [
{
"target": "deb",
"arch": "x64"
}
],
"icon": "./icons/"
}
},
"devDependencies": {
"electron": "^7.1.0",
"electron-builder": "^22.0.0"
}
}

BIN
preview/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -1 +0,0 @@
html{font-size:1vw;width:100%;height:100%}body{position:fixed;left:0;top:0;display:flex;width:100%;height:100%;line-height:1.5;font-size:1.4rem;color:#62778d}.layout-left{display:flex;flex-direction:column;width:18rem;height:64rem;background:#526273}.layout-left wc-scroll{flex:1}.layout-left .domain-list{width:100%}.layout-left .domain-list .item{display:flex;justify-content:flex-end;align-items:center;height:4rem;padding:0 2rem 0 1rem;color:#f3f5fb;cursor:pointer;transition:background .1s ease-in-out}.layout-left .domain-list .item wc-icon{--size:1.2rem;margin:auto -1.5rem auto .5rem;color:#7e909a}.layout-left .domain-list .item:hover,.layout-left .domain-list .item.active{background:#62778d}.layout-left .domain-list .item.active{border-left:0.3rem solid #ffb618;color:#ffb618;font-weight:bold}.layout-left .domain-list .item.blank{justify-content:center;cursor:default}.layout-left .domain-list .item.blank:hover{background:none}.layout-left .action{display:flex;align-items:center;height:5rem;padding:0 1rem}.layout-right{flex:1;display:flex;flex-direction:column;background:#f7f8fb}.layout-right .toolbar{display:flex;align-items:center;justify-content:space-between;height:4rem;padding:0 1.5rem;background:#e8ebf4;box-shadow:0 0.2rem 0.5rem rgba(0,0,0,0.1)}.layout-right .toolbar wc-input{width:20rem}.layout-right .main{overflow:hidden;flex:1;display:flex;flex-direction:column;margin:2rem 1rem;padding:0 .5rem;background:#fff}.layout-right .thead{display:flex;align-items:center;justify-content:center;height:4rem;margin-bottom:.8rem;border-bottom:0.1rem solid #e8ebf4;text-align:center}.layout-right .thead span{flex:1}.layout-right .thead .long{flex:1.5}.layout-right wc-scroll{flex:1}.layout-right .record-list{width:100%;line-height:4rem}.layout-right .record-list .item{display:flex;justify-content:center;align-items:center;height:4rem;padding:0 1rem;border-bottom:0.1rem solid #e8ebf4;text-align:center}.layout-right .record-list .item wc-input,.layout-right .record-list .item span,.layout-right .record-list .item section{flex:1}.layout-right .record-list .item .long{flex:1.5}.layout-right .record-list .item section{display:flex;align-items:center;justify-content:center}.layout-right .record-list .item wc-button{margin-left:.5rem}.layout-right .record-list .item:last-child{border-bottom:none}.permission-error{position:fixed;left:0;top:0;z-index:102401;display:flex;flex-direction:column;align-items:center;width:100%;height:100%;padding:5rem;background:rgba(255,233,233,0.95);-webkit-backdrop-filter:blur(0.5rem);backdrop-filter:blur(0.5rem)}.permission-error pre{font-family:'Courier New', Courier, monospace;font-size:1.6rem;color:#eb3b48}.permission-error fieldset{width:60rem;padding:0 3rem 3rem;border:0.1rem solid #ffb618;border-radius:.8rem}.permission-error fieldset legend{padding:0 1rem;font-size:1.6rem}.permission-error fieldset dt{margin-top:2rem;font-weight:bold}.permission-error fieldset code{display:block;padding:.8rem 1rem;margin-top:.8rem;border-left:0.3rem solid #dae1e9;background:rgba(255,255,255,0.3);font-family:'Courier New', Courier, monospace}.permission-error.show{display:flex}.context-menu{display:flex;flex-direction:column;width:10rem;padding:.5rem 0;background:#fff}.context-menu .item{height:3rem;line-height:3rem;padding:0 1.5rem;cursor:pointer}.context-menu .item:hover{background:#f2f5fc}

View File

@ -1,148 +0,0 @@
@charset "UTF-8";
/**
* {sonist app style}
* @authors yutent<yutent@doui.cc>
* @date 2018/12/16 17:15:07
*/
@import "./var.scss";
html {font-size:1vw;width:100%;height:100%;}
body {
position:fixed;left:0;top:0;
display:flex;
width:100%;height:100%;line-height:1.5;
font-size:1.4rem;color:nth($cd, 1);
}
.layout-left {
display:flex;flex-direction:column;
width:18rem;height:64rem;
background:nth($cd, 2);
wc-scroll {flex:1;}
.domain-list {
width:100%;
.item {
display:flex;justify-content:flex-end;align-items:center;
height:4rem;padding:0 2rem 0 1rem;
color:nth($cp, 1);cursor:pointer;
@include ts(background);
wc-icon {--size:1.2rem;margin:auto -1.5rem auto .5rem;color:nth($cgr, 3);}
&:hover,
&.active {background:nth($cd, 1);}
&.active {border-left:.3rem solid nth($co, 1);color:nth($co, 1);font-weight:bold;}
&.blank {
justify-content:center;cursor:default;
&:hover {background:none;}
}
}
}
.action {
display:flex;align-items:center;
height:5rem;padding:0 1rem;
}
}
.layout-right {
flex:1;display:flex;flex-direction:column;
background:#f7f8fb;
.toolbar {
display:flex;align-items:center;justify-content:space-between;
height:4rem;padding:0 1.5rem;
background:nth($cp, 2);box-shadow:0 .2rem .5rem rgba(0, 0, 0, .1);
wc-input {width:20rem;}
}
.main {
overflow:hidden;
flex:1;display:flex;flex-direction:column;margin:2rem 1rem;padding:0 .5rem;
background:#fff;
}
.thead {
display:flex;align-items:center;justify-content:center;
height:4rem;margin-bottom:.8rem;
border-bottom:.1rem solid nth($cp, 2);text-align:center;
span {flex:1;}
.long {flex:1.5;}
}
wc-scroll {flex:1;}
.record-list {
width:100%;line-height:4rem;
.item {
display:flex;justify-content:center;align-items:center;
height:4rem;padding:0 1rem;
border-bottom:.1rem solid nth($cp, 2);text-align:center;
wc-input,span,section {flex:1;}
.long {flex:1.5;}
section {display:flex;align-items:center;justify-content:center;}
wc-button {margin-left:.5rem;}
&:last-child {border-bottom:none;}
}
}
}
.permission-error {
position:fixed;left:0;top:0;z-index:102401;
display:flex;flex-direction:column;align-items:center;
width:100%;height:100%;padding:5rem;
background:rgba(255, 233, 233, .95);
backdrop-filter:blur(.5rem);
pre {font-family:'Courier New', Courier, monospace;font-size:1.6rem;color:nth($cr, 2);}
fieldset {
width:60rem;padding:0 3rem 3rem;
border:.1rem solid nth($co, 1);border-radius:.8rem;
legend {padding:0 1rem;font-size:1.6rem;}
dt {margin-top:2rem;font-weight:bold;}
code {
display:block;
padding:.8rem 1rem;margin-top:.8rem;
border-left:.3rem solid nth($cp, 3);
background:rgba(255, 255, 255, .3);
font-family:'Courier New', Courier, monospace;
}
}
&.show {display:flex;}
}
.context-menu {
display:flex;
flex-direction:column;
width:10rem;
padding:.5rem 0;
background:#fff;
.item {
height:3rem;line-height:3rem;padding:0 1.5rem;cursor:pointer;
&:hover {background:#f2f5fc;}
}
}

View File

@ -1,15 +0,0 @@
$ct: #3fc2a7 #19b491 #16967a;
$cg: #58d68d #2ecc71 #27ae60;
$cpp: #ac61ce #9b59b6 #8e44ad;
$cb: #66b1ff #409eff #3a8ee6;
$cr: #ff5061 #eb3b48 #ce3742;
$co: #ffb618 #f39c12 #e67e22;
$cp: #f3f5fb #e8ebf4 #dae1e9;
$cgr: #aabac3 #90a3ae #7e909a;
$cd: #62778d #526273 #425064;
@mixin ts($c: all, $t: .1s, $m: ease-in-out){
transition:$c $t $m;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -1,106 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title></title>
<link href="/lib/css/reset-basic.css" rel="stylesheet">
<link href="/css/app.css" rel="stylesheet">
<script src="/js/app.js" type="module"></script>
</head>
<body anot="app" class="do-fn-noselect">
<div class="layout-left">
<wc-scroll>
<ul class="domain-list" @contextmenu="showMenu">
<li
class="item"
:class="{active: it === activeDomain}"
:for="it in domains"
:data-name="it"
@click="toggleDomain(it)">
<span :text="it"></span>
<wc-icon is="right"></wc-icon>
</li>
<li :if="domains.size() < 1" class="item blank">没有域名</li>
</ul>
</wc-scroll>
<section class="action">
<wc-button
title="新增域名"
color="blue"
circle icon="plus"
@active="addDomain"></wc-button>
</section>
</div>
<div class="layout-right">
<header class="toolbar">
<wc-button icon="plus" color="teal" @active="addRecord">新增记录</wc-button>
<wc-button icon="fly" color="blue" @active="save">保存</wc-button>
</header>
<main class="main">
<header class="thead">
<span class="long">主机记录</span>
<span>类型</span>
<span class="long">记录值</span>
<span class="long">操作</span>
<span>备注</span>
</header>
<wc-scroll ref="records">
<ul class="record-list">
<li class="item" :for="it in records">
<wc-input auto-border autofocus class="long" @change="updateCacheDict(it)" :duplex="it.record" label="根域请填 @"></wc-input>
<span>A</span>
<wc-input auto-border class="long" :duplex="it.value" label="请填写IP"></wc-input>
<section class="long">
<wc-switch :duplex="it.enabled" @change="recordChanges(it)"></wc-switch>
<wc-button size="mini" color="red" @active="$remove">删除</wc-button>
<wc-button size="mini" color="blue" @active="clone(it)">克隆</wc-button>
</section>
<wc-input auto-border :duplex="it.remark"></wc-input>
</li>
</ul>
</wc-scroll>
</main>
</div>
<div class="permission-error" :if="permissionShow">
<pre>
/************************************************************/
* hosts文件没有写权限 *
/************************************************************/
</pre>
<fieldset>
<legend>操作指引</legend>
<dl>
<dt>MacOS用户</dt>
<dd>打开终端, 执行以下命令</dd>
<dd><code>sudo chown $USER:admin /etc/hosts</code></dd>
<dt>Linux用户</dt>
<dd>打开终端, 执行以下命令</dd>
<dd><code>sudo chown $USER: /etc/hosts</code></dd>
<dt>完成之后</dt>
<dd>点击下面的按钮重新检测.</dd>
<dd>
<wc-button color="red" size="mini" @active="check">权限检测</wc-button>
</dd>
</dl>
</fieldset>
</div>
<wc-layer ref="context" left="100px" top="0" radius="0">
<ul class="context-menu" @click="confirmAction">
<li class="item" data-act="del">删除域名</li>
<li class="item" data-act="edit">修改域名</li>
</ul>
</wc-layer>
</body>
</html>

View File

@ -1,220 +0,0 @@
/**
* {sonist app}
* @author yutent<yutent@doui.cc>
* @date 2018/12/16 17:15:57
*/
import '/lib/anot.js'
import '/lib/form/button.js'
import '/lib/form/input.js'
import '/lib/form/switch.js'
import layer from '/lib/layer/index.js'
import { clickOutside } from '/lib/utils.js'
const log = console.log
const { remote, ipcRenderer } = require('electron')
const WIN = remote.getCurrentWindow()
const $doc = Anot(document)
var dict = {}
var tmp_records = {}
Anot({
$id: 'app',
state: {
filter: '',
activeDomain: '', //当前选中的域名
editDomain: '', // 当前临时要编辑的域名, 即右键菜单选择到的
domains: [],
records: [],
permissionShow: false
},
mounted() {
// this.$refs.context.show()
this.check()
clickOutside(this.$refs.context, ev => {
this.$refs.context.close()
})
},
watch: {},
methods: {
addRecord() {
if (this.activeDomain) {
this.records.unshift({
record: '',
value: '',
enabled: true,
remark: ''
})
this.$refs.records.scrollTop = 0
} else {
layer.toast('请先选择域名')
}
},
addDomain() {
layer
.prompt('请输入根域名', function(val, done) {
if (/^[\w.]+\.[a-z]+$/.test(val)) {
done()
} else {
layer.toast('域名格式错误', 'error')
}
})
.then(val => {
this.domains.push(val)
dict[val] = []
if (!this.activeDomain) {
this.toggleDomain(val)
}
this.save()
})
.catch(Anot.noop)
},
toggleDomain(name) {
this.activeDomain = name
this.records = dict[name]
tmp_records = Object.create(null)
for (let it of this.records) {
if (tmp_records[it.record]) {
tmp_records[it.record].push(it)
} else {
tmp_records[it.record] = [it]
}
}
document.title = `伪域名解析「 ${name}`
setTimeout(() => {
this.$refs.records.scrollTop = 0
}, 50)
},
showMenu(ev) {
this.$refs.context.close()
var { pageX, pageY } = ev
if (pageY + 70 > 600) {
pageY -= 70
}
var elem = ev.target
if (elem.tagName !== 'LI') {
elem = elem.parentNode
}
this.editDomain = elem.dataset.name
Anot.nextTick(_ => {
this.$refs.context.moveTo({ left: pageX + 'px', top: pageY + 'px' })
this.$refs.context.show()
})
},
confirmAction(ev) {
this.$refs.context.close()
if (ev.target.tagName === 'LI') {
var act = ev.target.dataset.act
if (act === 'del') {
layer
.confirm(`是否要删除域名「${this.editDomain}」?`, (val, done) => {
if (dict[this.editDomain].length > 0) {
return layer.toast(
'该域名下有主机记录, 请先删除主机记录后再删除域名',
'error'
)
}
done()
})
.then(res => {
delete dict[this.editDomain]
this.domains.remove(this.editDomain)
this.activeDomain = ''
this.editDomain = ''
this.records.clear()
this.save()
this.toggleDomain(this.domains[0])
})
.catch(Anot.noop)
} else if (act === 'edit') {
layer
.prompt(`请输入新的名字「${this.editDomain}`, (val, done) => {
if (val && val !== this.editDomain) {
if (/^[\w.]+\.[a-z]+$/.test(val)) {
done()
} else {
layer.toast('域名格式错误', 'error')
}
}
})
.then(val => {
var idx = this.domains.indexOf(this.editDomain)
this.domains.set(idx, val)
dict[val] = dict[this.editDomain]
delete dict[this.editDomain]
this.activeDomain = ''
this.editDomain = ''
this.save()
this.toggleDomain(val)
})
.catch(Anot.noop)
}
}
},
check() {
var check = ipcRenderer.sendSync('dns-host', { type: 'check' })
if (check) {
dict = ipcRenderer.sendSync('dns-host', { type: 'history' })
var tmp = []
for (var k in dict) {
if (k) {
tmp.push(k)
} else {
delete dict[k]
}
}
this.domains = tmp
} else {
this.permissionShow = true
}
},
updateCacheDict(item) {
clearTimeout(this.timer)
this.timer = setTimeout(_ => {
tmp_records = Object.create(null)
for (let it of this.records) {
if (tmp_records[it.record]) {
tmp_records[it.record].push(it)
} else {
tmp_records[it.record] = [it]
}
}
}, 1000)
},
clone(item) {
var params = { ...item }
params.enabled = false
this.records.unshift(params)
this.$refs.records.scrollTop = 0
},
// 同一个记录, 允许一条被激活
recordChanges(item) {
if (item.enabled) {
if (tmp_records[item.record].length > 1) {
for (let it of tmp_records[item.record]) {
if (it.value !== item.value) {
it.enabled = false
}
}
}
}
},
save() {
if (this.activeDomain) {
dict[this.activeDomain] = this.records.$model
}
ipcRenderer.send('dns-host', { type: 'set', data: dict })
layer.toast('保存成功', 'success')
}
}
})

File diff suppressed because one or more lines are too long

View File

@ -1,60 +0,0 @@
@charset "UTF-8";
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2014-10-10 00:45:09
*
* douiCSS
*
* ,
* do-st-*
* do-fn-*
* do-mod-modname
* UIdo-uiname, .do-uiname
* .do-layer .body { ... }
*
*
* 1 display float position overflow z-index /
* 2 width height margin padding border
* 3 line-height font-size vertical-align text-align user-select outline ....
* 4 color background opacity cursor ...
* 5 content list-style quotes ...
*
*/
* {margin: 0;padding: 0;vertical-align: baseline;box-sizing:border-box;}
::before,::after {box-sizing:border-box;}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section,content {display: block;}
img {border:0;display:inline-block;}
ol, ul {list-style: none;}
blockquote, q {quotes: none;}
blockquote::before, blockquote::after,
q::before, q::after {content: '';content: none;}
table {border-collapse: collapse;border-spacing: 0;}
a:focus,input,textarea,button:focus,input:focus,textarea:focus {outline:none;}
::-moz-focus-inner {
border:none;outline:none;
}
body {font-family:Helvetica, Arial,"WenQuanYi Micro Hei","PingFang SC","Hiragino Sans GB","Segoe UI", "Microsoft Yahei", sans-serif;-webkit-font-smoothing: antialiased;text-size-adjust: 100%;-webkit-tap-highlight-color: transparent;}
code,pre,samp {font-family:Menlo,Monaco,Consolas,"Courier New",monospace;}
[anot],[\:repeat],[\:if],[is-widget],slot {visibility:hidden;}
.do-fn-cl { *zoom: 1; }
.do-fn-cl::after { content: "."; display: block; height: 0; clear: both; visibility: hidden; overflow:hidden;}
.do-fn-clear {clear:both;display:inline;}
.do-fn-show{display:block;}
.do-fn-hide{display:none;}
.do-fn-fl{float:left;}
.do-fn-fr{float:right;}
.do-fn-noselect {-webkit-touch-callout: none;-webkit-user-select: none;-moz-user-select: none;user-select: none;}
.do-fn-noselect img, .do-fn-noselect a {-webkit-user-drag:none;}
.do-fn-ell {overflow:hidden; white-space:nowrap; text-overflow:ellipsis }
.do-st-thin {-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
.do-st-hand {cursor:pointer;}
[class^="do-icon-"], [class*=" do-icon-"] {display:inline-block;font-family:"uifont" !important;font-style:normal;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}

View File

@ -1 +0,0 @@
"use strict";import{bind,unbind}from"../utils.js";const DEF_OPT={axis:"",limit:!1,overflow:!0};export default class Drag{constructor(t){this.$elem=t,this._init()}_init(){this.$elem.style.transform="";var{x:t,y:s}=this.$elem.getBoundingClientRect();this.pos={x:t,y:s,_x:0,_y:0}}by(t,s={}){return this.$drag=t,this.opt=Object.assign(Object.create(null),DEF_OPT,s),!1!==this.opt.limit&&(this.opt.overflow=!1),t.style.cursor="move",this._handleResize=bind(window,"resize",this._init.bind(this)),this._handleMousedown=bind(t,"mousedown",t=>{if(this.disabled)return;var s=this.$elem.getBoundingClientRect();s.x-this.pos._x!==this.pos.x&&(this.pos.x=s.x-this.pos._x),s.y-this.pos._y!==this.pos.y&&(this.pos.y=s.y-this.pos._y);let e=t.pageX,i=t.pageY,o=document.documentElement.clientWidth,n=document.documentElement.clientHeight,h=s.width,d=s.height,p=[0,o-h,n-d,0];if("parent"===this.opt.limit){let t=this.$elem.parentNode.getBoundingClientRect();p=[t.top,t.right-h,t.bottom-d,t.left]}let l=bind(document,"mousemove",t=>{t.preventDefault();let o=t.pageX-e+(s.x-this.pos.x),n=t.pageY-i+(s.y-this.pos.y);"x"===this.opt.axis&&(n=0),"y"===this.opt.axis&&(o=0),!1===this.opt.overflow&&(o<p[3]-this.pos.x?o=p[3]-this.pos.x:o>p[1]-this.pos.x&&(o=p[1]-this.pos.x),n<p[0]-this.pos.y?n=p[0]-this.pos.y:n>p[2]-this.pos.y&&(n=p[2]-this.pos.y)),this.pos._x=o,this.pos._y=n,this.$elem.dispatchEvent(new CustomEvent("dragging",{detail:{offset:{x:this.pos.x+o,y:this.pos.y+n},move:{x:o,y:n}}})),this.$elem.style.transform=`translate(${o}px, ${n}px)`}),m=bind(document,"mouseup",t=>{this.$elem.dispatchEvent(new CustomEvent("dragged",{detail:{offset:{x:this.pos.x+this.pos._x,y:this.pos.y+this.pos._y},move:{x:this.pos._x,y:this.pos._y}}})),unbind(document,"mousemove",l),unbind(document,"mouseup",m)})}),this}on(t,s){if(t&&"function"==typeof s)return bind(this,t,s)}off(t,s){unbind(this,t,s)}destroy(){unbind(window,"resize",this._handleResize),unbind(this.$drag,"mousedown",this._handleMousedown),delete this.$elem,delete this.$drag}};

View File

@ -1 +0,0 @@
"use strict";import Drag from"./core.js";Anot.directive("drag",{priority:1500,init:function(e){e.expr='"'+e.expr+'"',e.overflow=!0,e.axis="xy",e.element.dataset.axis&&(e.axis=e.element.dataset.axis,delete e.element.dataset.axis),e.limit=!1,e.element.dataset.limit&&(e.limit=e.element.dataset.limit,e.overflow=!1,delete e.element.dataset.limit)},update:function(e){var t=this.element;if(e)for(t=this.element.parentNode;t;){if(t.classList||Anot.error(`${this.name}=${this.expr}, 解析异常[元素不存在]`),"WC-LAYER"===t.tagName&&"layer"===e){t=t.root.children[1];break}if(t.classList.contains(e)||t.id===e)break;t=t.parentNode}new Drag(t).by(this.element,{limit:this.limit,axis:this.axis,overflow:this.overflow})}});

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2019-12-11 17:59:30
* @version v2.0.1
*
*/
'use strict'
import"../icon/index.js";import{bind,unbind}from"../utils.js";export default class Checkbox extends HTMLElement{static get observedAttributes(){return["label","color","value","checked","readonly","disabled"]}constructor(){super(),Object.defineProperty(this,"root",{value:this.attachShadow({mode:"open"}),writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(this,"props",{value:{label:"",color:"",value:[],checked:!1,readonly:!1,disabled:!1},writable:!0,enumerable:!1,configurable:!0}),this.root.innerHTML="<style>*{box-sizing:border-box;margin:0;padding:0}::before,::after{box-sizing:border-box}:host{display:inline-block;line-height:1;font-size:14px}:host label{display:flex;justify-content:center;align-items:center;min-width:32px;height:32px;padding:0 5px;line-height:0;-moz-user-select:none;user-select:none;white-space:nowrap;cursor:inherit;color:#7e909a}:host .dot{--size: 18px;padding:2px;margin-right:3px}:host([readonly]){opacity:0.8}:host([disabled]){cursor:not-allowed;opacity:0.6}:host([size='large']){font-size:16px}:host([size='large']) label{height:42px}:host([size='large']) .dot{--size: 22px}:host([size='medium']) label{height:38px}:host([size='medium']) .dot{--size: 20px}:host([size='mini']){font-size:12px}:host([size='mini']) label{height:20px}:host([size='mini']) .dot{--size: 14px}:host([color='red']) label.checked{color:#ff5061}:host([color='red']) label.checked .dot{border-color:#ff5061}:host([color='red']) label.checked .dot::after{background:#ff5061}:host([color='blue']) label.checked{color:#66b1ff}:host([color='blue']) label.checked .dot{border-color:#66b1ff}:host([color='blue']) label.checked .dot::after{background:#66b1ff}:host([color='green']) label.checked{color:#58d68d}:host([color='green']) label.checked .dot{border-color:#58d68d}:host([color='green']) label.checked .dot::after{background:#58d68d}:host([color='teal']) label.checked{color:#3fc2a7}:host([color='teal']) label.checked .dot{border-color:#3fc2a7}:host([color='teal']) label.checked .dot::after{background:#3fc2a7}:host([color='orange']) label.checked{color:#ffb618}:host([color='orange']) label.checked .dot{border-color:#ffb618}:host([color='orange']) label.checked .dot::after{background:#ffb618}:host([color='dark']) label.checked{color:#62778d}:host([color='dark']) label.checked .dot{border-color:#62778d}:host([color='dark']) label.checked .dot::after{background:#62778d}:host([color='purple']) label.checked{color:#ac61ce}:host([color='purple']) label.checked .dot{border-color:#ac61ce}:host([color='purple']) label.checked .dot::after{background:#ac61ce}\n</style> <label> <wc-icon class=\"dot\" is=\"checkbox-off\"></wc-icon> <slot></slot> </label> ",this.__SWITCH__=this.root.lastElementChild,this.__ICO__=this.__SWITCH__.children[0]}get value(){return this.props.value}set value(e){log(e,this,this.props.label),Array.isArray(e)?(this.props.value=e,this.checked=this.props.value.includes(this.props.label)):console.error("checkbox组件的value必须是数组, 当前为: "+typeof e)}get checked(){return this.props.checked}set checked(e){this.props.checked=!!e;var{value:o,checked:t,label:l,color:r}=this.props;this.__SWITCH__.classList.toggle("checked",t),this.__ICO__.setAttribute("is","checkbox-"+(t?"on":"off"));var s=o.indexOf(l);t?(this.__ICO__.setAttribute("color",r),s<0&&o.push(l)):(this.__ICO__.removeAttribute("color"),~s&&o.splice(s,1))}get readonly(){return this.props.readonly}set readonly(e){var o=typeof e;e!==this.props.readonly&&("boolean"===o&&e||"boolean"!==o?(this.props.readonly=!0,this.setAttribute("readonly","")):(this.props.readonly=!1,this.removeAttribute("readonly")))}get disabled(){return this.props.disabled}set disabled(e){var o=typeof e;e!==this.props.disabled&&("boolean"===o&&e||"boolean"!==o?(this.props.disabled=!0,this.setAttribute("disabled","")):(this.props.disabled=!1,this.removeAttribute("disabled")))}connectedCallback(){this._handlClick=bind(this,"click",e=>{e.preventDefault(),this.disabled||this.readonly||(this.checked=!this.checked,this.dispatchEvent(new CustomEvent("input")))})}disconnectedCallback(){unbind(this,"click",this._handlClick)}attributeChangedCallback(e,o,t){if(null!==t&&o!==t)switch(e){case"label":case"color":this.props[e]=t;break;case"checked":case"readonly":case"disabled":this[e]=!0}}};
if(!customElements.get('wc-checkbox')){
customElements.define('wc-checkbox', Checkbox)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2019-12-11 17:59:30
* @version v2.0.1
*
*/
'use strict'
export default class Progress extends HTMLElement{static get observedAttributes(){return["value","max"]}constructor(){super(),Object.defineProperty(this,"root",{value:this.attachShadow({mode:"open"}),writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(this,"props",{value:{value:0,max:1},writable:!0,enumerable:!1,configurable:!0}),this.root.innerHTML="<style>*{box-sizing:border-box;margin:0;padding:0}::before,::after{box-sizing:border-box}:host{display:flex;align-items:center}:host label{flex:1;height:var(--size, 10px);border-radius:9px;background:#e8ebf4}:host label span{display:block;width:0;height:100%;border-radius:9px;background:#3fc2a7}:host([size='large']) label{height:18px}:host([size='medium']) label{height:14px}:host([size='mini']) label{height:6px}:host([color='red']) label span{background:#ff5061}:host([color='blue']) label span{background:#66b1ff}:host([color='green']) label span{background:#58d68d}:host([color='orange']) label span{background:#ffb618}:host([color='dark']) label span{background:#62778d}:host([color='purple']) label span{background:#ac61ce}\n</style> <label><span></span></label> ",this.__THUMB__=this.root.children[1].lastElementChild}get value(){return this.props.value}set value(e){this.props.value=+e,this.calculate()}calculate(){var{max:e,value:a}=this.props;this.__THUMB__.style.width=`${100*a/e}%`}connectedCallback(){this.calculate()}attributeChangedCallback(e,a,l){if(null!==l&&a!==l)switch(e){case t:var t=+l;(t!=t||t<1)&&(t=1),this.props.max=t,this.calculate();break;case"value":var r=+l;r==r&&(this.props.value=r,this.calculate())}}};
if(!customElements.get('wc-progress')){
customElements.define('wc-progress', Progress)
}

View File

@ -1,15 +0,0 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2019-12-11 17:59:30
* @version v2.0.1
*
*/
'use strict'
import{bind,unbind}from"../utils.js";export default class Radio extends HTMLElement{static get observedAttributes(){return["label","checked","readonly","disabled"]}constructor(){super(),Object.defineProperty(this,"root",{value:this.attachShadow({mode:"open"}),writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(this,"props",{value:{label:"",checked:!1,readonly:!1,disabled:!1},writable:!0,enumerable:!1,configurable:!0}),this.root.innerHTML="<style>*{box-sizing:border-box;margin:0;padding:0}::before,::after{box-sizing:border-box}:host{display:inline-block;line-height:1;font-size:14px}:host label{display:flex;justify-content:center;align-items:center;min-width:32px;height:32px;padding:0 5px;line-height:0;-moz-user-select:none;user-select:none;white-space:nowrap;cursor:inherit;color:#7e909a}:host label.checked .dot::after{display:block;width:12px;height:12px;border-radius:50%;background:#aabac3;content:''}:host .dot{width:18px;height:18px;padding:2px;margin-right:3px;border:1px solid #aabac3;border-radius:50%;background:#fff}:host([readonly]){opacity:0.8}:host([disabled]){cursor:not-allowed;opacity:0.6}:host([size='large']) label{width:58px;height:32px}:host([size='large']) .dot{width:26px;height:26px}:host([size='medium']) label{width:50px;height:28px}:host([size='medium']) .dot{width:22px;height:22px}:host([size='mini']) label{width:22px;height:14px;padding:2px}:host([size='mini']) .dot{width:10px;height:10px}:host([color='red']) label.checked{color:#ff5061}:host([color='red']) label.checked .dot{border-color:#ff5061}:host([color='red']) label.checked .dot::after{background:#ff5061}:host([color='blue']) label.checked{color:#66b1ff}:host([color='blue']) label.checked .dot{border-color:#66b1ff}:host([color='blue']) label.checked .dot::after{background:#66b1ff}:host([color='green']) label.checked{color:#58d68d}:host([color='green']) label.checked .dot{border-color:#58d68d}:host([color='green']) label.checked .dot::after{background:#58d68d}:host([color='teal']) label.checked{color:#3fc2a7}:host([color='teal']) label.checked .dot{border-color:#3fc2a7}:host([color='teal']) label.checked .dot::after{background:#3fc2a7}:host([color='orange']) label.checked{color:#ffb618}:host([color='orange']) label.checked .dot{border-color:#ffb618}:host([color='orange']) label.checked .dot::after{background:#ffb618}:host([color='dark']) label.checked{color:#62778d}:host([color='dark']) label.checked .dot{border-color:#62778d}:host([color='dark']) label.checked .dot::after{background:#62778d}:host([color='purple']) label.checked{color:#ac61ce}:host([color='purple']) label.checked .dot{border-color:#ac61ce}:host([color='purple']) label.checked .dot::after{background:#ac61ce}\n</style> <label> <span class=\"dot\"></span> <slot></slot> </label> ",this.__SWITCH__=this.root.lastElementChild}get value(){return this.props.label}set value(e){this.checked=this.props.label===e}get checked(){return this.props.checked}set checked(e){this.props.checked=!!e,this.__SWITCH__.classList.toggle("checked",this.props.checked)}get readonly(){return this.props.readonly}set readonly(e){var o=typeof e;e!==this.props.readonly&&("boolean"===o&&e||"boolean"!==o?(this.props.readonly=!0,this.setAttribute("readonly","")):(this.props.readonly=!1,this.removeAttribute("readonly")))}get disabled(){return this.props.disabled}set disabled(e){var o=typeof e;e!==this.props.disabled&&("boolean"===o&&e||"boolean"!==o?(this.props.disabled=!0,this.setAttribute("disabled","")):(this.props.disabled=!1,this.removeAttribute("disabled")))}connectedCallback(){this._handleClick=bind(this,"click",e=>{this.disabled||this.readonly||this.checked||(this.checked=!0,this.dispatchEvent(new CustomEvent("input")))})}disconnectedCallback(){unbind(this,"click",this._handleClick)}attributeChangedCallback(e,o,t){if(null!==t&&o!==t)switch(e){case"label":this.props.label=t;break;case"checked":case"readonly":case"disabled":this[e]=!0}}};
if(!customElements.get('wc-radio')){
customElements.define('wc-radio', Radio)
}

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2019-12-11 17:59:30
* @version v2.0.1
*
*/
'use strict'
import{ebind,bind,unbind}from"../utils.js";export default class Star extends HTMLElement{static get observedAttributes(){return["value","text","size","color","allow-half","show-value","starSize","disabled"]}constructor(){super(),Object.defineProperty(this,"root",{value:this.attachShadow({mode:"open"}),writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(this,"props",{value:{value:0,text:[],size:"",color:"","allow-half":!1,"show-value":!1,starSize:32,disabled:!1},writable:!0,enumerable:!1,configurable:!0}),this.root.innerHTML='<style>*{box-sizing:border-box;margin:0;padding:0}::before,::after{box-sizing:border-box}:host{display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;font-size:14px;--size: 24px}label{display:flex;align-items:center;line-height:0;cursor:inherit}label wc-icon{margin:0 3px;transition:transform .1s ease-in-out}label wc-icon:hover{transform:scale(1.05)}label span{padding:0 8px;margin:0 3px}:host([size=\'large\']){font-size:16px;--size: 36px}:host([size=\'medium\']){--size: 30px}:host([size=\'mini\']){font-size:12px;--size: 20px}:host([color=\'red\']) label span{color:#ff5061}:host([color=\'teal\']) label span{color:#3fc2a7}:host([color=\'green\']) label span{color:#58d68d}:host([color=\'grey\']) label span{color:#aabac3}:host([color=\'blue\']) label span{color:#66b1ff}:host([color=\'purple\']) label span{color:#ac61ce}:host([color=\'orange\']) label span{color:#ffb618}:host([disabled]){cursor:default;opacity:0.6}:host([disabled]) label wc-icon:hover{transform:none}\n</style> <label> <wc-icon data-idx="0" is="star" color="grey"></wc-icon> <wc-icon data-idx="1" is="star" color="grey"></wc-icon> <wc-icon data-idx="2" is="star" color="grey"></wc-icon> <wc-icon data-idx="3" is="star" color="grey"></wc-icon> <wc-icon data-idx="4" is="star" color="grey"></wc-icon> <span class="text"></span> </label> ',this.__BOX__=this.root.children[1],this.__STARS__=Array.from(this.__BOX__.children),this.__TEXT__=this.__STARS__.pop()}get value(){return this.props.value}set value(e){var t=+e;(e=t==t&&t>0?t:0)>5&&(e=5),this.props.value=e,this._updateDraw(-1)}_updateDraw(e,t=0){var s="star-half",{value:o,tmp:a={i:0,f:0}}=this.props;-1===e&&(e=Math.floor(o),t=+(o%1).toFixed(1),e>0&&e===o&&(e--,t=1)),this.props["allow-half"]||(t=t>0?1:0),e===a.i&&t===a.f||(t>.5&&(s="star-full"),this.__STARS__.forEach((t,s)=>{t.setAttribute("is",s<e?"star-full":"star"),t.setAttribute("color",s<e?this.props.color:"grey")}),t>0&&(this.__STARS__[e].setAttribute("is",s),this.__STARS__[e].setAttribute("color",this.props.color)),this.props.tmp={i:e,f:t},0===e&&0===t?this.__TEXT__.textContent="":5===this.props.text.length?this.__TEXT__.textContent=this.props.text[e]:this.props["show-value"]&&(this.__TEXT__.textContent=e+t))}connectedCallback(){ebind(this.__BOX__,"mousemove",e=>{if(!this.props.disabled&&"WC-ICON"===e.target.tagName){let t=+e.target.dataset.idx;this._updateDraw(t,+(e.offsetX/this.props.starSize).toFixed(1))}}),ebind(this.__BOX__,"click",e=>{var{tmp:t,disabled:s}=this.props;s||"WC-ICON"===e.target.tagName&&(this.props.value=t.i+t.f,this.dispatchEvent(new CustomEvent("input")))}),ebind(this.__BOX__,"mouseleave",e=>{this.props.disabled||this._updateDraw(-1)})}attributeChangedCallback(e,t,s){if(null!==s&&t!==s)switch(e){case"size":this.props.starSize=this.__STARS__[0].clientWidth;break;case"allow-half":case"show-value":case"disabled":this.props[e]=!0;break;case"color":s&&(this.props.color=s);break;case"text":s&&5===(s=s.split("|")).length&&(this.props.text=s.map(e=>e.trim()));break;case"value":this.value=s}}};
if(!customElements.get('wc-star')){
customElements.define('wc-star', Star)
}

View File

@ -1,15 +0,0 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2019-12-11 17:59:30
* @version v2.0.1
*
*/
'use strict'
import{bind,unbind}from"../utils.js";export default class Switch extends HTMLElement{static get observedAttributes(){return["active-text","inactive-text","checked","disabled"]}constructor(){super(),Object.defineProperty(this,"root",{value:this.attachShadow({mode:"open"}),writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(this,"props",{value:{"active-text":null,"inactive-text":null,checked:!1,disabled:!1},writable:!0,enumerable:!1,configurable:!0}),this.root.innerHTML="<style>*{box-sizing:border-box;margin:0;padding:0}::before,::after{box-sizing:border-box}:host{display:inline-block}:host section{display:flex;justify-content:center;align-items:center;white-space:nowrap}:host label{display:flex;width:38px;height:22px;padding:3px;margin:5px;line-height:0;border-radius:21px;background:#dae1e9;cursor:inherit}:host label.checked{flex-direction:row-reverse;background:#7e909a}:host .dot{width:16px;height:16px;border-radius:50%;background:#fff}:host([disabled]){cursor:not-allowed;opacity:0.6}:host([size='large']) label{width:58px;height:32px}:host([size='large']) .dot{width:26px;height:26px}:host([size='medium']) label{width:50px;height:28px}:host([size='medium']) .dot{width:22px;height:22px}:host([size='mini']) label{width:22px;height:14px;padding:2px}:host([size='mini']) .dot{width:10px;height:10px}:host([color='red']) label.checked{background:#ff5061}:host([color='blue']) label.checked{background:#66b1ff}:host([color='green']) label.checked{background:#58d68d}:host([color='teal']) label.checked{background:#3fc2a7}:host([color='orange']) label.checked{background:#ffb618}:host([color='dark']) label.checked{background:#62778d}:host([color='purple']) label.checked{background:#ac61ce}\n</style> <section> <label> <span class=\"dot\"></span> </label> <slot></slot> </section> ",this.__SWITCH__=this.root.lastElementChild.firstElementChild}get value(){return this.props.checked}set value(e){this.checked=e}get checked(){return this.props.checked}set checked(e){this.props.checked=!!e,this.__SWITCH__.classList.toggle("checked",this.props.checked)}get disabled(){return this.props.disabled}set disabled(e){var t=typeof e;e!==this.props.disabled&&("boolean"===t&&e||"boolean"!==t?(this.props.disabled=!0,this.setAttribute("disabled","")):(this.props.disabled=!1,this.removeAttribute("disabled")))}connectedCallback(){this._handleClick=bind(this,"click",e=>{this.disabled||(this.checked=!this.checked,this.checked?null!==this.props["active-text"]&&(this.textContent=this.props["active-text"]):null!==this.props["inactive-text"]&&(this.textContent=this.props["inactive-text"]),this.dispatchEvent(new CustomEvent("input")))})}disconnectedCallback(){unbind(this,"click",this._handleClick)}attributeChangedCallback(e,t,i){if(null!==i&&t!==i)switch(e){case"checked":case"disabled":this[e]=!0;break;case"active-text":case"inactive-text":this.props[e]=i+""}}};
if(!customElements.get('wc-switch')){
customElements.define('wc-switch', Switch)
}

View File

@ -1,15 +0,0 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2019-12-11 17:59:30
* @version v2.0.1
*
*/
'use strict'
import SVG_DICT from"./svg.js";export default class Icon extends HTMLElement{static get observedAttributes(){return["is"]}constructor(){super(),Object.defineProperty(this,"root",{value:this.attachShadow({mode:"open"}),writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(this,"props",{value:{is:""},writable:!0,enumerable:!1,configurable:!0}),this.root.innerHTML="<style>*{box-sizing:border-box;margin:0;padding:0}::before,::after{box-sizing:border-box}:host{display:inline-block;color:#526273}:host(:not([is])){display:none}.icon{display:block;width:var(--size, 32px);height:var(--size, 32px);fill:currentColor}.icon.load{animation:load 1.5s linear infinite}.icon circle{stroke:currentColor;animation:circle 1.5s ease-in-out infinite}:host([size='large']) .icon{width:42px;height:42px}:host([size='medium']) .icon{width:38px;height:38px}:host([size='mini']) .icon{width:20px;height:20px}:host([color='red']){color:#ff5061}:host([color='blue']){color:#66b1ff}:host([color='green']){color:#58d68d}:host([color='teal']){color:#3fc2a7}:host([color='orange']){color:#ffb618}:host([color='dark']){color:#62778d}:host([color='purple']){color:#ac61ce}:host([color='grey']){color:#aabac3}@keyframes circle{0%{stroke-dasharray:0, 3812px;stroke-dashoffset:0}50%{stroke-dasharray:1906px, 3812px;stroke-dashoffset:-287px}100%{stroke-dasharray:1906px, 3812px;stroke-dashoffset:-2393px}}@keyframes load{to{transform:rotate(360deg)}}\n</style> <svg class=\"icon\" viewBox=\"0 0 1024 1024\"></svg> ",this.__ICO__=this.root.lastElementChild,this.drawPath()}get is(){return this.props.is}drawPath(){var{is:o}=this.props,t=SVG_DICT[o];this.__ICO__&&o&&t&&(this.__ICO__.innerHTML="loading"===o?t:`<path d="${t}" />`,this.__ICO__.classList.toggle("load","loading"===o))}attributeChangedCallback(o,t,e){if(null!==e&&t!==e)switch(o){case"is":this.props.is=e,e?this.drawPath():this.removeAttribute("is")}}};
if(!customElements.get('wc-icon')){
customElements.define('wc-icon', Icon)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
/**
*
* @authors yutent (yutent@doui.cc)
* @date 2019-12-11 17:59:30
* @version v2.0.1
*
*/
'use strict'
import{bind,ebind,unbind}from"../utils.js";const IS_FF=!!window.sidebar;export default class Scroll extends HTMLElement{static get observedAttributes(){return[]}constructor(){super(),Object.defineProperty(this,"root",{value:this.attachShadow({mode:"open"}),writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(this,"props",{value:{},writable:!0,enumerable:!1,configurable:!0}),this.root.innerHTML='<style>*{box-sizing:border-box;margin:0;padding:0}::before,::after{box-sizing:border-box}:host{overflow:hidden;position:relative;display:flex;width:100%}:host .container{overflow:hidden;position:relative;width:100%;height:100%}.is-horizontal,.is-vertical{visibility:hidden;position:absolute;z-index:10240;opacity:0;transition:opacity 0.3s linear, visibility 0.3s linear}.is-horizontal .thumb,.is-vertical .thumb{display:block;border-radius:3px;background:rgba(44,47,53,0.25);cursor:default}.is-horizontal .thumb:hover,.is-vertical .thumb:hover{background:rgba(44,47,53,0.5)}.is-horizontal{left:0;bottom:1px;width:100%;height:6px}.is-horizontal .thumb{width:0;height:6px}.is-vertical{top:0;right:1px;width:6px;height:100%}.is-vertical .thumb{width:6px;height:0}:host(:hover) .is-horizontal,:host(:hover) .is-vertical{visibility:visible;opacity:1}\n</style> <div class="container"><slot></slot></div> <div class="is-horizontal"><span class="thumb"></span></div> <div class="is-vertical"><span class="thumb"></span></div> ',this.__BOX__=this.root.children[1],this.__X__=this.root.children[2].children[0],this.__Y__=this.root.children[3].children[0]}get scrollTop(){return this.__BOX__.scrollTop}set scrollTop(t){if((t=+t)==t){var{sh:s,oh:i,yh:e}=this.props;this.__BOX__.scrollTop=t;var o=this.__BOX__.scrollTop/(s-i)*(i-e);this.props.thumbY=o,this.__Y__.style.transform=`translateY(${o}px)`}}get scrollLeft(){return this.__BOX__.scrollLeft}set scrollLeft(t){if(n=+n,n==n){var{sw:s,ow:i,xw:e}=this.props;this.__BOX__.scrollLeft=n;var o=this.__BOX__.scrollLeft/(s-i)*(i-e);this.props.thumbX=o,this.__X__.style.transform=`translateX(${o}px)`}}get scrollHeight(){return this.__BOX__.scrollHeight}_fetchScrollX(t){var{sw:s,ow:i,xw:e}=this.props;return t<0?t=0:t>i-e&&(t=i-e),this.__BOX__.scrollLeft=t/(i-e)*(s-i),this.__X__.style.transform=`translateX(${t}px)`,t}_fetchScrollY(t){var{sh:s,oh:i,yh:e}=this.props;return t<0?t=0:t>i-e&&(t=i-e),this.__BOX__.scrollTop=t/(i-e)*(s-i),this.__Y__.style.transform=`translateY(${t}px)`,t}connectedCallback(){this._initFn=bind(this.__BOX__,"mouseenter",t=>{var s=this.__BOX__.offsetWidth,i=this.__BOX__.scrollWidth,e=this.__BOX__.offsetHeight,o=this.__BOX__.scrollHeight,r=e*e/o>>0,h=s*s/i>>0;r<50&&(r=50),h<50&&(h=50),h===s&&(h=0),r===e&&(r=0),this.props.oh=e,this.props.sh=o,this.props.ow=s,this.props.sw=i,this.props.yh=r,this.props.xw=h,this.__X__.style.width=h+"px",this.__Y__.style.height=r+"px"}),this._wheelFn=ebind(this.__BOX__,"wheel",t=>{t.preventDefault();var{sh:s,oh:i,yh:e,sw:o,ow:r,xw:h}=this.props;if(h||e){var _,l;if(IS_FF)_=t.deltaMode?10*t.deltaX:t.deltaX,l=t.deltaMode?10*t.deltaY:t.deltaY;else{var n=Math.abs(t.wheelDelta);n<120?(_=t.deltaX,l=t.deltaY):(_=t.deltaX/(n/120),l=t.deltaY/(n/120))}if(this.__BOX__.scrollTop+=l,this.__BOX__.scrollLeft+=_,h){var a=this.__BOX__.scrollLeft/(o-r)*(r-h);this.props.thumbX=a,this.__X__.style.transform=`translateX(${a}px)`}if(e){var p=this.__BOX__.scrollTop/(s-i)*(i-e);this.props.thumbY=p,this.__Y__.style.transform=`translateY(${p}px)`}}});var t,s,i,e,o=o=>{var{thumbY:r,thumbX:h}=this.props;null!==t&&(i=this._fetchScrollX(h+o.pageX-t)),null!==s&&(e=this._fetchScrollY(r+o.pageY-s))},r=h=>{t=null,s=null,this.props.thumbX=i,this.props.thumbY=e,unbind(document,"mousemove",o),unbind(document,"mouseup",r)};bind(this.__Y__,"mousedown",t=>{s=t.pageY,this.props.thumbY||(this.props.thumbY=0),bind(document,"mousemove",o),bind(document,"mouseup",r)}),bind(this.__X__,"mousedown",s=>{t=s.pageX,this.props.thumbX||(this.props.thumbX=0),bind(document,"mousemove",o),bind(document,"mouseup",r)}),this.__observer=new MutationObserver(this._initFn),this.__observer.observe(this,{childList:!0,subtree:!0,characterData:!0})}disconnectedCallback(){this.__observer.disconnect(),unbind(this.__BOX__,"mouseenter",this._initFn),unbind(this.__BOX__,"wheel",this._wheelFn)}};
if(!customElements.get('wc-scroll')){
customElements.define('wc-scroll', Scroll)
}

View File

@ -1 +0,0 @@
function noop(){}export const nextTick=function(){let t=[];let n=document.createTextNode("\x3c!-- --\x3e");new MutationObserver(function(){let n=t.length;for(let e=0;e<n;e++)t[e]();t=t.slice(n)}).observe(n,{characterData:!0});let e=!1;return function(o){t.push(o),e=!e,n.data=e}}();export const each=function(t,n){if(t)if(Array.isArray(t))for(let e,o=0;(e=t[o++])&&!1!==n(e,o-1););else for(let e in t)if(t.hasOwnProperty(e)&&!1===n(t[e],e))break};export const bind=function(t,n,e=noop,o=!1){let r=n.split(",");return each(r,function(n){n=n.trim(),t.addEventListener(n,e,o)}),e};export const ebind=function(t,n,e,o){return bind(t,n,function(t){t.stopPropagation(),e&&e(t)},o)};export const unbind=function(t,n,e=noop,o=!1){let r=n.split(",");each(r,function(n){n=n.trim(),t.removeEventListener(n,e,o)})};export const clickOutside=function(t,n=noop){return bind(document,"mousedown",e=>{if(e)if(e.path){for(var o=e.path.concat();o.length>3;)if(o.shift()===t)return}else{var r=e.explicitOriginalTarget||e.target;if(t===r||t.contains(r)||t.root&&t.root.contains(r))return}n(e)})};

View File

@ -1,77 +0,0 @@
/**
*
* @author yutent<yutent@doui.cc>
* @date 2019/09/16 20:51:19
*/
const { app, BrowserWindow, protocol, Menu } = require('electron')
const path = require('path')
const fs = require('iofs')
require('./tools/init')
const createMenu = require('./tools/menu')
const log = console.log
const MIME_TYPES = {
'.js': 'text/javascript',
'.html': 'text/html',
'.htm': 'text/plain',
'.css': 'text/css',
'.jpg': 'image/jpg',
'.png': 'image/png',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/ico'
}
const ROOT = __dirname
/* ----------------------------------------------------- */
app.commandLine.appendSwitch('--lang', 'zh-CN')
app.commandLine.appendSwitch('--autoplay-policy', 'no-user-gesture-required')
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
/* ----------------------------------------------------- */
// 初始化应用
app.once('ready', () => {
// 注册协议
protocol.registerBufferProtocol('app', (req, cb) => {
let file = req.url.replace(/^app:\/\/local\//, '')
let ext = path.extname(req.url)
let buff = fs.cat(path.resolve(ROOT, file))
cb({ data: buff, mimeType: MIME_TYPES[ext] })
})
Menu.setApplicationMenu(null)
// 创建浏览器窗口
let win = new BrowserWindow({
title: '伪域名解析',
width: 1000,
height: 640,
resizable: false,
maximizable: false,
icon: path.resolve(ROOT, './images/app.png'),
webPreferences: {
webSecurity: false,
experimentalFeatures: true,
nodeIntegration: true
}
})
win.on('closed', () => {
app.exit()
win = null
})
// win.openDevTools()
// 然后加载应用的 index.html
win.loadURL('app://local/index.html')
if (process.platform === 'darwin') {
createMenu(win)
}
})

View File

@ -1,128 +0,0 @@
/**
* 配置/DB通讯
* @author yutent<yutent@doui.cc>
* @date 2019/01/26 18:11:26
*/
'use strict'
const { app, ipcMain } = require('electron')
const fs = require('iofs')
const path = require('path')
const HOST_FILE = path.resolve(app.getPath('userData'), 'host.cache')
const LOCK_FILE = path.resolve(app.getPath('userData'), 'lock')
if (!fs.exists(HOST_FILE)) {
fs.echo('{}', HOST_FILE)
}
/* ********** 修复环境变量 start *********** */
let PATH_SET = new Set()
process.env.PATH.split(':').forEach(_ => {
PATH_SET.add(_)
})
PATH_SET.add('/usr/local/bin')
PATH_SET.add('/usr/local/sbin')
process.env.PATH = Array.from(PATH_SET).join(':')
PATH_SET = null
/* ********** 修复环境变量 end *********** */
var timer = null
ipcMain.on('dns-host', (ev, conn) => {
switch (conn.type) {
// 获取指定目录下的文件和目录
case 'get':
var cache = fs.cat(HOST_FILE)
ev.returnValue = JSON.parse(cache)
break
// 设置应用配置
case 'set':
clearTimeout(timer)
timer = setTimeout(() => {
var txt = ''
for (let k in conn.data) {
for (let it of conn.data[k]) {
if (it.enabled) {
var name = it.record === '@' ? '' : it.record
if (name) {
name += '.'
}
txt += `${it.value.padEnd(15, ' ')} ${name + k}\n`
}
}
txt += '\n\n'
}
fs.echo(JSON.stringify(conn.data), HOST_FILE)
fs.echo(txt, '/etc/hosts')
}, 1000)
break
case 'check':
try {
var stat = fs.echo('', '/etc/hosts', true)
ev.returnValue = true
} catch (e) {
ev.returnValue = false
}
break
case 'history':
if (fs.exists(LOCK_FILE)) {
var cache = fs.cat(HOST_FILE)
ev.returnValue = JSON.parse(cache)
return
}
var cache = fs.cat('/etc/hosts').toString()
var records = cache.split(/[\n\r]+/)
var list = []
var dict = {}
records.forEach(str => {
str = str.trim()
let matches = str.match(/^(#*?)\s*(\d+\.\d+\.\d+\.\d+)\s+(.*)/)
if (matches) {
let names = matches[3].split(/\s+/).map(it => it.trim())
let name
while ((name = names.pop())) {
list.push({ ip: matches[2], enabled: !matches[1], name })
}
}
})
records = null
list.forEach(it => {
it.name = it.name.split('.')
let domain = it.name.splice(-2, 2).join('.')
if (domain === 'com.cn' || domain === 'org.cn' || domain === 'net.cn') {
domain = it.name.pop() + '.' + domain
}
if (dict[domain]) {
dict[domain].push({
value: it.ip,
enabled: it.enabled,
record: it.name.join('.') || '@',
remark: ''
})
} else {
dict[domain] = [
{
value: it.ip,
enabled: it.enabled,
record: it.name.join('.') || '@',
remark: ''
}
]
}
})
list = null
ev.returnValue = dict
fs.echo(JSON.stringify(dict), HOST_FILE)
fs.echo('', LOCK_FILE)
}
})

View File

@ -1,67 +0,0 @@
/**
* 菜单项
* @author yutent<yutent@doui.cc>
* @date 2019/01/21 20:34:04
*/
'use strict'
const { Menu } = require('electron')
module.exports = function(win) {
let menuList = Menu.buildFromTemplate([
{
label: '伪域名解析',
submenu: [
{ role: 'about', label: '关于 伪域名解析' },
{ type: 'separator' },
{ role: 'quit', label: '退出' }
]
},
{
label: '编辑',
submenu: [
{ role: 'undo', label: '撤消重做' },
{ role: 'redo', label: '重做' },
{ type: 'separator' },
{ role: 'cut', label: '剪切' },
{ role: 'copy', label: '复制' },
{ role: 'paste', label: '粘贴' },
{ role: 'selectall', label: '全选' }
]
},
{
label: '显示',
submenu: [
{ label: '伪域名解析' },
{
type: 'separator'
},
{ label: '伪域名解析' }
]
},
{
label: '窗口',
submenu: [
{
role: 'minimize',
label: '最小化',
click() {
win.minimize()
}
}
]
},
{
role: 'help',
label: '帮助',
submenu: [
{
label: '官网',
click() {}
}
]
}
])
Menu.setApplicationMenu(menuList)
}

7
test.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
export RUN_ENV='development'
./usr/lib/hosts-switch/hosts-switch.py

3
usr/bin/hosts-switch Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
/usr/share/hosts-switch/hosts-switch.py

View File

@ -0,0 +1,9 @@
[Desktop Entry]
Name=Hosts Switch
Comment[zh_CN]=Hosts文件修改器
Exec=hosts-switch
Icon=hosts-switch
Terminal=false
X-MultipleArgs=false
Type=Application
Categories=Development;

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Window(Gtk.Window):
def __init__(self, title = 'Untitled window', width = 840, height = 520):
Gtk.Window.__init__(self, title = title)
self.set_default_size(width, height)
self.resize(width, height)
self.set_wmclass('Hosts-Switch', 'Hosts-Switch')
self.set_title('Hosts-Switch')
self.set_icon_name('hosts-switch')
def toggle_visible(self, icon):
if self.is_visible():
self.hide()
else:
self.present()

View File

@ -0,0 +1,67 @@
#!/usr/bin/python3
import gi, os, sys
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, Gio, GObject, GdkPixbuf
from webengine.gtk3 import WebEngine, create_setting, create_hmr_server
from _window import Window
APP_ID = 'fun.wkit.hosts-switch'
__dir__ = os.path.dirname(os.path.realpath(__file__))
web_root = os.path.join(__dir__, './webapp')
home_dir = os.getenv('HOME')
config_dir = os.path.join(home_dir, '.config/hosts-switch')
if not os.path.isdir(config_dir):
os.mkdir(config_dir)
class Application(Gtk.Application):
def __init__(self):
Gtk.Application.__init__(self, application_id = APP_ID)
self.window = Window()
web = WebEngine(self.window)
web.set_root(web_root)
if os.getenv('RUN_ENV') == 'development':
setting = create_setting({"devtools": True})
hmr = create_hmr_server()
web.use(setting).use(hmr)
web.connect('quit', self.quit_all)
web.load()
self.window.add(web)
def do_activate(self):
self.set_app_menu(None)
self.set_menubar(None)
self.add_window(self.window)
self.window.show_all()
def quit_all(self, widget):
self.remove_window(self.window)
if __name__ == "__main__":
try:
app = Application()
app.run(sys.argv)
except Exception as err:
print(err)

View File

@ -0,0 +1,41 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2023/07/20 14:19:13
*/
import 'es.shim'
import { html, css, Component } from 'wkit'
import { createApp, createRouter } from 'wkitd'
import store from './store.js'
import './components/home.js'
import { noop } from './utils/index.js'
createApp({
data: {
input: '',
img: '',
content: ''
},
styles: [
css`
:host {
display: flex;
width: 100%;
height: 100vh;
}
`
],
methods: {
quit() {
native.quit()
}
},
render() {
return html` <wc-home @contextmenu.prevent=${noop}></wc-home> `
}
})
.use(store)
.mount()

View File

@ -0,0 +1,262 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2023/08/08 18:19:17
*/
import { html, css, Component, classMap, nextTick, outsideClick } from 'wkit'
import 'ui/icon/index.js'
import 'ui/scroll/index.js'
import 'ui/layer/index.js'
import 'ui/space/index.js'
import 'ui/form/input.js'
import 'ui/form/switch.js'
import 'ui/form/button.js'
import 'ui/form/link.js'
import './sidebar.js'
import './permission.js'
import './records.js'
import { checkPermission, getHistory, saveHosts, noop } from '../utils/index.js'
const HOST_DATA = await getHistory()
class Home extends Component {
static props = {
editDomain: '', // 当前临时要编辑的域名, 即右键菜单选择到的
permissionShow: false
}
static styles = [
css`
:host {
flex: 1;
display: flex;
width: 100%;
height: 100%;
padding: 32px;
color: var(--color-dark-1);
background: #f0f0f0;
}
.visible {
display: block;
}
ul li {
list-style: none;
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
`,
css`
.main {
flex: 1;
display: flex;
flex-direction: column;
}
.main .toolbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 15px;
border-bottom: 1px solid var(--color-plain-3);
background: var(--color-plain-1);
}
.main .list {
overflow: hidden;
flex: 1;
}
`,
css`
.context-menu {
display: flex;
flex-direction: column;
width: 100px;
padding: 5px 0;
background: #fff;
}
.context-menu .item {
height: 30px;
line-height: 30px;
padding: 0 15px;
cursor: pointer;
}
.context-menu .item :hover {
background: #f2f5fc;
}
`
]
async mounted() {
let writable = await checkPermission()
if (writable) {
this.$store.HOST_DATA = HOST_DATA
this.$store.domains = Object.keys(HOST_DATA)
this.$refs.domain.mounted()
outsideClick(this.$refs.context, _ => this.$refs.context.close())
} else {
this.permissionShow = true
}
}
addRecord() {
if (this.$store.activeDomain) {
this.$store.records.push({
record: '',
value: '',
enabled: true,
remark: ''
})
nextTick(_ => (this.$refs.list.scrollTop = 1e6))
} else {
layer.toast('请先选择域名', 'warn')
}
}
showMenu(ev) {
this.$refs.context.close()
var { pageX, pageY } = ev
if (pageY + 70 > 600) {
pageY -= 70
}
var elem = ev.target
if (elem.tagName !== 'LI') {
elem = elem.parentNode
}
this.editDomain = elem.dataset.name
setTimeout(_ => {
this.$refs.context.moveTo({ left: pageX + 'px', top: pageY + 'px' })
this.$refs.context.show()
})
}
confirmAction(ev) {
this.$refs.context.close()
if (ev.target.tagName === 'LI') {
let act = ev.target.dataset.act
let { HOST_DATA, records, domains } = this.$store
let idx = domains.indexOf(this.editDomain)
if (act === 'del') {
layer
.confirm(`是否要删除域名「${this.editDomain}」?`)
.then(res => {
if (this.editDomain === this.$store.activeDomain) {
if (records.length) {
return layer.toast(
'该域名下有主机记录, 请先删除主机记录后再删除域名',
'error'
)
}
} else {
if (HOST_DATA[this.editDomain].length > 0) {
return layer.toast(
'该域名下有主机记录, 请先删除主机记录后再删除域名',
'error'
)
}
}
delete HOST_DATA[this.editDomain]
domains.splice(idx, 1)
this.editDomain = ''
this.$store.records = []
this.$store.activeDomain = domains[0]
this.$refs.domain.mounted()
this.save()
})
.catch(noop)
} else if (act === 'edit') {
layer
.prompt(
`请输入新的名字「${this.editDomain}`,
this.editDomain,
(val, done) => {
if (val === this.editDomain || HOST_DATA[val]) {
return layer.toast(`${val} 域名没有变化, 或已经存在`)
}
if (
val === 'localhost' ||
val === 'local' ||
/^[\w.]+\.[a-z]+$/.test(val)
) {
done()
} else {
layer.toast('域名格式错误', 'error')
}
}
)
.then(val => {
domains[idx] = val
HOST_DATA[val] = HOST_DATA[this.editDomain]
delete HOST_DATA[this.editDomain]
this.$store.activeDomain = val
this.editDomain = ''
this.$refs.domain.mounted()
this.save()
})
.catch(noop)
}
}
}
save() {
let { HOST_DATA, activeDomain, records } = this.$store
if (activeDomain) {
HOST_DATA[activeDomain] = records.filter(it => it.record && it.value)
}
saveHosts(HOST_DATA)
layer.toast('保存成功', 'success')
}
render() {
return html`
<wc-sidebar
ref="domain"
@toggle-domain=${_ => (this.$refs.list.scrollTop = 0)}
@show-menu=${ev => this.showMenu(ev.event)}
@save=${this.save}
></wc-sidebar>
<main class="main noselect">
<header class="toolbar">
<wc-button size="m" icon="plus" @click=${this.addRecord}
>新增记录</wc-button
>
<wc-button size="m" icon="fly" @click=${this.save}>保存</wc-button>
</header>
<wc-records class="list" ref="list"></wc-records>
</main>
<wc-permission
class=${classMap({ visible: this.permissionShow })}
></wc-permission>
<wc-layer ref="context" left="100px" top="0" radius="0">
<ul class="context-menu" @click=${this.confirmAction}>
<li class="item" data-act="del">删除域名</li>
<li class="item" data-act="edit">修改域名</li>
</ul>
</wc-layer>
`
}
}
Home.reg('home')

View File

@ -0,0 +1,109 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2023/08/08 18:19:17
*/
import { html, css, Component } from 'wkit'
import { checkPermission } from '../utils/index.js'
const tips_header = `/************************************************************/
* hosts文件没有写权限 *
/************************************************************/
`
class Permission extends Component {
static styles = css`
:host {
display: none;
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
.permission-error {
position: fixed;
left: 0;
top: 0;
z-index: 1024;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
padding: 24px 52px;
line-height: 1.5;
background: rgba(255, 233, 233, 0.95);
-webkit-backdrop-filter: blur(5px);
}
pre {
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
color: var(--color-red-1);
}
fieldset {
width: 600px;
padding: 0 30px 30px;
border: 1px solid var(--color-orange-1);
border-radius: 8px;
}
legend {
padding: 0 10px;
font-size: 16px;
}
dt {
margin-top: 20px;
font-weight: bold;
}
code {
display: block;
padding: 8px 10px;
margin-top: 8px;
border-left: 3px solid var(--color-plain-3);
background: rgba(255, 255, 255, 0.8);
font-family: 'Courier New', Courier, monospace;
}
`
async check() {
let writable = await checkPermission()
if (writable) {
location.reload()
} else {
layer.toast('hosts文件没有写权限, 请按提示修改', 'error')
}
}
render() {
return html`
<div class="permission-error">
<pre class="noselect">${tips_header}</pre>
<fieldset>
<legend>操作指引</legend>
<dl>
<dt>MacOS用户</dt>
<dd>打开终端, 执行以下命令</dd>
<dd><code>sudo chown $USER:admin /etc/hosts</code></dd>
<dt>Linux用户</dt>
<dd>打开终端, 执行以下命令</dd>
<dd><code>sudo chown $USER: /etc/hosts</code></dd>
<dt>完成之后</dt>
<dd>点击下面的按钮重新检测.</dd>
<dd>
<wc-button type="danger" @click=${this.check}>权限检测</wc-button>
</dd>
</dl>
</fieldset>
</div>
`
}
}
Permission.reg('permission')

View File

@ -0,0 +1,158 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2023/08/08 18:19:17
*/
import { html, css, Component, classMap, nextTick } from 'wkit'
import { noop } from '../utils/index.js'
class Records extends Component {
static styles = css`
:host {
display: flex;
flex-direction: column;
padding: 8px;
background: #fff;
}
.thead {
flex-shrink: 0;
}
.records {
overflow: hidden;
flex: 1;
}
.item {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
padding: 0 4px;
border-bottom: 1px solid var(--color-plain-2);
text-align: center;
}
.thead {
line-height: 40px;
}
.item span {
flex: 1;
}
.item .long {
flex: 1.5;
}
.item wc-input {
flex: 1;
min-width: 0;
--wc-input-border-color: transparent;
}
.item:last-child {
border-bottom: none;
}
`
set scrollTop(val) {
this.$refs.records.scrollTop = val
}
// 同一个记录, 允许一条被激活
recordChanges(ev, item) {
let { tmp_records } = this.$store
item.enabled = ev.target.value
if (item.enabled) {
if (tmp_records[item.record].length > 1) {
for (let it of tmp_records[item.record]) {
if (it.value !== item.value) {
it.enabled = false
}
}
}
}
}
updateCacheDict(ev, item) {
let { records } = this.$store
let tmp_records = Object.create(null)
item.record = ev.target.value
for (let it of records) {
if (tmp_records[it.record]) {
tmp_records[it.record].push(it)
} else {
tmp_records[it.record] = [it]
}
}
this.$store.tmp_records = tmp_records
}
clone(item) {
let { tmp_records, records } = this.$store
var params = { ...item }
params.enabled = false
records.push(params)
tmp_records[params.record].push(records.at(-1))
nextTick(_ => (this.$refs.records.scrollTop = 1e6))
}
render() {
return html`
<header class="thead item">
<span>主机记录</span>
<span>类型</span>
<span class="long">记录值</span>
<span>操作</span>
<span>备注</span>
</header>
<wc-scroll class="records" ref="records">
${this.$store.records.map(
(it, i) => html`
<div class="item">
<wc-input
size="m"
autofocus
@change=${ev => this.updateCacheDict(ev, it)}
:value=${it.record}
label="根域请填 @"
></wc-input>
<span>A</span>
<wc-input
size="m"
class="long"
:value=${it.value}
@change=${ev => (it.value = ev.target.value)}
label="请填写IP"
></wc-input>
<wc-space gap="s">
<wc-link
size="m"
type="danger"
@click=${ev => this.$store.records.splice(i, 1)}
>删除</wc-link
>
<wc-link size="m" type="info" @click=${ev => this.clone(it)}
>克隆</wc-link
>
<wc-switch
size="m"
:value=${it.enabled}
@change=${ev => this.recordChanges(ev, it)}
></wc-switch>
</wc-space>
<wc-input size="m" no-border :value=${it.remark}></wc-input>
</div>
`
)}
</wc-scroll>
`
}
}
Records.reg('records')

View File

@ -0,0 +1,165 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2023/08/08 18:19:17
*/
import { html, css, Component, classMap, nextTick } from 'wkit'
import { noop } from '../utils/index.js'
class Sidebar extends Component {
static styles = css`
:host {
flex-shrink: 0;
display: flex;
flex-direction: column;
width: 180px;
height: 100vh;
background: var(--color-plain-2);
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
.domain-list {
overflow: hidden;
flex: 1;
width: 100%;
}
.domain-list .item {
display: flex;
justify-content: flex-end;
align-items: center;
height: 40px;
padding: 0 12px;
cursor: pointer;
transition: background 0.15s ease-in-out;
}
.item wc-icon {
--wc-icon-size: 12px;
margin-left: 8px;
color: var(--color-grey-2);
}
.item:hover,
.item.active {
background: #fff;
}
.item.active {
border-right: 2px solid var(--color-orange-1);
color: var(--color-orange-1);
}
.item.active wc-icon {
color: var(--color-orange-1);
}
.item.blank {
justify-content: center;
cursor: default;
}
.item.blank:hover {
background: none;
}
.action {
flex-shrink: 0;
display: flex;
align-items: center;
height: 50px;
padding: 0 10px;
}
`
addDomain() {
layer
.prompt('请输入根域名', function (val, done) {
if (
val === 'localhost' ||
val === 'local' ||
/^[\w.\-]+\.[a-z]+$/.test(val)
) {
done()
} else {
layer.toast('域名格式错误', 'error')
}
})
.then(val => {
let { HOST_DATA, records } = this.$store
this.$store.domains.push(val)
HOST_DATA[val] = []
if (!this.$store.activeDomain) {
this.toggleDomain(null, val)
}
this.$emit('save')
})
.catch(noop)
}
toggleDomain(ev, name) {
let { HOST_DATA, records } = this.$store
name = name ?? ev.currentTarget.dataset.name
this.$store.activeDomain = name
this.$store.records = records = (HOST_DATA[name] || []).sort((a, b) =>
a.record.localeCompare(b.record)
)
let tmp_records = Object.create(null)
for (let it of records) {
if (tmp_records[it.record]) {
tmp_records[it.record].push(it)
} else {
tmp_records[it.record] = [it]
}
}
this.$store.tmp_records = tmp_records
document.title = `伪域名解析 ${name} `
localStorage.setItem('last_domain', name)
nextTick(() => {
this.$emit('toggle-domain')
})
}
mounted() {
this.toggleDomain(null, this.$store.activeDomain)
}
render() {
return html`
<wc-scroll class="domain-list noselect">
<ul
@contextmenu.prevent=${ev => this.$emit('show-menu', { event: ev })}
>
${this.$store.domains.map(
it => html`
<li
class=${classMap({
item: true,
active: it === this.$store.activeDomain
})}
data-name=${it}
@click=${this.toggleDomain}
>
<span>${it}</span>
<wc-icon name="right"></wc-icon>
</li>
`
)}
${this.$store.domains.length < 1
? html`<li class="item blank">没有域名</li>`
: ''}
</ul>
</wc-scroll>
<section class="action">
<wc-button circle icon="plus" @click=${this.addDomain}> </wc-button>
</section>
`
}
}
Sidebar.reg('sidebar')

View File

@ -0,0 +1,82 @@
@charset "UTF-8";
/**
*
* @authors yutent (yutent.io@gmail.com)
* @date 2014-10-10 00:45:09
*
* CSS
*
* ,
*
*
* 1 display float position overflow z-index /
* 2 width height margin padding border
* 3 line-height font-size vertical-align text-align user-select outline ....
* 4 color background opacity cursor ...
* 5 content list-style quotes ...
*
*/
* {margin: 0;padding: 0;vertical-align: baseline;box-sizing: border-box;}
::before, ::after {box-sizing: border-box;}
/* HTML5 display-role reset for older browsers */
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section,content {display: block;}
img {border: 0;display: inline-block;}
ol,ul {list-style: none;}
blockquote, q {quotes: none;}
blockquote::before, blockquote::after, q::before, q::after {content: '';content: none;}
table {border-collapse: collapse;border-spacing: 0;}
a:focus,input,textarea,button:focus,input:focus,textarea:focus {outline: none;}
::-moz-focus-inner {border: none;outline: none;}
body {font-family: 'Helvetica Neue', Arial, 'WenQuanYi Micro Hei', 'PingFang SC', 'Hiragino Sans GB', 'Segoe UI', 'Microsoft Yahei', sans-serif;-webkit-font-smoothing: antialiased;text-size-adjust: 100%;-webkit-tap-highlight-color: transparent;}
code, pre, samp {font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;white-space:pre-wrap;}
[anot],[\:repeat],[\:if] {visibility: hidden;}
.noselect {-webkit-touch-callout: none;-webkit-user-select: none;user-select: none;}
.noselect img, .noselect a {-webkit-user-drag: none;}
.text-ell {overflow: hidden;white-space: nowrap;text-overflow: ellipsis;}
.text-thin {-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
:root {
/* primary */
--color-teal-a: rgba(72, 201, 176, 0.35);
--color-teal-1: rgb(72, 201, 176);
--color-teal-2: rgb(26, 188, 156);
--color-teal-3: rgb(22, 160, 133);
/* success */
--color-green-a: rgba(70, 221, 126, 0.35);
--color-green-1: rgb(70, 221, 126);
--color-green-2: rgb(47, 208, 105);
--color-green-3: rgb(26, 196, 88);
/* info */
--color-blue-a: rgba(100, 181, 246, 0.35);
--color-blue-1: rgb(100, 181, 246);
--color-blue-2: rgb(66, 165, 245);
--color-blue-3: rgb(33, 150, 243);
/* danger */
--color-red-a: rgba(252, 118, 97, 0.35);
--color-red-1: #fc7661;
--color-red-2: #ff5f45;
--color-red-3: #f33e22;
/* warning */
--color-orange-a: rgba(254, 174, 117, 0.35);
--color-orange-1: #feae75;
--color-orange-2: #fd964b;
--color-orange-3: #f97316;
/* default1 */
--color-plain-a: rgba(150, 204, 248, 0.35);
--color-plain-1: rgb(242, 245, 252);
--color-plain-2: rgb(232, 235, 244);
--color-plain-3: rgb(218, 225, 233);
/* default2 */
--color-grey-a: rgba(206, 214, 224, 0.35);
--color-grey-1: rgb(206, 214, 224);
--color-grey-2: rgb(164, 176, 190);
--color-grey-3: rgb(134, 144, 155);
/* inverse */
--color-dark-a: rgba(100, 116, 139, 0.35);
--color-dark-1: #64748B;
--color-dark-2: #475569;
--color-dark-3: #2c3441;
}

View File

@ -0,0 +1,35 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<link rel="stylesheet" href="/css/reset.css">
<style>
html,body {
width:100%;
height: 100%;
line-height: 1.5;
font-size: 14px;
cursor:default;
}
a {color:inherit;text-decoration: none;}
</style>
<script type="importmap">
{
"imports":{
"es.shim":"app:///lib/es.shim.js",
"wkit":"app:///lib/wkit.js",
"wkitd":"app:///lib/wkitd.js",
"fetch":"app:///lib/fetch.js",
"crypto":"app:///lib/crypto.js",
"ui/":"app:///lib/ui/"
}
}
</script>
<script type="module" src="/app.js"></script>
</head>
<body>
<wc-app></wc-app>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
import { css, html, Component } from "wkit";
class Card extends Component {
static props = {
header: ""
};
static styles = css`:host{display:flex;border-radius:3px}.card-box{display:flex;flex-direction:column;position:relative;width:100%;border:1px solid var(--color-plain-2);border-radius:inherit;background:#fff;color:var(--color-dark-1);transition:box-shadow .2s ease-in-out;box-shadow:0 0 12px rgba(0,0,0,.12)}.card-box .header{display:flex;align-items:center;justify-content:space-between;width:100%;min-height:52px;padding:var(--card-padding, 8px 16px);border-bottom:1px solid var(--color-plain-2);font-size:16px;user-select:none}.card-box .content{flex:1;min-height:64px;padding:var(--card-padding, 8px 16px);font-size:14px;color:var(--color-dark-1)}:host([shadow=never]) .card-box,:host([shadow=hover]) .card-box{box-shadow:none}:host([shadow=hover]:hover) .card-box{box-shadow:0 0 12px rgba(0,0,0,.12)}`;
render() {
return html`
<div class="card-box">
<div class="header"><slot name="header">${this.header}</slot></div>
<div class="content"><slot></slot></div>
</div>
`;
}
}
Card.reg("card");

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,35 @@
import{nextTick as r,css as l,html as s,Component as a,classMap as h,outsideClick as c}from"wkit";import"../icon/index.js";const d={duration:100,custom:[{transform:"scaleY(0)",opacity:0},{transform:"scaleY(1)",opacity:1}]};class u extends a{static props={readOnly:!1,autofocus:!1,disabled:!1,clearable:!1,icon:"",placeholder:"",maxlength:{type:Number,default:null},minlength:{type:Number,default:null},value:"str!",lazy:"num!0"};#t=[];#e=-1;#i=!1;#s=0;static styles=[l`:host{position:relative;display:inline-flex;min-width:188px;height:32px;user-select:none;-moz-user-select:none;color:var(--color-dark-1);border-radius:3px;cursor:text;transition:box-shadow .15s linear}.label{flex:1;display:flex;justify-content:center;align-items:center;height:100%;font-size:14px;border:1px solid var(--wc-input-border-color, var(--color-grey-2));border-radius:inherit;background:var(--bg-color, #fff);color:inherit;cursor:inherit}.label input{flex:1;min-width:36px;width:0;height:100%;padding:0 8px;line-height:1;border:0;border-radius:inherit;font:inherit;color:inherit;background:none;outline:none;box-shadow:none;cursor:inherit}.label input::placeholder{color:var(--color-grey-1)}.label .close{--wc-icon-size: 18px;margin:0 8px 0 4px;padding:4px;border-radius:50%;color:var(--color-grey-2);cursor:pointer;transition:background .15s linear}.label .close:hover{background:var(--color-plain-1)}.label .icon{--wc-icon-size: 16px;margin:0 8px 0 4px;color:var(--color-grey-2)}.suggestion{overflow:hidden;position:absolute;z-index:1;left:0;top:calc(100% + 4px);width:100%;padding:4px 0;border-radius:4px;box-shadow:0 2px 5px rgba(0,0,0,.15);transform-origin:top}.suggestion .list{width:100%;background:#fff}.suggestion.hide{display:none}.suggestion li{overflow:hidden;width:100%;height:30px;line-height:30px;padding:0 8px;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.suggestion li:hover,.suggestion li[focus]{background:var(--color-plain-2)}:host([round]){border-radius:26px}:host([round]) .label input{padding:0 10px}:host(:focus-within){box-shadow:0 0 0 2px var(--color-plain-a)}:host([disabled]){pointer-events:none;cursor:not-allowed}:host([disabled]) .label{border-color:var(--color-grey-1);background:var(--color-plain-1);opacity:.6}:host([readonly]){cursor:default}`,l`:host([size=m]){min-width:128px;height:24px;font-size:12px}:host([size=m]) .label{height:24px;font-size:12px}:host([size=m]) .icon{--wc-icon-size: 12px}:host([size=m][circle]){width:24px;height:24px}:host([size=xl]){min-width:224px;height:36px;font-size:14px}:host([size=xl]) .label{height:36px;font-size:14px}:host([size=xl]) .icon{--wc-icon-size: 14px}:host([size=xl][circle]){width:36px;height:36px}:host([size=xxl]){min-width:288px;height:44px;font-size:14px}:host([size=xxl]) .label{height:44px;font-size:14px}:host([size=xxl]) .icon{--wc-icon-size: 14px}:host([size=xxl][circle]){width:44px;height:44px}`];renderClear(){return s`<wc-icon class="close" name="close" @click=${this.clear} />`}render(){let e=h({suggestion:!0,hide:!this.#t.length});return s`
<div class="label">
<slot name="prepend">
<wc-icon class="icon" name=${this.icon}></wc-icon>
</slot>
<input
ref="input"
@input=${this.handleInput}
@change=${this.handleChange}
@keydown=${this.handleKeyDown}
@focus=${this.handleFocus}
placeholder=${this.placeholder}
maxlength=${this.maxlength}
minlength=${this.minlength}
disabled=${this.disabled}
readonly=${this.readOnly}
autofocus=${this.autofocus}
:value=${this.value}
/>
${this.clearable&&this.value?this.renderClear():""}
<slot name="append"></slot>
<div class=${e} ref="suggestion" #animation=${d}>
<div ref="scroller" class="scroller">
<ul class="list" @click=${this.handleClickItem} ref="list">
${this.#t.map((t,i)=>s`<li
focus=${this.#e===i?!0:null}
index=${i}
>
${t.value}
</li>`)}
</ul>
</div>
</div>
</div>
`}handleInput(e){let{lazy:t}=this;this.value=e.currentTarget.value,!(t&&Date.now()-this.#s<t)&&(this.#s=Date.now(),this.emitFetchSuggest())}handleClickItem(e){let t=e.target.getAttribute("index");this.#e=t,this.emitSelect()}clear(){this.$refs.input.value="",this.value="",this.$emit("change"),this.$emit("input")}handleChange(){this.$emit("change")}handleKeyDown(e){let{lazy:t,minlength:i,value:o}=this;if(e.keyCode===13)return e.preventDefault(),this.#e>-1&&this.#i?this.emitSelect():t&&Date.now()-this.#s<t||(this.#s=Date.now(),i&&o.length<i)?void 0:this.$emit("submit");if(e.keyCode===38||e.keyCode===40){e.preventDefault();let n=e.keyCode===38?-1:1;this.#e+=n,this.#e<0&&(this.#e=0),this.#e>this.#t.length-1&&(this.#e=this.#t.length-1),this.$requestUpdate()}}emitSelect(){let e=this.#t[this.#e];this.value=e.value,this.$refs.suggestion.$animate(!0),this.#i=!1,this.$requestUpdate(),this.$emit("change"),this.$emit("input"),this.$emit("select",{index:this.#e,value:e})}emitFetchSuggest(){this.$emit("fetch-suggest",{value:this.value,send:e=>{this.#t=e.slice(0,10),this.#e=-1,this.$requestUpdate()}})}handleFocus(){this.#i||(this.#i=!0,this.$refs.suggestion.$animate())}mounted(){this.autofocus&&r(e=>this.$refs.input.focus()),c(this,()=>{this.#i=!1,this.$refs.suggestion.$animate(!0)})}}u.reg("input");

View File

@ -0,0 +1,5 @@
import{css as r,html as s,Component as l,bind as i,nextTick as n}from"wkit";class c extends l{static props={type:"primary",to:"",autofocus:!1,disabled:!1,lazy:0};static styles=[r`:host{position:relative;display:inline-flex;border-radius:2px;user-select:none;-moz-user-select:none;font-size:inherit;cursor:pointer;transition:box-shadow .15s linear}:host .link{display:flex;justify-content:center;align-items:center;width:100%;padding:var(--padding, 0 2px);line-height:1;font-size:inherit;font-family:inherit;outline:none;color:inherit;cursor:inherit;text-decoration:none;transition:color .15s linear}:host .link::-moz-focus-inner{border:none}:host::after{position:absolute;bottom:-2px;left:0;width:100%;height:1px;border-bottom:1px dashed rgba(0,0,0,0);content:"";opacity:0;transition:opacity .15s linear}`,r`:host([type=primary]){color:var(--color-teal-2)}:host([type=primary])::after{border-color:var(--color-teal-1)}:host([type=primary]:not([disabled]):hover){color:var(--color-teal-1)}:host([type=primary]:not([disabled]):active){color:var(--color-teal-3)}:host([type=primary]:not([disabled]):focus-within){box-shadow:0 0 0 2px var(--color-teal-a)}:host([type=info]){color:var(--color-blue-2)}:host([type=info])::after{border-color:var(--color-blue-1)}:host([type=info]:not([disabled]):hover){color:var(--color-blue-1)}:host([type=info]:not([disabled]):active){color:var(--color-blue-3)}:host([type=info]:not([disabled]):focus-within){box-shadow:0 0 0 2px var(--color-blue-a)}:host([type=success]){color:var(--color-green-2)}:host([type=success])::after{border-color:var(--color-green-1)}:host([type=success]:not([disabled]):hover){color:var(--color-green-1)}:host([type=success]:not([disabled]):active){color:var(--color-green-3)}:host([type=success]:not([disabled]):focus-within){box-shadow:0 0 0 2px var(--color-green-a)}:host([type=warning]){color:var(--color-orange-2)}:host([type=warning])::after{border-color:var(--color-orange-1)}:host([type=warning]:not([disabled]):hover){color:var(--color-orange-1)}:host([type=warning]:not([disabled]):active){color:var(--color-orange-3)}:host([type=warning]:not([disabled]):focus-within){box-shadow:0 0 0 2px var(--color-orange-a)}:host([type=danger]){color:var(--color-red-2)}:host([type=danger])::after{border-color:var(--color-red-1)}:host([type=danger]:not([disabled]):hover){color:var(--color-red-1)}:host([type=danger]:not([disabled]):active){color:var(--color-red-3)}:host([type=danger]:not([disabled]):focus-within){box-shadow:0 0 0 2px var(--color-red-a)}:host([type=secondary]){color:var(--color-dark-2)}:host([type=secondary])::after{border-color:var(--color-dark-1)}:host([type=secondary]:not([disabled]):hover){color:var(--color-dark-1)}:host([type=secondary]:not([disabled]):active){color:var(--color-dark-3)}:host([type=secondary]:not([disabled]):focus-within){box-shadow:0 0 0 2px var(--color-dark-a)}:host([type=help]){color:var(--color-grey-2)}:host([type=help])::after{border-color:var(--color-grey-1)}:host([type=help]:not([disabled]):hover){color:var(--color-grey-1)}:host([type=help]:not([disabled]):active){color:var(--color-grey-3)}:host([type=help]:not([disabled]):focus-within){box-shadow:0 0 0 2px var(--color-grey-a)}`,r`:host(:not([disabled]):hover)::after,:host([underline])::after{opacity:1}:host([disabled]){cursor:not-allowed;opacity:.6}`];mounted(){this.stamp=0,this.autofocus&&n(o=>this.$refs.a.focus()),this._clickFn=i(this.$refs.a,"click",o=>{let{disabled:a,lazy:e}=this,t=Date.now();if(a){o.preventDefault(),o.stopPropagation();return}if(e>0&&t-this.stamp<e){o.preventDefault(),o.stopPropagation();return}this.stamp=t},!0)}render(){return s`
<a tabindex="0" ref="a" class="link" href=${this.to||"javascript:;"}>
<slot />
</a>
`}}c.reg("link");

View File

@ -0,0 +1,13 @@
import{css as e,html as i,Component as s,classMap as a}from"wkit";class n extends s{static props={value:{type:Boolean,default:!1},inactiveText:"",activeText:"",inlineText:!1,disabled:!1,readonly:!1};static styles=[e`:host{display:inline-flex;align-items:center;font-size:14px;cursor:pointer}:host label{display:flex;justify-content:center;align-items:center;min-width:32px;padding:0 8px 0 2px;line-height:1;-moz-user-select:none;user-select:none;white-space:nowrap;cursor:inherit;outline:none;color:var(--color-dark-1)}:host .dot{display:flex;align-items:center;justify-content:space-between;min-width:36px;height:18px;padding:0 4px;margin-right:5px;line-height:14px;border-radius:16px;background:var(--color-plain-3);transition:box-shadow .2s ease,background .2s ease}:host .dot::before{display:block;width:14px;height:14px;border-radius:50%;background:#fff;content:""}:host .dot::after{display:flex;padding:0 2px;font-size:12px;content:attr(st);color:#fff}:host .dot.open{flex-direction:row-reverse;background:var(--color-teal-1)}`,e`:host(:focus-within) .dot{box-shadow:0 0 0 2px var(--color-plain-a)}`,e`:host([size=m]){height:24px;font-size:12px}:host([size=m]) .dot{min-width:30px;height:16px;line-height:12px}:host([size=m]) .dot::before{width:12px;height:12px}:host([size=xl]){height:36px;font-size:14px}:host([size=xl]) .dot{min-width:35px;height:18px;line-height:14px}:host([size=xl]) .dot::before{width:14px;height:14px}:host([size=xxl]){height:44px;font-size:14px}:host([size=xxl]) .dot{min-width:35px;height:18px;line-height:14px}:host([size=xxl]) .dot::before{width:14px;height:14px}`,e`:host([type=primary]) .dot.open{background:var(--color-teal-1)}:host([type=primary]):host(:focus-within) .dot{box-shadow:0 0 0 2px var(--color-teal-a)}:host([type=info]) .dot.open{background:var(--color-blue-1)}:host([type=info]):host(:focus-within) .dot{box-shadow:0 0 0 2px var(--color-blue-a)}:host([type=success]) .dot.open{background:var(--color-green-1)}:host([type=success]):host(:focus-within) .dot{box-shadow:0 0 0 2px var(--color-green-a)}:host([type=warning]) .dot.open{background:var(--color-orange-1)}:host([type=warning]):host(:focus-within) .dot{box-shadow:0 0 0 2px var(--color-orange-a)}:host([type=danger]) .dot.open{background:var(--color-red-1)}:host([type=danger]):host(:focus-within) .dot{box-shadow:0 0 0 2px var(--color-red-a)}`,e`:host([readonly]),:host([disabled]){cursor:not-allowed;opacity:.6}:host([readonly]){cursor:default}`];toggleCheck(t){if(this.disabled||this.readOnly)return;t.stopPropagation(),this.value=!this.value;let o={value:this.value};this.$emit("input"),this.$emit("change",o)}handleClick(t){(t.type==="click"||t.keyCode===32)&&(t.preventDefault(),this.toggleCheck(t))}mounted(){}render(){let t=a({dot:!0,open:this.value});return i` <label
tabindex=${this.disabled||this.readOnly?"none":0}
@click=${this.handleClick}
@keydown=${this.handleClick}
>
<span
class=${t}
st=${this.inlineText?this.value?this.activeText:this.inactiveText:""}
></span>
<slot
>${this.inlineText?"":this.value?this.activeText:this.inactiveText}</slot
>
</label>`}}n.reg("switch");

View File

@ -0,0 +1,31 @@
import { css, svg, html, Component } from "wkit";
import SVG_DICT from "./svg.js";
let dict = SVG_DICT;
if (window.EXT_SVG_DICT) {
Object.assign(dict, EXT_SVG_DICT);
}
class Icon extends Component {
static props = {
name: {
type: String,
default: null,
observer(val) {
if (val === "") {
this.name = null;
}
}
}
};
static styles = css`:host{display:inline-flex;width:var(--wc-icon-size, 32px);height:var(--wc-icon-size, 32px)}:host(:not([name])){display:none}.icon{display:block;width:100%;height:100%;color:inherit;fill:currentColor}.icon.loading{animation:load 1.5s linear infinite}.icon circle{stroke:currentColor;animation:circle 1.5s ease-in-out infinite}:host([size=s]){width:20px;height:20px}:host([size=m]){width:24px;height:24px}:host([size=l]){width:32px;height:32px}:host([size=xl]){width:36px;height:36px}:host([size=xxl]){width:44px;height:44px}@keyframes circle{0%{stroke-dasharray:0,3812px;stroke-dashoffset:0}50%{stroke-dasharray:1906px,3812px;stroke-dashoffset:-287px}100%{stroke-dasharray:1906px,3812px;stroke-dashoffset:-2393px}}@keyframes load{to{transform:rotate(360deg)}}`;
render() {
return html`
<svg
class="icon ${this.name === "loading" ? "loading" : ""}"
viewBox="0 0 1024 1024"
>
${this.name === "loading" ? svg`<circle class="circle" cx="512" cy="512" r="384" fill="none" stroke-width="80" />` : svg`<path d="${dict[this.name]}" />`}
</svg>
`;
}
}
Icon.reg("icon");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
import{css as p,html as $,bind as a,unbind as x,Component as _}from"wkit";class z extends _{static props={axis:"xy",delay:1e3,distance:1};static styles=[p`:host{position:relative;display:block}:host .container{overflow:hidden;position:relative;width:100%;height:100%;max-height:inherit}:host .wrapper{overflow:auto;scrollbar-width:none;width:100%;height:100%;max-height:inherit;scrollbar-width:0}:host .wrapper::-webkit-scrollbar{display:none}:host .content{min-width:100%;width:fit-content;height:fit-content}`,p`.is-horizontal,.is-vertical{visibility:hidden;position:absolute;display:flex;justify-content:flex-end;opacity:0;user-select:none;transition:opacity .3s linear,visibility .3s linear}.is-horizontal .thumb,.is-vertical .thumb{display:block;border-radius:5px;background:rgba(44,47,53,.25);cursor:default;transition:width .1s linear,height .1s linear}.is-horizontal .thumb:hover,.is-vertical .thumb:hover{background:rgba(44,47,53,.5)}.is-horizontal{flex-direction:column;left:0;bottom:0;width:100%;height:10px}.is-horizontal .thumb{width:0;height:6px}.is-horizontal .thumb:hover{height:10px}.is-vertical{top:0;right:0;width:10px;height:100%}.is-vertical .thumb{width:6px;height:0}.is-vertical .thumb:hover{width:10px}`,p`:host(:hover) .is-horizontal,:host(:hover) .is-vertical{visibility:visible;opacity:1}:host([axis=x]) .wrapper{overflow-y:hidden}:host([axis=x]) .is-vertical{display:none}:host([axis=y]) .wrapper{overflow-x:hidden}:host([axis=y]) .is-horizontal{display:none}:host([disabled]) .wrapper{overflow:hidden}:host([disabled]) .is-vertical,:host([disabled]) .is-horizontal{display:none}`];stamp=0;cache={xBar:0,yBar:0,thumbX:0,thumbY:0};get scrollTop(){return this.$refs.box?.scrollTop||0}set scrollTop(t){t=+t,t===t&&(this.$refs.box.scrollTop=t)}get scrollLeft(){return this.$refs.box?.scrollLeft||0}set scrollLeft(t){t=+t,t===t&&(this.$refs.box.scrollLeft=t)}get scrollHeight(){return this.$refs.box?.scrollHeight}get scrollWidth(){return this.$refs.box?.scrollWidth}__init__(t){let e=this.offsetWidth,r=this.offsetHeight,s=this.scrollWidth,l=this.scrollHeight,h=50,i=50;h=r*(r/l)>>0,i=e*(e/s)>>0,h<50&&(h=50),i<50&&(i=50),i>=e&&(i=0),h>=r&&(h=0),this.cache.yBar=h,this.cache.xBar=i,i>0?(this.$refs.x.parentNode.style.display="flex",this.$refs.x.style.width=i+"px"):this.$refs.x.parentNode.style.display="none",h>0?(this.$refs.y.parentNode.style.display="flex",this.$refs.y.style.height=h+"px"):this.$refs.y.parentNode.style.display="none"}_fetchScrollX(t){let{xBar:e}=this.cache,{scrollWidth:r,offsetWidth:s}=this;return t<0?t=0:t>s-e&&(t=s-e),this.scrollLeft=(r-s)*(t/(s-e)),this.$refs.x.style.transform=`translateX(${t}px)`,t}_fetchScrollY(t){let{yBar:e}=this.cache,{scrollHeight:r,offsetHeight:s}=this;return t<0?t=0:t>s-e&&(t=s-e),this.scrollTop=(r-s)*(t/(s-e)),this.$refs.y.style.transform=`translateY(${t}px)`,t}_fireReachEnd(t="reach-bottom"){let e=this.delay,{scrollHeight:r,offsetHeight:s}=this,l=this.scrollTop,h=Date.now();if(h-this.stamp>e){if(t==="reach-bottom"){if(s+l<r)return}else if(l>0)return;this.stamp=h,this.$emit(t)}}mounted(){let t,e,r,s,l=i=>{let{thumbY:c,thumbX:n}=this.cache;t!==void 0&&(r=this._fetchScrollX(n+i.pageX-t)),e!==void 0&&(s=this._fetchScrollY(c+i.pageY-e))},h=i=>{Math.abs(i.pageY-e)>this.distance&&this._fireReachEnd(i.pageY>e?"reach-bottom":"reach-top"),t=void 0,e=void 0,this.cache.thumbX=r||0,this.cache.thumbY=s||0,delete this._active,x(document,"mousemove",l),x(document,"mouseup",h)};a(this.$refs.box,"scroll",i=>{if(i.stopPropagation(),this._active)return;let{xBar:c,yBar:n,thumbX:m,thumbY:f}=this.cache,{axis:d,scrollHeight:y,scrollWidth:g,offsetHeight:u,offsetWidth:b,scrollTop:v,scrollLeft:w}=this;if(!(c===0&&n===0)){if((d==="y"||d==="xy")&&n){let o=~~(v/(y-u)*(u-n));o!==f&&(this.cache.thumbY=o,this.$refs.y.style.transform=`translateY(${o}px)`,Math.abs(o-f)>this.distance&&this._fireReachEnd(o>f?"reach-bottom":"reach-top"))}if((d==="x"||d==="xy")&&c){let o=~~(w/(g-b)*(b-c));o!==m&&(this.cache.thumbX=o,this.$refs.x.style.transform=`translateX(${o}px)`)}this.$emit("scroll")}}),a(this.$refs.y,"mousedown",i=>{e=i.pageY,this._active=!0,a(document,"mousemove",l),a(document,"mouseup",h)}),a(this.$refs.x,"mousedown",i=>{t=i.pageX,this._active=!0,a(document,"mousemove",l),a(document,"mouseup",h)}),this.__observer=new ResizeObserver(this.__init__.bind(this)),this.__observer.observe(this.$refs.cont)}unmounted(){this.__observer?.disconnect()}render(){return $`
<div class="container">
<div class="wrapper" ref="box">
<div class="content" ref="cont"><slot></slot></div>
</div>
</div>
<div class="is-horizontal">
<span ref="x" class="thumb"></span>
</div>
<div class="is-vertical">
<span ref="y" class="thumb"></span>
</div>
`}}z.reg("scroll");

View File

@ -0,0 +1,8 @@
import { css, html, Component } from "wkit";
class Space extends Component {
static styles = css`:host{display:block}.container{display:flex;flex-wrap:wrap;align-items:center;width:100%;padding:6px 0;gap:12px}:host([vertical]) .container{flex-direction:column}:host([justify]) .container{justify-content:space-between}:host([gap=s]) .container{padding:2px 0;gap:4px}:host([gap=m]) .container{padding:4px 0;gap:8px}:host([gap=l]) .container{padding:6px 0;gap:12px}:host([gap=xl]) .container{padding:8px 0;gap:16px}:host([gap=xxl]) .container{padding:10px 0;gap:20px}:host([gap=xxxl]) .container{padding:12px 0;gap:24px}`;
render() {
return html`<div class="container"><slot /></div>`;
}
}
Space.reg("space");

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,54 @@
var J=Object.defineProperty;var V=(i,e,t)=>e in i?J(i,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[e]=t;var d=(i,e,t)=>(V(i,typeof e!="symbol"?e+"":e,t),t),q=(i,e,t)=>{if(!e.has(i))throw TypeError("Cannot "+t)};var b=(i,e,t)=>(q(i,e,"read from private field"),t?t.call(i):e.get(i)),m=(i,e,t)=>{if(e.has(i))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(i):e.set(i,t)},T=(i,e,t,o)=>(q(i,e,"write to private field"),o?o.call(i,t):e.set(i,t),t);var C=(i,e,t)=>(q(i,e,"access private method"),t);import"wkit";var c=Symbol("router"),h=Symbol("router-view"),y=Symbol("store"),x=new Set;var P=class extends WeakMap{broadcast(){for(let e of x)e.$requestUpdate()}assign(e){x.add(e)}deassign(e){x.add(e)}},Y=new P;Object.defineProperty(window,"wkitd",{get(){return Y},set(i){console.error("Can not set readonly property wkitd of window")},enumerable:!1});import{html as Z,css as ne,Component as F}from"wkit";var B=encodeURIComponent,W=decodeURIComponent;function w(){}function H(i,e,t){Object.defineProperty(i,e,{get(){return t},set(o){},enumerable:!1})}function U(i,e,t){var o;if(Array.isArray(e))e.forEach(function(r,s){o=i?`${i}[${Array.isArray(r)?s:""}]`:s,typeof r=="object"?U(o,r,t):t(o,r)});else for(let r in e)o=i?`${i}[${r}]`:r,typeof e[r]=="object"?U(o,e[r],t):t(o,e[r])}function M(i=""){let e=new URLSearchParams(i),t=Object.create(null);for(let[o,r]of e.entries()){let s=W(o),n=W(r),a=0;if(/(\w+)\[(\w*?)\]/.test(s)){let u=RegExp.$1,l=RegExp.$2;s=u,!l||+l==+l?(n=[n],a|=2):(a|=1,n={[l]:n})}t[s]?a&2?t[s]=t[s].concat(n):a&1?Object.assign(t[s],n):(Array.isArray(t[n])||(t[s]=[t[s]]),t[s].push(n)):t[s]=n}return t}function v(i={}){if(i===null)return"";if(typeof i=="string"||typeof i=="number"||typeof i=="boolean")return i;let e=[];return typeof i=="object"&&U("",i,function(o,r){e.push(o+"="+B(r))}),e.join("&")}import{Component as ie}from"wkit";import{bind as L}from"wkit";var Q=/^(#!|#)[\/]+?/,ee=/(\/[^/]*)(:[A-Za-z0-9_]+)(\?)?/g,k="hash",te="history",R=class{type=k;#e=new Map;#s=new Set;#n=new Map;#t=!1;#r=Object.create(null);#o;constructor(e=k){this.type=e,L(window,"popstate",this.#i.bind(this))}get route(){return this.#r}get views(){return Array.from(this.#s)}#i(e){this.#t&&this.#p()}#h(e){if(e.path==="!")e.regexp=null;else{let t=[],o;if(e.path.includes("?")&&e.path.at(-1)!=="?")throw new SyntaxError(`The exp "?" can only be used in the last.
${JSON.stringify(e)}
`);o=e.path.replace(ee,function(r,s,n,a){return t.push(n.slice(1)),s==="/"&&(s="/?"),s+"([A-Za-z0-9_]+)"+a}),o="^"+o+"$",e.regexp=new RegExp(o),e.vars=t}return e}#a(e){if(e.path!=="!"&&e.path[0]!=="/"){console.error('route path must start with "/"');return}e.path=e.path.replace(/^[\/]+|[\/]+$|\s+/g,"/"),this.#e.set(e.path,this.#h(e)),this.#s.add(e.name)}#p(){let e=this.type===k,t=window.wkitd.get(h),o=location.hash||"#/",r=e?o:location.href.replace(location.origin,"").replace(o,""),s;if(r.includes("?")&&([r,s]=r.split("?")),r=r.replace(Q,"/"),!(!t||r===this.#r.path)){for(let[n,a]of this.#e){let u=r.match(a.regexp);if(u){let l=Object.create(null);for(let E=1;E<u.length;E++)l[[a.vars[E-1]]]=u[E];let A={path:r,name:a.name,params:l,query:M(s)};return this.#o?this.#o(this.route,A,()=>{this.#l(A)}):this.#l(A)}}if(this.#e.get("!")){let n=this.#e.get("!");t.current=n.name,this.#r={path:r,name:n.name,params:{},query:{}}}}}#l(e){let t=window.wkitd.get(h);t.current=e.name,this.#r=e,this.#c()}#c(){for(let[e,t]of this.#n)t.call(e,this.route)}init(){this.#t=!0,this.#i()}rsync(e,t){this.#n.set(e,t),this.#t&&this.#c()}beforeEach(e=w){this.#o=e}addRoute(e){Array.isArray(e)?e.forEach(t=>{this.#a(t)}):this.#a(e),this.#t&&this.#i()}go(e=0){history.go(e)}back(){this.go(-1)}forward(){this.go(1)}push(e={path:"",query:{}},t=!1){let o="",r="";typeof e=="string"?o=e.trim():(r=v(e.query||""),o=e.path+(r?`?${r}`:"")),!(!o&&o===location.hash.slice(1))&&(this.type===k?t?location.replace(o.replace(/^\//,"#/")):location.hash=o:(t?window.history.replaceState({path:o},null,o+r):window.history.pushState({path:o},null,o+r),this.#p()))}replace(e={path:"",query:{}}){this.push(e,!0)}};function j(){return()=>new R}function re(){return()=>new R(te)}import{Component as D,html as oe,css as N,raw as I}from"wkit";var $,_=class extends D{constructor(){super(...arguments);m(this,$,[])}created(){window.wkitd.set(h,this)}sync(t){T(this,$,t)}render(){let t={immediate:!0,custom:[{transform:"translateX(-32px)",opacity:0},{transform:"translateX(0)",opacity:1}]};if(this.keepAlive){let o=b(this,$).map(r=>[this.transition?`<${r} ref="${r}" :__keep_alive__="%s" #animation="%s" style="%s"></${r}>`:`<${r} ref="${r}" :__keep_alive__="%s" style=%s></${r}>`,[this.current===r,{...t,immediate:this.current===r},this.current===r?"":"display:none"]]);return I(o.map(r=>r[0]).join(""),o.map(r=>r[1]).flat())}else if(this.current)return this.transition?I(`<${this.current} #animation="%s"></${this.current}>`,[t]):I(`<${this.current}></${this.current}>`)}};$=new WeakMap,d(_,"props",{keepAlive:!1,transition:!1,current:{type:String,default:"",attribute:!1,observer(t,o){this.keepAlive&&t&&(o&&this.$refs[o]&&this.$refs[o].deactivated(),this.$refs[t]?.$requestUpdate(),this.$refs[t]?.$animate(),this.$refs[t]?.activated())}}}),d(_,"styles",N`
:host {
display: block;
}
`);var f,O,X,S,z,g=class extends D{constructor(){super(...arguments);m(this,O);m(this,S);m(this,f,"")}mounted(){this.$router.rsync(this,t=>{this.classList.toggle("active",t.path===this.to.path)})}render(){return T(this,f,C(this,S,z).call(this)),oe`<a title=${b(this,f)} @click=${C(this,O,X)}>
<slot></slot
></a>`}};f=new WeakMap,O=new WeakSet,X=function(){let t=this.$router.type,{path:o}=this.to;this.disabled||(t==="hash"?location.hash=b(this,f):this.$router.push(this.to))},S=new WeakSet,z=function(){let t=this.$router.type,{path:o="",query:r={}}=this.to,s=typeof r=="string"?r.replaceAll("?",""):v(r);return o=o.replace(/^\//,""),s&&(o+="?"+s),"/"+o},d(g,"props",{to:Object,disabled:!1}),d(g,"styles",N`
:host {
display: inline-flex;
align-items: center;
-webkit-user-select: none;
user-select: none;
}
a {
display: flex;
align-items: center;
width: 100%;
height: 100%;
color: inherit;
text-decoration: inherit;
cursor: pointer;
}
:host([disabled]) a {
opacity: 0.6;
cursor: not-allowed;
}
`);customElements.get("router-view")||customElements.define("router-view",_);customElements.get("router-link")||customElements.define("router-link",g);function Re({history:i=j(),routes:e=[]}={}){let t=i();window.wkitd.set(c,t),t.addRoute(e);function o(){Object.defineProperty(ie.prototype,"$router",{get(){return window.wkitd.get(c)},set(r){console.error("Can not set readonly property $router of Component")},enumerable:!1})}return o.beforeEach=t.beforeEach.bind(t),o}import{Component as K}from"wkit";function G(i){return i===null?i:new Proxy(i,{get(e,t,o){let r=Reflect.get(e,t,o);return typeof r=="object"?G(r):r},set(e,t,o,r){return Reflect.set(e,t,o,r),window.wkitd.broadcast(),!0}})}function se(i={}){let e=!1;return function(){Object.defineProperty(K.prototype,"$store",{get(){return window.wkitd.get(y)},set(t){if(e)return console.error("Can not set readonly property $store of Component");window.wkitd.set(y,G(t)),e=!0},enumerable:!1}),K.prototype.$store=i}}var p=class extends F{};function Ie({data:i={},styles:e=[],methods:t={},mounted:o=w,render:r}={}){return new function(){p.props=i,p.styles=e,Object.assign(p.prototype,t,{mounted:o,created(){H(F.prototype,"$app",this)}}),this.use=function(s=w,...n){return s.apply(p.prototype,n),this},this.mount=function(){let s=window.wkitd.get(c);r?p.prototype.render=r:s?p.prototype.render=function(){return Z`<router-view></router-view>`}:(p.styles=ne`
:host {
font-family: monospace;
color: #647889;
}
.code {
margin: 16px 0;
background: #f7f8fb;
}
`,p.prototype.render=function(){return Z`
<h1>It works!!!</h1>
<cite>
If you don't use router, you may define the
<b>render</b> property.
</cite>
<div class="code">
<pre><code> createApp({</code></pre>
<pre><code> render() {</code></pre>
<pre><code> return html\`&lt;wc-home&gt;&lt;/wc-home&gt;\`</code></pre>
<pre><code> }</code></pre>
<pre><code> })</code></pre>
<pre><code> .mount()</code></pre>
</div>
`}),s&&(p.prototype.mounted=function(...n){let a=window.wkitd.get(h);if(a)a.sync(s.views),s.init(),o.call(this,...n);else throw new Error('<router-view /> not found, "Router" works Unexpected.')}),p.reg("app")}}}function We(){return window.wkitd.get(y)}function He(){return window.wkitd.get(c)}function Me(){return window.wkitd.get(c)?.route}export{Ie as createApp,Re as createRouter,se as createStore,j as createWebHashHistory,re as createWebHistory,Me as getCurrentPage,He as getRouter,We as getStore};

View File

@ -0,0 +1,15 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2023/09/04 11:11:51
*/
import { createStore } from 'wkitd'
export default createStore({
HOST_DATA: {},
activeDomain: localStorage.getItem('last_domain') || '', //当前选中的域名
domains: [],
records: [],
tmp_records: []
})

View File

@ -0,0 +1,100 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2023/09/04 19:18:28
*/
import { nextTick } from 'wkit'
const APP_CONFIG_DIR = `${native.env.CONFIG_DIR}/hosts-switch`
const HOST_FILE = `${APP_CONFIG_DIR}/host.cache`
const LOCK_FILE = `${APP_CONFIG_DIR}/lock`
export function noop() {}
export function checkPermission() {
return native.fs.access('/etc/hosts', 'a+')
}
export async function getHistory() {
if (await native.fs.isfile(LOCK_FILE)) {
let cache = await native.fs.read(HOST_FILE)
return JSON.parse(cache)
}
let cache = await native.fs.read('/etc/hosts')
let records = cache.split(/[\n\r]+/)
let list = []
let dict = {}
records.forEach(str => {
str = str.trim()
let matches = str.match(/^(#*?)\s*(\d+\.\d+\.\d+\.\d+)\s+(.*)/)
if (matches) {
let names = matches[3].split(/\s+/).map(it => it.trim())
let name
while ((name = names.pop())) {
list.push({ ip: matches[2], enabled: !matches[1], name })
}
}
})
records = null
list.forEach(it => {
it.name = it.name.split('.')
let domain = it.name.splice(-2, 2).join('.')
if (domain === 'com.cn' || domain === 'org.cn' || domain === 'net.cn') {
domain = it.name.pop() + '.' + domain
}
if (dict[domain]) {
dict[domain].push({
value: it.ip,
enabled: it.enabled,
record: it.name.join('.') || '@',
remark: ''
})
} else {
dict[domain] = [
{
value: it.ip,
enabled: it.enabled,
record: it.name.join('.') || '@',
remark: ''
}
]
}
})
list = null
try {
await native.fs.write(HOST_FILE, JSON.stringify(dict))
await native.fs.write(LOCK_FILE, '')
} catch (err) {}
return dict
}
export function saveHosts(dict) {
nextTick(async () => {
var txt = ''
for (let k in dict) {
for (let it of dict[k]) {
if (it.enabled) {
var name = it.record === '@' ? '' : it.record
if (name) {
name += '.'
}
txt += `${it.value.padEnd(15, ' ')} ${name + k}\n`
}
}
txt += '\n'
}
try {
await native.fs.write(HOST_FILE, JSON.stringify(dict))
await native.fs.write('/etc/hosts', txt)
} catch (err) {
layer.alert(err)
}
})
}