@arshdelight/practi 0.9.3 → 0.9.5

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.
@@ -0,0 +1,106 @@
1
+ // practi web 默认前端逻辑:拉 /api/directs 渲染目录表格(客户端分页,每页 20 条)。
2
+ // 数据与展示分离:本文件只消费 JSON 数据窗,改样式改结构都在文件里随便改,
3
+ // 保存即触发 live reload(服务端注入的 /_lr.js 负责自动刷新)。
4
+ 'use strict';
5
+
6
+ var PAGE_SIZE = 20;
7
+ var docs = [];
8
+ var page = 1;
9
+
10
+ function escapeHtml(s) {
11
+ return String(s).replace(/[&<>"']/g, function (c) {
12
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
13
+ });
14
+ }
15
+
16
+ // 空态书本图标(lucide book-open,同 hub /me 空态)
17
+ var BOOK_ICON =
18
+ '<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
19
+ '<path d="M12 7v14"/>' +
20
+ '<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>' +
21
+ '</svg>';
22
+
23
+ function plural(n, unit) {
24
+ return n + ' ' + unit + (n === 1 ? '' : 's');
25
+ }
26
+
27
+ // 本地时刻、精确到分钟:YYYY-MM-DD HH:mm(等宽数字,列内不截断)
28
+ function fmtDateTime(iso) {
29
+ var d = new Date(iso);
30
+ if (isNaN(d.getTime())) return '';
31
+ var pad = function (n) { return (n < 10 ? '0' : '') + n; };
32
+ return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
33
+ }
34
+
35
+ // Added 单元格:claimedAt=声明值(认领盖戳);无 claimedAt 而 addedAt 存在=文件 mtime
36
+ // 推测值(弱化 + 悬浮说明);两者皆无=空白
37
+ function addedCell(d) {
38
+ if (!d.addedAt) return '<td class="doc-date"></td>';
39
+ if (!d.claimedAt) {
40
+ return '<td class="doc-date derived" title="file time (unstamped claim)">' + escapeHtml(fmtDateTime(d.addedAt)) + '</td>';
41
+ }
42
+ return '<td class="doc-date">' + escapeHtml(fmtDateTime(d.addedAt)) + '</td>';
43
+ }
44
+
45
+ function render() {
46
+ var t = POP_I18N.t;
47
+ var app = document.getElementById('app');
48
+ if (docs.length === 0) {
49
+ app.innerHTML =
50
+ '<div class="empty">' + BOOK_ICON +
51
+ '<p class="lead">' + t('emptyLead') + '</p>' +
52
+ '<p class="hint">' + t('emptyHint') + '</p></div>';
53
+ return;
54
+ }
55
+ var totalPages = Math.max(1, Math.ceil(docs.length / PAGE_SIZE));
56
+ if (page > totalPages) page = totalPages;
57
+ var rows = docs
58
+ .slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
59
+ .map(function (d) {
60
+ return '<tr>' +
61
+ '<td><a class="doc-name" href="/pop/' + encodeURIComponent(d.hash) + '">' + escapeHtml(d.name) + '</a>' +
62
+ (d.description ? '<p class="doc-desc">' + escapeHtml(d.description) + '</p>' : '') + '</td>' +
63
+ '<td class="doc-steps">' + d.steps + '</td>' +
64
+ addedCell(d) +
65
+ '</tr>';
66
+ })
67
+ .join('\n');
68
+ var pager = '';
69
+ if (page > 1 || page < totalPages) {
70
+ pager = '<div class="pager">' +
71
+ (page > 1 ? '<button type="button" data-page="' + (page - 1) + '">' + t('pagerPrev') + '</button>' : '') +
72
+ (page < totalPages ? '<button type="button" data-page="' + (page + 1) + '">' + t('pagerNext') + '</button>' : '') +
73
+ '</div>';
74
+ }
75
+ app.innerHTML =
76
+ '<h3 class="page-title">' + t('pageTitle') + '</h3>' +
77
+ '<table class="doc-table">' +
78
+ '<thead><tr><th>' + t('thDocument') + '</th><th class="col-steps">' + t('thSteps') + '</th><th class="col-added">' + t('thAdded') + '</th></tr></thead>' +
79
+ '<tbody>' + rows + '</tbody></table>' +
80
+ '<div class="count-row"><span>' + t('countDocs', docs.length) + ' · ' + t('pageOf', page, totalPages) + '</span>' + pager + '</div>';
81
+ }
82
+
83
+ // 翻页走事件委托:innerHTML 重渲染不需要重复绑监听
84
+ document.addEventListener('click', function (e) {
85
+ var btn = e.target.closest('[data-page]');
86
+ if (!btn) return;
87
+ page = Number(btn.getAttribute('data-page'));
88
+ render();
89
+ });
90
+
91
+ // 语言切换:整页文案重渲染
92
+ POP_I18N.onChange(render);
93
+
94
+ fetch('/api/directs')
95
+ .then(function (r) { return r.json(); })
96
+ .then(function (data) {
97
+ // 按 added 倒序:后发的在前;无 addedAt(时间未知)垫底
98
+ docs = (data.docs || []).slice().sort(function (a, b) {
99
+ return new Date(b.addedAt || 0) - new Date(a.addedAt || 0);
100
+ });
101
+ render();
102
+ })
103
+ .catch(function () {
104
+ document.getElementById('app').innerHTML =
105
+ '<div class="empty"><p class="lead">' + POP_I18N.t('loadFail') + '</p></div>';
106
+ });
@@ -0,0 +1,32 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>practi — detail</title>
7
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%2267.5%2075%20459%20459%22%20fill%3D%22none%22%20stroke%3D%22%23111827%22%20stroke-width%3D%2227%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%0A%20%20%20%20%3Cpath%20d%3D%22M%20301.0%20252.0%20C%20300.5%20246.2%20299.6%20228.7%20298.0%20217.2%20C%20296.4%20205.7%20294.8%20194.1%20291.4%20183.0%20C%20287.9%20171.8%20283.5%20160.2%20277.3%20150.3%20C%20271.1%20140.4%20263.1%20130.9%20254.1%20123.7%20C%20245.1%20116.5%20234.0%20110.2%20223.1%20107.1%20C%20212.2%20104.1%20199.5%20103.2%20188.5%20105.4%20C%20177.5%20107.5%20165.6%20112.8%20157.2%20119.9%20C%20148.8%20127.1%20142.8%20137.8%20138.0%20148.2%20C%20133.3%20158.7%20129.8%20171.1%20128.8%20182.7%20C%20127.7%20194.3%20127.4%20208.0%20131.8%20218.0%20C%20136.2%20228.0%20145.9%20236.1%20155.2%20242.8%20C%20164.4%20249.6%20176.1%20254.3%20187.3%20258.4%20C%20198.4%20262.5%20210.4%20264.9%20222.0%20267.2%20C%20233.7%20269.5%20256.5%20269.2%20257.2%20272.3%20C%20257.8%20275.5%20236.5%20281.6%20225.8%20286.2%20C%20215.0%20290.7%20203.3%20294.4%20192.6%20299.5%20C%20181.8%20304.7%20171.2%20310.6%20161.2%20317.1%20C%20151.2%20323.6%20140.8%20330.3%20132.6%20338.6%20C%20124.3%20346.9%20115.6%20356.3%20111.5%20366.7%20C%20107.4%20377.1%20103.6%20391.8%20107.9%20400.9%20C%20112.1%20410.0%20126.3%20418.0%20137.0%20421.5%20C%20147.7%20425.0%20160.5%20423.7%20172.0%20422.0%20C%20183.5%20420.2%20195.4%20416.3%20206.0%20411.3%20C%20216.6%20406.2%20226.8%20399.5%20235.6%20391.8%20C%20244.4%20384.1%20251.5%20374.1%20259.0%20365.0%20C%20266.5%20355.9%20274.3%20345.5%20280.7%20337.4%20C%20287.0%20329.2%20294.3%20319.6%20297.0%20316.0%22%20%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22M%20381.0%20210.0%20C%20386.8%20209.4%20404.2%20205.8%20415.6%20206.3%20C%20426.9%20206.8%20438.9%20208.3%20449.1%20212.9%20C%20459.2%20217.4%20469.8%20224.7%20476.4%20233.4%20C%20483.1%20242.2%20487.7%20254.4%20489.1%20265.4%20C%20490.6%20276.5%20489.7%20289.4%20485.2%20299.6%20C%20480.7%20309.8%20471.5%20320.6%20462.1%20326.7%20C%20452.7%20332.7%20440.2%20335.1%20428.8%20336.0%20C%20417.4%20336.9%20404.9%20334.7%20393.6%20332.1%20C%20382.3%20329.4%20369.0%20323.4%20361.0%20320.1%20C%20353.1%20316.7%20348.5%20313.3%20346.0%20312.0%22%20%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22M%20310.0%20303.0%20C%20311.1%20308.7%20315.4%20325.9%20316.8%20337.4%20C%20318.1%20349.0%20319.1%20360.6%20318.0%20372.1%20C%20316.8%20383.7%20313.7%20395.6%20309.9%20406.8%20C%20306.1%20418.1%20300.9%20429.2%20295.1%20439.5%20C%20289.3%20449.9%20283.2%20458.2%20275.0%20469.1%20C%20266.9%20480.1%20250.8%20499.0%20246.0%20505.0%22%20%2F%3E%0A%20%20%3C%2Fsvg%3E">
8
+ <link rel="stylesheet" href="/style.css">
9
+ </head>
10
+ <body>
11
+ <header>
12
+ <!-- 站点图标:与 hub 的 LogoMark 同一组贝塞尔路径 -->
13
+ <a class="brand-link" href="/" aria-label="practi — all practices">
14
+ <svg class="logo" viewBox="67.5 75 459 459" fill="none" stroke="currentColor" stroke-width="27" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
15
+ <path d="M 301.0 252.0 C 300.5 246.2 299.6 228.7 298.0 217.2 C 296.4 205.7 294.8 194.1 291.4 183.0 C 287.9 171.8 283.5 160.2 277.3 150.3 C 271.1 140.4 263.1 130.9 254.1 123.7 C 245.1 116.5 234.0 110.2 223.1 107.1 C 212.2 104.1 199.5 103.2 188.5 105.4 C 177.5 107.5 165.6 112.8 157.2 119.9 C 148.8 127.1 142.8 137.8 138.0 148.2 C 133.3 158.7 129.8 171.1 128.8 182.7 C 127.7 194.3 127.4 208.0 131.8 218.0 C 136.2 228.0 145.9 236.1 155.2 242.8 C 164.4 249.6 176.1 254.3 187.3 258.4 C 198.4 262.5 210.4 264.9 222.0 267.2 C 233.7 269.5 256.5 269.2 257.2 272.3 C 257.8 275.5 236.5 281.6 225.8 286.2 C 215.0 290.7 203.3 294.4 192.6 299.5 C 181.8 304.7 171.2 310.6 161.2 317.1 C 151.2 323.6 140.8 330.3 132.6 338.6 C 124.3 346.9 115.6 356.3 111.5 366.7 C 107.4 377.1 103.6 391.8 107.9 400.9 C 112.1 410.0 126.3 418.0 137.0 421.5 C 147.7 425.0 160.5 423.7 172.0 422.0 C 183.5 420.2 195.4 416.3 206.0 411.3 C 216.6 406.2 226.8 399.5 235.6 391.8 C 244.4 384.1 251.5 374.1 259.0 365.0 C 266.5 355.9 274.3 345.5 280.7 337.4 C 287.0 329.2 294.3 319.6 297.0 316.0" />
16
+ <path d="M 381.0 210.0 C 386.8 209.4 404.2 205.8 415.6 206.3 C 426.9 206.8 438.9 208.3 449.1 212.9 C 459.2 217.4 469.8 224.7 476.4 233.4 C 483.1 242.2 487.7 254.4 489.1 265.4 C 490.6 276.5 489.7 289.4 485.2 299.6 C 480.7 309.8 471.5 320.6 462.1 326.7 C 452.7 332.7 440.2 335.1 428.8 336.0 C 417.4 336.9 404.9 334.7 393.6 332.1 C 382.3 329.4 369.0 323.4 361.0 320.1 C 353.1 316.7 348.5 313.3 346.0 312.0" />
17
+ <path d="M 310.0 303.0 C 311.1 308.7 315.4 325.9 316.8 337.4 C 318.1 349.0 319.1 360.6 318.0 372.1 C 316.8 383.7 313.7 395.6 309.9 406.8 C 306.1 418.1 300.9 429.2 295.1 439.5 C 289.3 449.9 283.2 458.2 275.0 469.1 C 266.9 480.1 250.8 499.0 246.0 505.0" />
18
+ </svg>
19
+ <span class="brand">practi</span>
20
+ </a>
21
+ </header>
22
+ <div class="shell">
23
+ <aside class="side" id="side" aria-label="outline"></aside>
24
+ <main id="app" class="detail">
25
+ <div class="empty"><p class="lead">…</p></div>
26
+ </main>
27
+ </div>
28
+ <script src="/lang.js" defer></script>
29
+ <script src="/marked.min.js"></script>
30
+ <script src="/detail.js" defer></script>
31
+ </body>
32
+ </html>