@iftc/yete 0.0.1-alpha.2 → 0.0.1-alpha.4

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 CHANGED
@@ -1,7 +1,9 @@
1
1
  # Yete
2
2
 
3
- Yete 是一个轻量级、可扩展的 Web 框架。使用 Dexie 库操作 IndexedDB,将资源文件缓存到浏览器中,并支持离线访问。
4
- 开发文档:https://iftc.koyeb.app/docs/yete
3
+ &nbsp;&nbsp;&nbsp;&nbsp;Yete 是一个轻量级、可扩展的 Web 框架。使用 Dexie 库操作 IndexedDB,将资源文件缓存到浏览器中,能够减少加载时间和带宽消耗。<br>
4
+ &nbsp;&nbsp;&nbsp;&nbsp;每次构建后,会重新下载一次资源文件,并缓存到浏览器中。<br>
5
+ &nbsp;&nbsp;&nbsp;&nbsp;`libs` 目录下的文件不会视为资源文件,如需使用,请使用 `yete add <libdirpath>` 命令添加。<br>
6
+ &nbsp;&nbsp;&nbsp;&nbsp;开发文档:https://iftc.koyeb.app/docs/yete
5
7
 
6
8
  ## 快速开始
7
9
 
@@ -28,7 +30,7 @@ yete build && yete serve
28
30
 
29
31
  ## 许可证
30
32
 
