webc.site 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/importmap/genDoc.md +9 -0
- package/importmap/x/On.js +13 -0
- package/importmap/x/On.md +8 -0
- package/importmap/x/a.js +23 -0
- package/importmap/x/a.md +3 -0
- package/importmap/x/delayRoute.js +17 -0
- package/importmap/x/delayRoute.md +11 -0
- package/importmap/x/dom.js +3 -0
- package/importmap/x/dom.md +7 -0
- package/importmap/x/package.json +22 -0
- package/importmap/x/rmWait.js +2 -0
- package/importmap/x/rmWait.md +3 -0
- package/importmap/x/route.js +59 -0
- package/importmap/x/route.md +37 -0
- package/importmap/x/selfA.js +13 -0
- package/importmap/x/selfA.md +6 -0
- package/package.json +1 -1
- package/x.js +1 -1
- package/x.js.map +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 人工智能驱动的界面开发流程笔记
|
|
2
|
+
|
|
3
|
+
## 纯 web 组件,支持无样式
|
|
4
|
+
|
|
5
|
+
纯 web 组件,可适配全部前端框架。
|
|
6
|
+
|
|
7
|
+
组件样式和组件逻辑完全拆分。
|
|
8
|
+
|
|
9
|
+
可只引用组件的逻辑,然后自己写样式。
|
|
10
|
+
|
|
11
|
+
同时支持无打包直接引用的使用方式,及配合 vite 等构建器打包的构建。
|
|
12
|
+
|
|
13
|
+
## 初衷
|
|
14
|
+
|
|
15
|
+
人工智能开发前端,比较大的问题是调试。
|
|
16
|
+
|
|
17
|
+
虽然[谷歌反重力](https://antigravity.google) 之类的开发工具,有打开浏览器调试能力,但因为交互流程比较深、需要登录才能访问、需要后台准备数据等等,人工智能全自动浏览器调试流程经常被阻塞,无法继续。
|
|
18
|
+
|
|
19
|
+
为了加速开发,我建议的方案是:
|
|
20
|
+
|
|
21
|
+
不在组件中直接调用后端接口读取数据,而是用异步回调的函数的方式,暴露给上层。
|
|
22
|
+
|
|
23
|
+
这样,就可以在 `Demo.svelte` 用假数据来展示组件的不同状态.
|
|
24
|
+
|
|
25
|
+
不再对后端数据状态有依赖。不需要登录,不需要在调试的之前,去后端准备数据。
|
|
26
|
+
|
|
27
|
+
也方便调整样式之后,查看在不同状态下,组件是否呈现有问题。
|
|
28
|
+
|
|
29
|
+
具体方案,可以参考我的演示前端库 [webc-zh](https://github.com/webc-zh/webc-zh.github.io)。
|
|
30
|
+
|
|
31
|
+
组件库在线浏览 [GitHub Page](https://webc-zh.github.io)
|
|
32
|
+
|
|
33
|
+
这里我把前端组件,都拆分为独立的组件文件夹,每个文件夹包含了所有需要的资源(比如 svg 等)。
|
|
34
|
+
|
|
35
|
+
如此做的好处是,可以类似[shadcn](https://ui.shadcn.com) 一样,实现按需添加。
|
|
36
|
+
|
|
37
|
+
比如, `./comDev.sh com/scroll`,就可以对滚动条进行单独的开发,调试。
|
|
38
|
+
|
|
39
|
+
调试默认会打开文件下的 `Demo.svelte` 作为调试入口。
|
|
40
|
+
|
|
41
|
+
完整的开发提示词,可以参考 [.agents/skills/com/SKILL.md](.agents/skills/com/SKILL.md)
|
|
42
|
+
|
|
43
|
+
谷歌反重力中 `/com ` 即可使用。
|
|
44
|
+
|
|
45
|
+

|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const On = (elem, dict) => {
|
|
2
|
+
let event, func;
|
|
3
|
+
for (event in dict) {
|
|
4
|
+
func = dict[event];
|
|
5
|
+
elem.addEventListener(event, func);
|
|
6
|
+
}
|
|
7
|
+
return () => {
|
|
8
|
+
for (event in dict) {
|
|
9
|
+
func = dict[event];
|
|
10
|
+
elem.removeEventListener(event, func);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
};
|
package/importmap/x/a.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { goto, selfA, B } from "../../x.js";
|
|
2
|
+
B.addEventListener("click", (e) => {
|
|
3
|
+
var href, name, p;
|
|
4
|
+
p = e.target;
|
|
5
|
+
while (p) {
|
|
6
|
+
({ nodeName: name } = p);
|
|
7
|
+
if (name === "A") {
|
|
8
|
+
({ href } = p);
|
|
9
|
+
if (href) {
|
|
10
|
+
href = selfA(p, e);
|
|
11
|
+
if (href !== void 0) {
|
|
12
|
+
goto(href);
|
|
13
|
+
} else if (!p.target) {
|
|
14
|
+
p.target = "_blank";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
break;
|
|
18
|
+
} else if (name === "BODY") {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
p = p.parentNode;
|
|
22
|
+
}
|
|
23
|
+
});
|
package/importmap/x/a.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { route } from "./route.js";
|
|
2
|
+
|
|
3
|
+
// 避免 onMount 之前,route 被触发,导致重复加载数据
|
|
4
|
+
export const delayRoute = (loadUrl) => {
|
|
5
|
+
let t;
|
|
6
|
+
const unbind = route((url, preUrl) => {
|
|
7
|
+
t = setTimeout(() => {
|
|
8
|
+
loadUrl(url, preUrl);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
return () => {
|
|
12
|
+
unbind();
|
|
13
|
+
clearTimeout(t);
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default delayRoute;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@3-/x",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"homepage": "https://webc-zh.github.io",
|
|
7
|
+
"license": "MulanPSL-2.0",
|
|
8
|
+
"author": "i18n.site@gmail.com",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/webc-zh/webc-zh.github.io.git"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"./*"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
"./*": "./*"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {},
|
|
21
|
+
"devDependencies": {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { On } from "../../x.js";
|
|
2
|
+
export const nowUrl = () => location.pathname.slice(1),
|
|
3
|
+
[route, setPre, preUrl, refresh, removeSlash, split, setUrl, goto] = (() => {
|
|
4
|
+
let PRE = nowUrl(),
|
|
5
|
+
HOOK = [];
|
|
6
|
+
if (location.hash) PRE += location.hash;
|
|
7
|
+
|
|
8
|
+
const HASH = "#",
|
|
9
|
+
route = (hook) => {
|
|
10
|
+
HOOK.push(hook);
|
|
11
|
+
hook(nowUrl());
|
|
12
|
+
return () => {
|
|
13
|
+
HOOK = HOOK.filter((f) => f !== hook);
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
setPre = (url) => {
|
|
17
|
+
PRE = url;
|
|
18
|
+
},
|
|
19
|
+
preUrl = () => PRE,
|
|
20
|
+
refresh = (url) => {
|
|
21
|
+
url = url || nowUrl();
|
|
22
|
+
for (const f of HOOK) f(url, PRE);
|
|
23
|
+
setPre(url);
|
|
24
|
+
},
|
|
25
|
+
removeSlash = (url) => (url[0] === "/" ? url.slice(1) : url),
|
|
26
|
+
split = (str, s) => {
|
|
27
|
+
const p = str.indexOf(s);
|
|
28
|
+
return p >= 0 ? [str.slice(0, p), str.slice(p + 1)] : [str, ""];
|
|
29
|
+
},
|
|
30
|
+
setUrl = (url) => {
|
|
31
|
+
url = removeSlash(url);
|
|
32
|
+
if (url !== PRE) {
|
|
33
|
+
const [path, hash] = split(url, HASH),
|
|
34
|
+
[p] = split(PRE, HASH);
|
|
35
|
+
setPre(url);
|
|
36
|
+
if (path !== p) {
|
|
37
|
+
history.pushState(null, "", "/" + url);
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
if (location.hash.slice(1) !== hash) {
|
|
41
|
+
location.hash = hash;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
46
|
+
},
|
|
47
|
+
goto = (url) => {
|
|
48
|
+
if (setUrl(url)) refresh(url);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
On(window, {
|
|
52
|
+
popstate: () => {
|
|
53
|
+
const url = nowUrl();
|
|
54
|
+
if (url !== split(PRE, HASH)[0]) refresh(url);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return [route, setPre, preUrl, refresh, removeSlash, split, setUrl, goto];
|
|
59
|
+
})();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 路由管理与跳转
|
|
2
|
+
|
|
3
|
+
- `nowUrl()`
|
|
4
|
+
- 返回值:当前 URL 路径(不含首部斜杠)。
|
|
5
|
+
|
|
6
|
+
- `route(hook)`
|
|
7
|
+
- `hook`: 路由变化时执行的回调函数。格式:
|
|
8
|
+
- 参数:
|
|
9
|
+
- `url`: 字符串,新 URL 路径。
|
|
10
|
+
- `preUrl`: 字符串(可选),旧 URL 路径。
|
|
11
|
+
- 返回值:无。
|
|
12
|
+
- 返回值:取消订阅的函数,格式为 `() => void`。
|
|
13
|
+
|
|
14
|
+
- `setPre(url)`
|
|
15
|
+
- `url`: 字符串,新的前一次 URL 路径。
|
|
16
|
+
|
|
17
|
+
- `preUrl()`
|
|
18
|
+
- 返回值:前一次的 URL 路径。
|
|
19
|
+
|
|
20
|
+
- `refresh(url)`
|
|
21
|
+
- `url`: 字符串(可选),要触发回调的 URL 路径,默认是当前 URL。
|
|
22
|
+
|
|
23
|
+
- `removeSlash(url)`
|
|
24
|
+
- `url`: 字符串。
|
|
25
|
+
- 返回值:移除首部斜杠后的字符串。
|
|
26
|
+
|
|
27
|
+
- `split(str, s)`
|
|
28
|
+
- `str`: 待拆分字符串。
|
|
29
|
+
- `s`: 分隔符。
|
|
30
|
+
- 返回值:包含两个元素的数组 `[前部, 后部]`。
|
|
31
|
+
|
|
32
|
+
- `setUrl(url)`
|
|
33
|
+
- `url`: 目标 URL 路径。
|
|
34
|
+
- 返回值:若路径改变返回 `1`,若仅 hash 改变不返回值。
|
|
35
|
+
|
|
36
|
+
- `goto(url)`
|
|
37
|
+
- `url`: 目标 URL 路径。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// 判断A标签的href是否为当前网站的, 如果是, 返回url, 以实现不刷新跳转
|
|
2
|
+
export const selfA = (p, e) => {
|
|
3
|
+
var hash, url;
|
|
4
|
+
if (p.host === location.host) {
|
|
5
|
+
({ hash } = p);
|
|
6
|
+
url = p.pathname.slice(1) + p.search;
|
|
7
|
+
if (hash) {
|
|
8
|
+
url += hash;
|
|
9
|
+
}
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
return url;
|
|
12
|
+
}
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"webc.site","version":"0.1.
|
|
1
|
+
{"name":"webc.site","version":"0.1.19","description":"","keywords":[],"homepage":"https://webc-zh.github.io","license":"MulanPSL-2.0","author":"i18n.site@gmail.com","repository":{"type":"git","url":"git+https://github.com/webc-zh/webc-zh.github.io.git"},"files":["./*"],"type":"module","exports":{"./*":"./*"},"scripts":{},"devDependencies":{}}
|
package/x.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=(e,t)=>{let n,r;for(n in t)r=t[n],e.addEventListener(n,r);return()=>{for(n in t)r=t[n],e.removeEventListener(n,r)}},t=
|
|
1
|
+
const e=(e,t)=>{let n,r;for(n in t)r=t[n],e.addEventListener(n,r);return()=>{for(n in t)r=t[n],e.removeEventListener(n,r)}},t=()=>location.pathname.slice(1),[n,r,i,a,o,s,c,l]=(()=>{let n=t(),r=[];location.hash&&(n+=location.hash);let i=e=>(r.push(e),e(t()),()=>{r=r.filter(t=>t!==e)}),a=e=>{n=e},o=()=>n,s=e=>{e||=t();for(let t of r)t(e,n);a(e)},c=e=>e[0]===`/`?e.slice(1):e,l=(e,t)=>{let n=e.indexOf(t);return n>=0?[e.slice(0,n),e.slice(n+1)]:[e,``]},u=e=>{if(e=c(e),e!==n){let[t,r]=l(e,`#`),[i]=l(n,`#`);if(a(e),t!==i)return history.pushState(null,``,`/`+e),1;if(location.hash.slice(1)!==r){location.hash=r;return}}window.dispatchEvent(new HashChangeEvent(`hashchange`))};return e(window,{popstate:()=>{let e=t();e!==l(n,`#`)[0]&&s(e)}}),[i,a,o,s,c,l,u,e=>{u(e)&&s(e)}]})(),u=(e,t)=>{var n,r;if(e.host===location.host)return{hash:n}=e,r=e.pathname.slice(1)+e.search,n&&(r+=n),t.preventDefault(),r},d=document,f=d.body,p=d.createElement.bind(d);export{f as B,d as D,e as On,l as goto,p as newEl,t as nowUrl,i as preUrl,a as refresh,o as removeSlash,n as route,u as selfA,r as setPre,c as setUrl,s as split};
|
package/x.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x.js","names":[],"sources":["../../importmap/x/On.js","../../importmap/x/dom.js"],"sourcesContent":["export const On = (elem, dict) => {\n let event, func;\n for (event in dict) {\n func = dict[event];\n elem.addEventListener(event, func);\n }\n return () => {\n for (event in dict) {\n func = dict[event];\n elem.removeEventListener(event, func);\n }\n };\n};\n","export const D = document,\n B = D.body,\n newEl = D.createElement.bind(D);\n"],"mappings":"AAAA,MAAa,GAAM,EAAM,IAAS,CAChC,IAAI,EAAO,EACX,IAAK,KAAS,EACZ,EAAO,EAAK,GACZ,EAAK,iBAAiB,EAAO,CAAI,EAEnC,UAAa,CACX,IAAK,KAAS,EACZ,EAAO,EAAK,GACZ,EAAK,oBAAoB,EAAO,CAAI,CAExC,CACF,ECZa,EAAI,SACf,EAAI,EAAE,KACN,EAAQ,EAAE,cAAc,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"x.js","names":[],"sources":["../../importmap/x/On.js","../../importmap/x/route.js","../../importmap/x/selfA.js","../../importmap/x/dom.js"],"sourcesContent":["export const On = (elem, dict) => {\n let event, func;\n for (event in dict) {\n func = dict[event];\n elem.addEventListener(event, func);\n }\n return () => {\n for (event in dict) {\n func = dict[event];\n elem.removeEventListener(event, func);\n }\n };\n};\n","import { On } from \"x/On.js\";\n\nexport const nowUrl = () => location.pathname.slice(1),\n [route, setPre, preUrl, refresh, removeSlash, split, setUrl, goto] = (() => {\n let PRE = nowUrl(),\n HOOK = [];\n if (location.hash) PRE += location.hash;\n\n const HASH = \"#\",\n route = (hook) => {\n HOOK.push(hook);\n hook(nowUrl());\n return () => {\n HOOK = HOOK.filter((f) => f !== hook);\n };\n },\n setPre = (url) => {\n PRE = url;\n },\n preUrl = () => PRE,\n refresh = (url) => {\n url = url || nowUrl();\n for (const f of HOOK) f(url, PRE);\n setPre(url);\n },\n removeSlash = (url) => (url[0] === \"/\" ? url.slice(1) : url),\n split = (str, s) => {\n const p = str.indexOf(s);\n return p >= 0 ? [str.slice(0, p), str.slice(p + 1)] : [str, \"\"];\n },\n setUrl = (url) => {\n url = removeSlash(url);\n if (url !== PRE) {\n const [path, hash] = split(url, HASH),\n [p] = split(PRE, HASH);\n setPre(url);\n if (path !== p) {\n history.pushState(null, \"\", \"/\" + url);\n return 1;\n }\n if (location.hash.slice(1) !== hash) {\n location.hash = hash;\n return;\n }\n }\n window.dispatchEvent(new HashChangeEvent(\"hashchange\"));\n },\n goto = (url) => {\n if (setUrl(url)) refresh(url);\n };\n\n On(window, {\n popstate: () => {\n const url = nowUrl();\n if (url !== split(PRE, HASH)[0]) refresh(url);\n },\n });\n\n return [route, setPre, preUrl, refresh, removeSlash, split, setUrl, goto];\n })();\n","// 判断A标签的href是否为当前网站的, 如果是, 返回url, 以实现不刷新跳转\nexport const selfA = (p, e) => {\n var hash, url;\n if (p.host === location.host) {\n ({ hash } = p);\n url = p.pathname.slice(1) + p.search;\n if (hash) {\n url += hash;\n }\n e.preventDefault();\n return url;\n }\n};\n","export const D = document,\n B = D.body,\n newEl = D.createElement.bind(D);\n"],"mappings":"AAAA,MAAa,GAAM,EAAM,IAAS,CAChC,IAAI,EAAO,EACX,IAAK,KAAS,EACZ,EAAO,EAAK,GACZ,EAAK,iBAAiB,EAAO,CAAI,EAEnC,UAAa,CACX,IAAK,KAAS,EACZ,EAAO,EAAK,GACZ,EAAK,oBAAoB,EAAO,CAAI,CAExC,CACF,ECVa,MAAe,SAAS,SAAS,MAAM,CAAC,EACnD,CAAC,EAAO,EAAQ,EAAQ,EAAS,EAAa,EAAO,EAAQ,QAAe,CAC1E,IAAI,EAAM,EAAO,EACf,EAAO,CAAC,EACN,SAAS,OAAM,GAAO,SAAS,MAEnC,IACE,EAAS,IACP,EAAK,KAAK,CAAI,EACd,EAAK,EAAO,CAAC,MACA,CACX,EAAO,EAAK,OAAQ,GAAM,IAAM,CAAI,CACtC,GAEF,EAAU,GAAQ,CAChB,EAAM,CACR,EACA,MAAe,EACf,EAAW,GAAQ,CACjB,IAAa,EAAO,EACpB,IAAK,IAAM,KAAK,EAAM,EAAE,EAAK,CAAG,EAChC,EAAO,CAAG,CACZ,EACA,EAAe,GAAS,EAAI,KAAO,IAAM,EAAI,MAAM,CAAC,EAAI,EACxD,GAAS,EAAK,IAAM,CAClB,IAAM,EAAI,EAAI,QAAQ,CAAC,EACvB,OAAO,GAAK,EAAI,CAAC,EAAI,MAAM,EAAG,CAAC,EAAG,EAAI,MAAM,EAAI,CAAC,CAAC,EAAI,CAAC,EAAK,EAAE,CAChE,EACA,EAAU,GAAQ,CAEhB,GADA,EAAM,EAAY,CAAG,EACjB,IAAQ,EAAK,CACf,GAAM,CAAC,EAAM,GAAQ,EAAM,EAAK,GAAI,EAClC,CAAC,GAAK,EAAM,EAAK,GAAI,EAEvB,GADA,EAAO,CAAG,EACN,IAAS,EAEX,OADA,QAAQ,UAAU,KAAM,GAAI,IAAM,CAAG,EAC9B,EAET,GAAI,SAAS,KAAK,MAAM,CAAC,IAAM,EAAM,CACnC,SAAS,KAAO,EAChB,MACF,CACF,CACA,OAAO,cAAc,IAAI,gBAAgB,YAAY,CAAC,CACxD,EAYF,OAPA,EAAG,OAAQ,CACT,aAAgB,CACd,IAAM,EAAM,EAAO,EACf,IAAQ,EAAM,EAAK,GAAI,EAAE,IAAI,EAAQ,CAAG,CAC9C,CACF,CAAC,EAEM,CAAC,EAAO,EAAQ,EAAQ,EAAS,EAAa,EAAO,EAXlD,GAAQ,CACV,EAAO,CAAG,GAAG,EAAQ,CAAG,CAC9B,CASsE,CAC1E,GAAG,EC1DQ,GAAS,EAAG,IAAM,CAC7B,IAAI,EAAM,EACV,GAAI,EAAE,OAAS,SAAS,KAOtB,MANC,SAAW,EACZ,EAAM,EAAE,SAAS,MAAM,CAAC,EAAI,EAAE,OAC1B,IACF,GAAO,GAET,EAAE,eAAe,EACV,CAEX,ECZa,EAAI,SACf,EAAI,EAAE,KACN,EAAQ,EAAE,cAAc,KAAK,CAAC"}
|