31
- [MIT](https://github.com/IFTC-XLKJ/Yete/blob/main/LICENSE)
33
+ [MIT](https://github.com/IFTC-XLKJ/Yete?tab=MIT-1-ov-file)
32
34
 
33
35
  ## 联系作者
34
36
 
package/index.js CHANGED
@@ -1,5 +1,3 @@
1
- const { add } = require("dexie");
2
-
3
1
  /**
4
2
  * 自定义错误类
5
3
  */
@@ -7,6 +5,7 @@ class YeteError extends Error {
7
5
  /**
8
6
  * 构造函数
9
7
  * @param {String} message 错误信息
8
+ * @example new YeteError("Something went wrong.");
10
9
  */
11
10
  constructor(message) {
12
11
  super(message);
@@ -15,11 +14,22 @@ class YeteError extends Error {
15
14
  }
16
15
  /**
17
16
  * 页面基类
17
+ * @example
18
+ * class MyPage extends Page {
19
+ * constructor() {
20
+ * super("my-page");
21
+ * }
22
+ * render() {
23
+ * return document.createElement('div', { className: 'page' });
24
+ * }
25
+ * }
18
26
  */
19
27
  class Page {
20
28
  /**
21
29
  * 构造函数
22
30
  * @param {String} name
31
+ * @example
32
+ * new Page("my-page");
23
33
  */
24
34
  constructor(name) {
25
35
  this.name = name;
@@ -27,21 +37,49 @@ class Page {
27
37
  /**
28
38
  * 渲染页面
29
39
  * @returns {HTMLElement}
40
+ * @example
41
+ * return document.createElement('div', { className: 'page' });
30
42
  */
31
43
  render() {
32
- return document.createElement('div', { className: 'page' }, this.name);
44
+ return document.createElement('div', { className: 'page' });
33
45
  }
34
46
  }
35
47
 
48
+ /**
49
+ * 页面数组
50
+ * @property {Page} page 页面对象
51
+ */
36
52
  const pages = [];
53
+ /**
54
+ * 路由数组
55
+ * @property {String} path 路径
56
+ * @property {HTMLElement} page 页面
57
+ */
37
58
  const routes = [];
59
+ /**
60
+ * 事件数组
61
+ * @property {String} name 事件名称
62
+ * @property {Function} callback 回调函数
63
+ */
38
64
  const events = [];
65
+ let isStarted = false;
66
+
67
+ let custom404Page = null;
68
+ let custom502Page = null;
39
69
 
70
+ let searchParams = new URLSearchParams("");
71
+
72
+ /**
73
+ * 导出的对象
74
+ */
40
75
  const obj = {
41
76
  /**
42
77
  * 设置页面图标
43
78
  * @param {String} iconPath
44
79
  * @returns
80
+ * @throws {YeteError}
81
+ * @example
82
+ * setIcon("icon.png");
45
83
  */
46
84
  setIcon: function (iconPath) {
47
85
  const fileType = getFileType(iconPath);
@@ -63,6 +101,9 @@ const obj = {
63
101
  /**
64
102
  * 设置页面标题
65
103
  * @param {String} title
104
+ * @throws {YeteError}
105
+ * @example
106
+ * setTitle("Yete");
66
107
  */
67
108
  setTitle: function (title) {
68
109
  document.title = title;
@@ -70,6 +111,8 @@ const obj = {
70
111
  /**
71
112
  * 配置路由
72
113
  * @param {Array<Object>} options
114
+ * @property {String} path 路径
115
+ * @property {HTMLElement} page 页面
73
116
  * @example
74
117
  * options: [
75
118
  * { path: '/', page: () => loadPage(HomePage) },
@@ -80,10 +123,26 @@ const obj = {
80
123
  console.log(options);
81
124
  routes.push(...options);
82
125
  },
126
+ /**
127
+ * 页面基类
128
+ * @example
129
+ * class MyPage extends Page {
130
+ * constructor() {
131
+ * super("my-page");
132
+ * }
133
+ * render() {
134
+ * return document.createElement('div', { className: 'page' });
135
+ * }
136
+ * }
137
+ */
83
138
  Page: Page,
84
139
  /**
85
140
  * 加载组件
86
141
  * @param {Page} page
142
+ * @returns {HTMLElement}
143
+ * @throws {YeteError}
144
+ * @example
145
+ * loadPage(MyPage);
87
146
  */
88
147
  loadPage: function (page) {
89
148
  console.log(page);
@@ -96,14 +155,28 @@ const obj = {
96
155
  },
97
156
  /**
98
157
  * 启动程序
158
+ * @description 该方法只能调用一次
159
+ * @throws {YeteError}
160
+ * @example
161
+ * start();
99
162
  */
100
163
  start: function () {
164
+ if (isStarted) {
165
+ throw new YeteError("The program has already started.");
166
+ }
167
+ if (!routes.some(r => r.path === '/')) {
168
+ throw new YeteError("The route must contain a path '/'.");
169
+ }
170
+ isStarted = true;
101
171
  const path = location.hash.slice(1) || '/';
102
172
  obj.toPage(path);
103
173
  },
104
174
  /**
105
175
  * 加载 CSS 文件
106
176
  * @param {String} cssPath
177
+ * @returns {Promise<void>}
178
+ * @example
179
+ * await loadCSS("index.css");
107
180
  */
108
181
  loadCSS: async function (cssPath) {
109
182
  return new Promise(async (resolve, reject) => {
@@ -119,6 +192,9 @@ const obj = {
119
192
  /**
120
193
  * 加载 JS 文件
121
194
  * @param {String} scriptPath
195
+ * @returns {Promise<void>}
196
+ * @example
197
+ * await loadScript("index.js");
122
198
  */
123
199
  loadScript: async function (scriptPath) {
124
200
  return new Promise(async (resolve, reject) => {
@@ -134,19 +210,29 @@ const obj = {
134
210
  /**
135
211
  * 跳转到指定页面
136
212
  * @param {String} path
213
+ * @param {String} message
214
+ * @throws {YeteError}
215
+ * @example
216
+ * toPage('/about');
137
217
  */
138
- toPage: function (path) {
218
+ toPage: function (path, message = null) {
139
219
  location.hash = path;
140
220
  const page = routes.find(r => r.path === path);
141
221
  console.log(pages, page);
142
222
  if (page) {
143
223
  document.body.innerHTML = "";
144
224
  document.body.appendChild(page.page);
145
- obj.emit('page-change', new obj.YeteEvent("page-change", { path, isNotFound: false }));
225
+ obj.emit('page-change', new obj.YeteEvent("page-change", { path, isNotFound: false, message }));
146
226
  } else {
227
+ if (custom404Page) {
228
+ document.body.innerHTML = "";
229
+ document.body.appendChild(custom404Page);
230
+ obj.emit('page-change', new obj.YeteEvent("page-change", { path, isNotFound: true }));
231
+ return;
232
+ }
147
233
  document.body.innerHTML = "<center><h1>404 Not Found</h1><p>Power By Yete</p></center>";
148
234
  obj.emit('page-change', new obj.YeteEvent("page-change", { path, isNotFound: true }));
149
- throw new YeteError("The page not found.");
235
+ console.error(new YeteError("The page not found."));
150
236
  }
151
237
  },
152
238
  /**
@@ -154,6 +240,8 @@ const obj = {
154
240
  * @param {String} tag
155
241
  * @param {Object} options
156
242
  * @returns {HTMLElement}
243
+ * @example
244
+ * const button = newElement('Text', { className: 'btn', textContent: 'Click Me' });
157
245
  */
158
246
  newElement: function (tag, options = {}) {
159
247
  if (!UIElements[tag]) {
@@ -175,6 +263,8 @@ const obj = {
175
263
  * 添加事件监听器
176
264
  * @param {String} type
177
265
  * @param {Function} listener
266
+ * @example
267
+ * addEventListener('click', () => console.log('Clicked'));
178
268
  */
179
269
  addEventListener: function (type, listener) {
180
270
  events.push({ type, listener });
@@ -183,6 +273,8 @@ const obj = {
183
273
  * 移除事件监听器
184
274
  * @param {String} type
185
275
  * @param {Function} listener
276
+ * @example
277
+ * removeEventListener('click', () => console.log('Clicked'));
186
278
  */
187
279
  removeEventListener: function (type, listener) {
188
280
  const index = events.findIndex(e => e.type === type && e.listener === listener);
@@ -194,6 +286,8 @@ const obj = {
194
286
  * 派发事件
195
287
  * @param {String} type
196
288
  * @param {Object} event
289
+ * @example
290
+ * dispatchEvent('click', new YeteEvent("click", { bubbles: true, cancelable: true }));
197
291
  */
198
292
  dispatchEvent: function (type, event) {
199
293
  events.forEach(e => {
@@ -204,12 +298,19 @@ const obj = {
204
298
  },
205
299
  /**
206
300
  * 创建事件
301
+ * @param {String} type
302
+ * @param {Object} options
303
+ * @returns {YeteEvent}
304
+ * @example
305
+ * const event = new YeteEvent("click", { bubbles: true, cancelable: true });
207
306
  */
208
307
  YeteEvent: class extends Event {
209
308
  /**
210
309
  * 构造函数
211
310
  * @param {String} type
212
311
  * @param {Object} options
312
+ * @example
313
+ * const event = new YeteEvent("click", { bubbles: true, cancelable: true });
213
314
  */
214
315
  constructor(type, options) {
215
316
  super(type, { bubbles: true, cancelable: true });
@@ -218,33 +319,404 @@ const obj = {
218
319
  }
219
320
  }
220
321
  },
322
+ /**
323
+ * 获取绑定的元素
324
+ * @returns {NodeList}
325
+ * @example
326
+ * const elements = getBinding();
327
+ */
328
+ getBinding: function () {
329
+ const elementsWithId = document.querySelectorAll('[yete-id]');
330
+ return elementsWithId;
331
+ },
332
+ /**
333
+ * 自定义 404 页面
334
+ * @description 当页面不存在时,会显示此页面
335
+ * @param {Page} page
336
+ * @example
337
+ * custom404(Custom404Page);
338
+ */
339
+ custom404: function (page = null) {
340
+ if (!page) {
341
+ custom404Page = null;
342
+ return;
343
+ }
344
+ const p = new page();
345
+ if (p instanceof Page) {
346
+ custom404Page = p.page;
347
+ } else {
348
+ throw new YeteError("The page not found.");
349
+ }
350
+ },
351
+ /**
352
+ * 自定义 502 错误页面
353
+ * @description 当页面有未捕获的错误被抛出时,会显示此页面
354
+ * @param {Page} page
355
+ * @example
356
+ * custom502(Custom502Page);
357
+ */
358
+ custom502: function (page = null) {
359
+ if (!page) {
360
+ custom502Page = null;
361
+ return;
362
+ }
363
+ const p = new page();
364
+ if (p instanceof Page) {
365
+ custom502Page = p.page;
366
+ } else {
367
+ throw new YeteError("The page not found.");
368
+ }
369
+ },
370
+ /**
371
+ * HTTP 请求
372
+ * @description HTTP 请求封装,提供更便捷的方式进行 HTTP 请求
373
+ * @example
374
+ * const res = await HTTP.GET("https://example.com");
375
+ * const data = await res.json();
376
+ */
377
+ HTTP: class {
378
+ constructor() { }
379
+ /**
380
+ * GET 请求
381
+ * @param {String} url
382
+ * @param {Object} headers
383
+ * @returns {Promise<Response>}
384
+ * @example
385
+ * const res = await HTTP.GET("https://example.com");
386
+ * const data = await res.json();
387
+ */
388
+ static async GET(url, headers = {}) {
389
+ return await fetch(url, { headers });
390
+ }
391
+ /**
392
+ * POST 请求
393
+ * @param {String} url
394
+ * @param {Object} body
395
+ * @param {Object} headers
396
+ * @returns {Promise<Response>}
397
+ * @example
398
+ * const res = await HTTP.POST("https://example.com", { name: "Yete" });
399
+ * const data = await res.json();
400
+ */
401
+ static async POST(url, body, headers = {}) {
402
+ return await fetch(url, { method: "POST", body, headers });
403
+ }
404
+ /**
405
+ * PUT 请求
406
+ * @param {String} url
407
+ * @param {Object} body
408
+ * @param {Object} headers
409
+ * @returns {Promise<Response>}
410
+ * @example
411
+ * const res = await HTTP.PUT("https://example.com", { name: "Yete" });
412
+ * const data = await res.json();
413
+ */
414
+ static async PUT(url, body, headers = {}) {
415
+ return await fetch(url, { method: "PUT", body, headers });
416
+ }
417
+ /**
418
+ * DELETE 请求
419
+ * @param {String} url
420
+ * @param {Object} headers
421
+ * @returns {Promise<Response>}
422
+ * @example
423
+ * const res = await HTTP.DELETE("https://example.com");
424
+ * const data = await res.json();
425
+ */
426
+ static async DELETE(url, headers = {}) {
427
+ return await fetch(url, { method: "DELETE", headers });
428
+ }
429
+ /**
430
+ * PATCH 请求
431
+ * @param {String} url
432
+ * @param {Object} body
433
+ * @param {Object} headers
434
+ * @returns {Promise<Response>}
435
+ * @example
436
+ * const res = await HTTP.PATCH("https://example.com", { name: "Yete" });
437
+ * const data = await res.json();
438
+ */
439
+ static async PATCH(url, body, headers = {}) {
440
+ return await fetch(url, { method: "PATCH", body, headers });
441
+ }
442
+ /**
443
+ * HEAD 请求
444
+ * @param {String} url
445
+ * @param {Object} headers
446
+ * @returns {Promise<Response>}
447
+ * @example
448
+ * const res = await HTTP.HEAD("https://example.com");
449
+ * const data = await res.json();
450
+ */
451
+ static async HEAD(url, headers = {}) {
452
+ return await fetch(url, { method: "HEAD", headers });
453
+ }
454
+ /**
455
+ * OPTIONS 请求
456
+ * @param {String} url
457
+ * @param {Object} headers
458
+ * @returns {Promise<Response>}
459
+ * @example
460
+ * const res = await HTTP.OPTIONS("https://example.com");
461
+ * const data = await res.json();
462
+ */
463
+ static async OPTIONS(url, headers = {}) {
464
+ return await fetch(url, { method: "OPTIONS", headers });
465
+ }
466
+ /**
467
+ * TRACE 请求
468
+ * @param {String} url
469
+ * @param {Object} headers
470
+ * @returns {Promise<Response>}
471
+ * @example
472
+ * const res = await HTTP.TRACE("https://example.com");
473
+ * const data = await res.json();
474
+ */
475
+ static async TRACE(url, headers = {}) {
476
+ return await fetch(url, { method: "TRACE", headers });
477
+ }
478
+ /**
479
+ * CONNECT 请求
480
+ * @param {String} url
481
+ * @param {Object} headers
482
+ * @returns {Promise<Response>}
483
+ * @example
484
+ * const res = await HTTP.CONNECT("https://example.com");
485
+ * const data = await res.json();
486
+ */
487
+ static async CONNECT(url, headers = {}) {
488
+ return await fetch(url, { method: "CONNECT", headers });
489
+ }
490
+ /**
491
+ * 读取流
492
+ * @param {Response} response
493
+ * @param {Function} callback
494
+ * @returns {Promise<void>}
495
+ * @example
496
+ * await HTTP.readStream(res, ({ chunk, done }) => { console.log(done, chunk); });
497
+ */
498
+ static async readStream(response, callback = ({ chunk, done }) => { console.log(done, chunk); }) {
499
+ if (!response.ok) {
500
+ throw new YeteError("Response is not ok.");
501
+ }
502
+ if (!response.body) {
503
+ throw new YeteError("Response body is not available.");
504
+ }
505
+ const reader = response.body.getReader();
506
+ const decoder = new TextDecoder('utf-8');
507
+ try {
508
+ while (true) {
509
+ const { done, value } = await reader.read();
510
+ if (done) {
511
+ const finalChunk = decoder.decode();
512
+ callback({ chunk: finalChunk || null, done: true });
513
+ break;
514
+ }
515
+ const chunk = decoder.decode(value, { stream: true });
516
+ callback({ chunk, done: false });
517
+ }
518
+ } catch (error) {
519
+ throw new YeteError(`Stream reading failed: ${error.message}`);
520
+ } finally {
521
+ reader.releaseLock();
522
+ }
523
+ }
524
+ /**
525
+ * 读取响应体
526
+ * @param {Response} response
527
+ * @param {String} type
528
+ * @returns {Promise<any>}
529
+ * @example
530
+ * const data = await HTTP.readBody(res);
531
+ */
532
+ static async readBody(response, type = "text") {
533
+ if (!response.ok) {
534
+ throw new YeteError("Response is not ok.");
535
+ }
536
+ if (!response.body) {
537
+ throw new YeteError("Response body is not available.");
538
+ }
539
+ switch (type) {
540
+ case "text":
541
+ return await response.text();
542
+ case "json":
543
+ return await response.json();
544
+ case "blob":
545
+ return await response.blob();
546
+ case "arrayBuffer":
547
+ return await response.arrayBuffer();
548
+ case "formData":
549
+ return await response.formData();
550
+ default:
551
+ throw new YeteError("Invalid type.");
552
+ }
553
+ }
554
+ /**
555
+ * 读取响应头
556
+ * @param {Response} response
557
+ * @returns {Promise<Headers>}
558
+ * @example
559
+ * const headers = await HTTP.readHeaders(res);
560
+ */
561
+ static async readHeaders(response) {
562
+ return response.headers;
563
+ }
564
+ },
565
+ /**
566
+ * 获取 URL 参数
567
+ * @example
568
+ * const params = searchParams;
569
+ */
570
+ searchParams: function () {
571
+ return searchParams;
572
+ },
573
+ /**
574
+ * 加载 UI
575
+ * @description 可通过 URL 或 XML 字符串加载 UI
576
+ * @param {String} str
577
+ * @returns {Promise<HTMLElement>}
578
+ * @example
579
+ * await loadUI("https://example.com/ui.xml");
580
+ * await loadUI(`<Yete><Text>Hello World</Text></Yete>`);
581
+ */
582
+ loadUI: async function (str) {
583
+ let isURL = false;
584
+ try {
585
+ new URL(str);
586
+ isURL = true;
587
+ } catch { }
588
+ if (isURL) {
589
+ const res = await fetch(str);
590
+ const text = await res.text();
591
+ return loadUI(text);
592
+ } else {
593
+ return loadUI(str);
594
+ }
595
+ }
221
596
  };
222
597
 
598
+ /**
599
+ * 加载 UI
600
+ * @param {String} xmlstring
601
+ * @returns {HTMLElement}
602
+ */
603
+ function loadUI(xmlstring) {
604
+ const parser = new DOMParser();
605
+ try {
606
+ const xmlDoc = parser.parseFromString(xmlstring, "text/xml");
607
+ const root = xmlDoc.documentElement;
608
+ if (root.tagName === "Yete") {
609
+ let html = "<div yete-root>";
610
+ const rootNodes = root.childNodes;
611
+ for (let i = 0; i < rootNodes.length; i++) {
612
+ const node = rootNodes[i];
613
+ html += parseNode(node);
614
+ }
615
+ html += "</div>";
616
+ return new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
617
+ } else {
618
+ throw new YeteError("Invalid XML string.");
619
+ }
620
+ } catch (error) {
621
+ throw new YeteError(`Failed to LoadUI : ${error.message}`);
622
+ }
623
+ function parseNode(node) {
624
+ const tagName = node.tagName;
625
+ if (node.nodeName === "#text") {
626
+ return node.textContent;
627
+ }
628
+ if (node instanceof NodeList) {
629
+ let html = "";
630
+ for (let i = 0; i < node.length; i++) {
631
+ html += parseNode(node[i]);
632
+ }
633
+ return html;
634
+ }
635
+ if (UIElements[tagName]) {
636
+ const htmlTagName = UIElements[tagName].html;
637
+ let attrs = "";
638
+ for (const i in UIElements[tagName].attr) {
639
+ const attr = UIElements[tagName].attr[i];
640
+ console.log(node.hasAttribute(attr), attr);
641
+ if (node.hasAttribute(attr)) {
642
+ const value = node.getAttribute(attr);
643
+ console.log(attr, value);
644
+ if (value == "true") {
645
+ attrs += ` ${attr == "id" ? `${"yete-id"}` : attr}`;
646
+ } else {
647
+ attrs += ` ${attr == "id" ? `${"yete-id"}` : attr}="${value}"`;
648
+ }
649
+ }
650
+ }
651
+ return `<${htmlTagName} ${attrs} style="${UIElements[tagName].style || ""}">${parseNode(node.childNodes)}</${htmlTagName}>`;
652
+ } else {
653
+ throw new YeteError(`Invalid tag name: ${tagName}`);
654
+ }
655
+ }
656
+ }
657
+
223
658
  /**
224
659
  * 添加事件监听器
225
660
  * @param {String} type
226
661
  * @param {Function} listener
662
+ * @example
663
+ * on('click', () => console.log('Clicked'));
227
664
  */
228
665
  obj.on = obj.addEventListener;
229
666
  /**
230
667
  * 移除事件监听器
231
668
  * @param {String} type
232
669
  * @param {Function} listener
670
+ * @example
671
+ * off('click', () => console.log('Clicked'));
233
672
  */
234
673
  obj.off = obj.removeEventListener;
235
674
  /**
236
675
  * 派发事件
237
676
  * @param {String} type
238
677
  * @param {Object} event
678
+ * @example
679
+ * emit('click', new YeteEvent("click", { bubbles: true, cancelable: true }));
239
680
  */
240
681
  obj.emit = obj.dispatchEvent;
241
682
 
242
- // 监听 hashchange 事件,实现页面跳转
683
+
684
+ window.addEventListener("error", function (event) {
685
+ const { message, filename, lineno, colno, error } = event;
686
+ if (custom502Page) {
687
+ document.body.innerHTML = "";
688
+ document.body.appendChild(custom502Page);
689
+ const errorMsgEl = document.getElementById("error-message");
690
+ const errorFilenameEl = document.getElementById("error-filename");
691
+ const errorLinenoEl = document.getElementById("error-lineno");
692
+ const errorColnoEl = document.getElementById("error-colno");
693
+ const errorErrorEl = document.getElementById("error-error");
694
+ if (errorMsgEl) errorMsgEl.innerText = message;
695
+ if (errorFilenameEl) errorFilenameEl.innerText = filename;
696
+ if (errorLinenoEl) errorLinenoEl.innerText = lineno;
697
+ if (errorColnoEl) errorColnoEl.innerText = colno;
698
+ if (errorErrorEl) errorErrorEl.innerText = error;
699
+ obj.emit('page-change', new obj.YeteEvent("page-change", { path, isError: true }));
700
+ } else {
701
+ document.body.innerHTML = `<center><h1>Have an error: "${message}" in "${filename}" at ${lineno}:${colno}</h1><p>Power by Yete.</p></center>`;
702
+ obj.emit('page-change', new obj.YeteEvent("page-change", { path, isError: true }));
703
+ }
704
+ });
705
+
243
706
  window.addEventListener('hashchange', () => {
244
- const path = location.hash.slice(1) || '/';
707
+ const fullpath = location.hash.slice(1) || '/';
708
+ console.log("[Yete]", "Full path", fullpath);
709
+ const SearchParams = fullpath.split("?").slice(1).join("?") || '';
710
+ const path = fullpath.split("?")[0];
711
+ const query = new URLSearchParams(SearchParams);
712
+ searchParams = query;
713
+ console.log("[Yete]", "Query", query);
714
+ console.log("[Yete]", "Navigate to", path);
245
715
  obj.toPage(path);
246
716
  });
247
717
 
718
+ console.log("%cYete v" + YeteVersion, "border-radius: 16px; font-size: 64px; color: #fff; background-color: #007bff; padding: 5px;margin: 5px;");
719
+
248
720
  /**
249
721
  * 获取文件类型
250
722
  * @param {String} filePath
@@ -0,0 +1,156 @@
1
+ class Button extends HTMLElement {
2
+ static get observedAttributes() {
3
+ return ['variant', 'size', 'disabled', 'loading', 'icon', 'icon-position'];
4
+ }
5
+
6
+ constructor() {
7
+ super();
8
+ this.attachShadow({ mode: "open" });
9
+ this._rippleElement = null;
10
+ }
11
+
12
+ connectedCallback() {
13
+ this._render();
14
+ this._addEventListeners();
15
+ }
16
+
17
+ attributeChangedCallback(name, oldValue, newValue) {
18
+ if (oldValue !== newValue) {
19
+ this._render();
20
+ }
21
+ }
22
+
23
+ _render() {
24
+ const variant = this.getAttribute('variant') || 'contained';
25
+ const size = this.getAttribute('size') || 'medium';
26
+ const disabled = this.hasAttribute('disabled');
27
+ const loading = this.hasAttribute('loading');
28
+ const icon = this.getAttribute('icon');
29
+ const iconPosition = this.getAttribute('icon-position') || 'start';
30
+
31
+ this.shadowRoot.innerHTML = `
32
+ <style>
33
+ :host {
34
+ display: inline-block;
35
+ user-select: none;
36
+ }
37
+ .button {
38
+ font-family: Roboto, sans-serif;
39
+ font-size: 14px;
40
+ font-weight: 500;
41
+ letter-spacing: 0.089em;
42
+ border-radius: 20px; /* Android风格圆角 */
43
+ border: none;
44
+ cursor: pointer;
45
+ position: relative;
46
+ overflow: hidden;
47
+ transition: all 0.2s ease;
48
+ display: inline-flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ gap: 8px;
52
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); /* Android阴影 */
53
+ min-height: 36px;
54
+ padding: 8px 16px;
55
+ }
56
+ .button:hover {
57
+ box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
58
+ }
59
+ .button:active {
60
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
61
+ transform: translateY(1px);
62
+ }
63
+ }
64
+ .button:disabled {
65
+ cursor: not-allowed;
66
+ opacity: 0.6;
67
+ }
68
+ /* 变体样式 */
69
+ .button.contained {
70
+ background: #1976d2; /* Android蓝色 */
71
+ color: white;
72
+ }
73
+ .button.contained:hover {
74
+ background: #1565c0;
75
+ }
76
+ .button.contained:active {
77
+ background: #0d47a1;
78
+ }
79
+ .button.outlined {
80
+ background: transparent;
81
+ color: #1976d2;
82
+ border: 1px solid #1976d2;
83
+ }
84
+ .button.outlined:hover {
85
+ background: rgba(25, 118, 210, 0.04);
86
+ }
87
+ .button.text {
88
+ background: transparent;
89
+ color: #1976d2;
90
+ box-shadow: none;
91
+ }
92
+ .button.text:hover {
93
+ background: rgba(25, 118, 210, 0.04);
94
+ }
95
+ /* 尺寸样式 */
96
+ .button.small { padding: 6px 12px; min-height: 32px; font-size: 12px; }
97
+ .button.medium { padding: 8px 16px; min-height: 36px; font-size: 14px; }
98
+ .button.large { padding: 10px 20px; min-height: 44px; font-size: 16px; }
99
+ /* 波纹效果 */
100
+ .ripple {
101
+ position: absolute;
102
+ border-radius: 50%;
103
+ background: rgba(255, 255, 255, 0.5);
104
+ transform: scale(0);
105
+ animation: ripple-animation 0.6s linear;
106
+ pointer-events: none;
107
+ }
108
+ @keyframes ripple-animation {
109
+ to { transform: scale(4); opacity: 0; }
110
+ }
111
+ /* 加载状态 */
112
+ .spinner {
113
+ width: 18px;
114
+ height: 18px;
115
+ border: 2px solid currentColor;
116
+ border-top-color: transparent;
117
+ border-radius: 50%;
118
+ animation: spin 0.8s linear infinite;
119
+ }
120
+ @keyframes spin {
121
+ to { transform: rotate(360deg); }
122
+ }
123
+ </style>
124
+ <button class="button ${variant} ${size}" ?disabled="${disabled}">
125
+ ${loading ? '<span class="spinner"></span>' : ''}
126
+ ${!loading && icon && iconPosition === 'start' ? `<span class="icon">${icon}</span>` : ''}
127
+ <slot></slot>
128
+ ${!loading && icon && iconPosition === 'end' ? `<span class="icon">${icon}</span>` : ''}
129
+ </button>
130
+ `;
131
+ }
132
+
133
+ _addEventListeners() {
134
+ const button = this.shadowRoot.querySelector('.button');
135
+ button?.addEventListener('click', (e) => this._handleRipple(e));
136
+ }
137
+
138
+ _handleRipple(event) {
139
+ if (this.hasAttribute('disabled')) return;
140
+
141
+ const button = event.currentTarget;
142
+ const rect = button.getBoundingClientRect();
143
+ const size = Math.max(rect.width, rect.height);
144
+
145
+ this._rippleElement = document.createElement('span');
146
+ this._rippleElement.classList.add('ripple');
147
+ this._rippleElement.style.width = this._rippleElement.style.height = `${size}px`;
148
+ this._rippleElement.style.left = `${event.clientX - rect.left - size / 2}px`;
149
+ this._rippleElement.style.top = `${event.clientY - rect.top - size / 2}px`;
150
+
151
+ button.appendChild(this._rippleElement);
152
+ setTimeout(() => this._rippleElement?.remove(), 600);
153
+ }
154
+ }
155
+
156
+ customElements.define("yete-button", Button);
@@ -0,0 +1,21 @@
1
+ class Text extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ this.attachShadow({ mode: 'open' });
5
+ }
6
+
7
+ connectedCallback() {
8
+ this.shadowRoot.innerHTML = `
9
+ <style>
10
+ :host {
11
+ display: block;
12
+ color: var(--text-color, #757575);
13
+ user-select: none;
14
+ }
15
+ </style>
16
+ <slot></slot>
17
+ `;
18
+ }
19
+ }
20
+
21
+ customElements.define('yete-text', Text);
@@ -7,6 +7,15 @@
7
7
  "html": "yete-text",
8
8
  "style": null,
9
9
  "attr": ["id", "class"]
10
+ },
11
+ "Button": {
12
+ "html": "yete-button",
13
+ "style": null,
14
+ "attr": ["id", "class"]
10
15
  }
11
- }
16
+ },
17
+ "files": [
18
+ "Text.js",
19
+ "Button.js"
20
+ ]
12
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iftc/yete",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.4",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "IFTC",
package/src/loader.js CHANGED
@@ -24,7 +24,10 @@ async function loadIndex() {
24
24
  script.src = url;
25
25
  document.body.appendChild(script);
26
26
  } else {
27
+ document.body.innerHTML = `<center><h1>index.js not found in the database. Reseting...</h1><p>Powered By Yete</p></center>`;
27
28
  console.error("Error: index.js not found in the database.");
29
+ localStorage.clear();
30
+ location.reload();
28
31
  }
29
32
  }
30
33
 
@@ -38,12 +41,42 @@ async function updateFiles() {
38
41
  path: file,
39
42
  size: blob.size,
40
43
  type: blob.type,
41
- lastModified: blob.lastModified,
44
+ lastModified: new Date(),
42
45
  blob: blob
43
46
  });
44
47
  }
45
48
  }
46
49
 
50
+ async function loadLibs() {
51
+ const files = yete.files;
52
+ const libFiles = files.filter(f => f.startsWith("libs/"));
53
+ console.log(libFiles);
54
+ for (const file of libFiles) {
55
+ const f = await db.files.get({ path: file });
56
+ console.log(file, f)
57
+ const url = URL.createObjectURL(f.blob);
58
+ if (file.endsWith(".js")) {
59
+ const script = document.createElement('script');
60
+ script.src = url;
61
+ document.body.appendChild(script);
62
+ } else if (file.endsWith(".css")) {
63
+ const style = document.createElement('style');
64
+ style.src = url;
65
+ document.head.appendChild(style);
66
+ } else if (file.endsWith(".ttf") || file.endsWith(".otf") || file.endsWith(".woff") || file.endsWith(".woff2")) {
67
+ const font = document.createElement("style");
68
+ style.textContent = `@font-face {
69
+ font-family: '${file.split("/").pop().split(".").slice(0, -1).join(".")}';
70
+ src: url('${url}') format('${file.split(".").pop()}'),
71
+ font-weight: normal;
72
+ font-style: normal;
73
+ font-display: swap;
74
+ }`;
75
+ document.head.appendChild(font);
76
+ }
77
+ }
78
+ }
79
+
47
80
  async function main() {
48
81
  const version = yete.version;
49
82
  if (localStorage.getItem('yete-version') != version) {
@@ -53,6 +86,7 @@ async function main() {
53
86
  } else {
54
87
  await loadFiles();
55
88
  }
89
+ await loadLibs();
56
90
  }
57
91
 
58
92
  main();