sandtable 0.4.0 → 1.0.1
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 +157 -22
- package/dashboard/dashboard.html +1318 -834
- package/package.json +1 -1
- package/server.js +10 -1
- package/src/builder/build.js +121 -230
- package/src/check/check.js +137 -0
- package/src/cli/sandtable.js +36 -0
- package/src/contract/default-contract.json +21 -0
- package/src/contract/loader.js +203 -0
- package/src/progress/parser.js +302 -0
- package/src/scanner/scan.js +47 -251
- package/src/scanner/scan.js.v0.4.bak +415 -0
- package/templates/.sandtable.template.json +24 -26
package/dashboard/dashboard.html
CHANGED
|
@@ -3,742 +3,955 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title
|
|
6
|
+
<title>Sandtable IDE · 沙盘 v0.3</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
|
10
|
+
<script>
|
|
11
|
+
tailwind.config = {
|
|
12
|
+
darkMode: "class",
|
|
13
|
+
theme: {
|
|
14
|
+
extend: {
|
|
15
|
+
colors: {
|
|
16
|
+
"on-primary-container": "#fefcff",
|
|
17
|
+
"on-primary": "#ffffff",
|
|
18
|
+
"primary": "#0058be",
|
|
19
|
+
"surface-container": "#eceef0",
|
|
20
|
+
"on-primary-fixed": "#001a42",
|
|
21
|
+
"on-secondary-fixed": "#002113",
|
|
22
|
+
"on-secondary-fixed-variant": "#005236",
|
|
23
|
+
"surface-bright": "#f7f9fb",
|
|
24
|
+
"inverse-on-surface": "#eff1f3",
|
|
25
|
+
"on-error": "#ffffff",
|
|
26
|
+
"primary-fixed-dim": "#adc6ff",
|
|
27
|
+
"surface-variant": "#e0e3e5",
|
|
28
|
+
"tertiary-container": "#a36700",
|
|
29
|
+
"on-surface": "#191c1e",
|
|
30
|
+
"on-tertiary-fixed-variant": "#653e00",
|
|
31
|
+
"secondary": "#006c49",
|
|
32
|
+
"secondary-fixed": "#6ffbbe",
|
|
33
|
+
"on-secondary": "#ffffff",
|
|
34
|
+
"background": "#f7f9fb",
|
|
35
|
+
"on-background": "#191c1e",
|
|
36
|
+
"on-error-container": "#93000a",
|
|
37
|
+
"surface-container-lowest": "#ffffff",
|
|
38
|
+
"surface-tint": "#005ac2",
|
|
39
|
+
"surface-dim": "#d8dadc",
|
|
40
|
+
"inverse-surface": "#2d3133",
|
|
41
|
+
"on-tertiary": "#ffffff",
|
|
42
|
+
"error": "#ba1a1a",
|
|
43
|
+
"surface-container-highest": "#e0e3e5",
|
|
44
|
+
"outline-design": "#727785",
|
|
45
|
+
"primary-fixed": "#d8e2ff",
|
|
46
|
+
"inverse-primary": "#adc6ff",
|
|
47
|
+
"surface": "#f7f9fb",
|
|
48
|
+
"secondary-container": "#6cf8bb",
|
|
49
|
+
"surface-container-high": "#e6e8ea",
|
|
50
|
+
"tertiary-fixed": "#ffddb8",
|
|
51
|
+
"surface-container-low": "#f2f4f6",
|
|
52
|
+
"on-surface-variant": "#424754",
|
|
53
|
+
"tertiary-fixed-dim": "#ffb95f",
|
|
54
|
+
"on-tertiary-container": "#fffbff",
|
|
55
|
+
"secondary-fixed-dim": "#4edea3",
|
|
56
|
+
"on-secondary-container": "#00714d",
|
|
57
|
+
"on-primary-fixed-variant": "#004395",
|
|
58
|
+
"error-container": "#ffdad6",
|
|
59
|
+
"primary-container": "#2170e4",
|
|
60
|
+
"outline-variant": "#c2c6d6",
|
|
61
|
+
"tertiary": "#825100",
|
|
62
|
+
"on-tertiary-fixed": "#2a1700"
|
|
63
|
+
},
|
|
64
|
+
borderRadius: {
|
|
65
|
+
"DEFAULT": "0.25rem",
|
|
66
|
+
"lg": "0.5rem",
|
|
67
|
+
"xl": "0.75rem",
|
|
68
|
+
"full": "9999px"
|
|
69
|
+
},
|
|
70
|
+
spacing: {
|
|
71
|
+
"base": "4px",
|
|
72
|
+
"xl": "32px",
|
|
73
|
+
"md": "16px",
|
|
74
|
+
"xs": "4px",
|
|
75
|
+
"container-margin": "24px",
|
|
76
|
+
"sm": "8px",
|
|
77
|
+
"lg": "24px",
|
|
78
|
+
"gutter": "16px"
|
|
79
|
+
},
|
|
80
|
+
fontFamily: {
|
|
81
|
+
"code-md": ["JetBrains Mono"],
|
|
82
|
+
"code-sm": ["JetBrains Mono"],
|
|
83
|
+
"display-lg": ["Inter"],
|
|
84
|
+
"headline-sm": ["Inter"],
|
|
85
|
+
"headline-md": ["Inter"],
|
|
86
|
+
"body-md": ["Inter"],
|
|
87
|
+
"title-md": ["Inter"],
|
|
88
|
+
"body-lg": ["Inter"],
|
|
89
|
+
"label-caps": ["Inter"]
|
|
90
|
+
},
|
|
91
|
+
fontSize: {
|
|
92
|
+
"code-md": ["14px", { lineHeight: "20px", fontWeight: "500" }],
|
|
93
|
+
"code-sm": ["12px", { lineHeight: "16px", fontWeight: "500" }],
|
|
94
|
+
"display-lg": ["32px", { lineHeight: "40px", letterSpacing: "-0.02em", fontWeight: "600" }],
|
|
95
|
+
"headline-sm": ["20px", { lineHeight: "28px", fontWeight: "600" }],
|
|
96
|
+
"headline-md": ["24px", { lineHeight: "32px", letterSpacing: "-0.01em", fontWeight: "600" }],
|
|
97
|
+
"body-md": ["14px", { lineHeight: "20px", fontWeight: "400" }],
|
|
98
|
+
"title-md": ["16px", { lineHeight: "24px", fontWeight: "600" }],
|
|
99
|
+
"body-lg": ["16px", { lineHeight: "26px", fontWeight: "400" }],
|
|
100
|
+
"label-caps": ["12px", { lineHeight: "16px", letterSpacing: "0.05em", fontWeight: "700" }]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</script>
|
|
7
106
|
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
107
|
+
.material-symbols-outlined {
|
|
108
|
+
font-family: 'Material Symbols Outlined', sans-serif;
|
|
109
|
+
font-weight: normal; font-style: normal;
|
|
110
|
+
font-size: 20px; line-height: 1;
|
|
111
|
+
letter-spacing: normal; text-transform: none;
|
|
112
|
+
display: inline-block; white-space: nowrap;
|
|
113
|
+
word-wrap: normal; direction: ltr;
|
|
114
|
+
-webkit-font-smoothing: antialiased;
|
|
115
|
+
}
|
|
116
|
+
.pulse-border {
|
|
117
|
+
animation: pulse-border 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
118
|
+
}
|
|
119
|
+
@keyframes pulse-border {
|
|
120
|
+
0%, 100% { border-color: rgba(59, 130, 246, 1); box-shadow: 0 0 0px rgba(59, 130, 246, 0); }
|
|
121
|
+
50% { border-color: rgba(59, 130, 246, 0.3); box-shadow: 0 0 8px rgba(59, 130, 246, 0.4); }
|
|
122
|
+
}
|
|
123
|
+
.accordion-content {
|
|
124
|
+
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
|
|
125
|
+
max-height: 2000px; opacity: 1; overflow: hidden;
|
|
126
|
+
}
|
|
127
|
+
.accordion-content.collapsed {
|
|
128
|
+
max-height: 0; opacity: 0;
|
|
20
129
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
#header h1::before{content:'◆';color:var(--accent);font-size:10px}
|
|
28
|
-
.badge{background:rgba(255,255,255,.1);padding:2px 10px;border-radius:10px;font-size:10px;font-weight:500;letter-spacing:.3px}
|
|
29
|
-
#header .right{font-size:11px;display:flex;align-items:center;gap:12px;color:#9aa0ab;position:relative}
|
|
30
|
-
.dot{width:8px;height:8px;border-radius:50%;background:var(--accent);display:inline-block;box-shadow:0 0 6px rgba(0,184,148,.4);animation:pulse 2s infinite}
|
|
31
|
-
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
|
32
|
-
|
|
33
|
-
/* Brief */
|
|
34
|
-
#brief{background:linear-gradient(135deg,#16213e,#0f3460);color:#b0bec5;padding:10px 24px;font-size:12px;line-height:1.6;border-bottom:2px solid rgba(0,184,148,.2)}
|
|
35
|
-
#brief strong{color:var(--accent);font-size:11px;margin-right:10px;text-transform:uppercase;letter-spacing:.5px}
|
|
36
|
-
#brief a{color:#64b5f6;text-decoration:none;cursor:pointer;transition:color .15s}
|
|
37
|
-
#brief a:hover{color:#90caf9;text-decoration:underline}
|
|
38
|
-
|
|
39
|
-
/* Tree tab bar — inside left panel */
|
|
40
|
-
#tree-tabs{display:flex;gap:4px;margin-bottom:10px;flex-wrap:wrap;align-items:center}
|
|
41
|
-
#tree-tabs button{padding:4px 10px;border:1px solid #d5dce6;background:#fff;border-radius:7px;font-size:10.5px;cursor:pointer;color:var(--muted);transition:all .15s;white-space:nowrap;font-weight:500}
|
|
42
|
-
#tree-tabs button:hover{border-color:var(--accent);color:var(--accent);background:#f0fdf9}
|
|
43
|
-
#tree-tabs button.on{background:var(--accent);color:#fff;border-color:var(--accent);font-weight:600}
|
|
44
|
-
|
|
45
|
-
/* Main dual layout */
|
|
46
|
-
#main{display:flex;flex:1;min-height:0}
|
|
47
|
-
#left{width:40%;border-right:1px solid var(--border);overflow-y:auto;padding:20px 24px;background:#fafbfc;transition:background .5s ease}
|
|
48
|
-
#right{width:60%;overflow-y:auto;padding:20px 24px;background:#f8f9fb;transition:background .5s ease}
|
|
49
|
-
#left::-webkit-scrollbar,#right::-webkit-scrollbar{width:5px}
|
|
50
|
-
#left::-webkit-scrollbar-thumb,#right::-webkit-scrollbar-thumb{background:#d0d5dd;border-radius:3px}
|
|
51
|
-
|
|
52
|
-
/* Filter-connected: unified panel tint when doc filter is active */
|
|
53
|
-
body.filter-active #left, body.filter-active #right{background:#eef2f7}
|
|
54
|
-
body.filter-active #left{border-right-color:#d5dce6}
|
|
55
|
-
.tree-card.filter-match{border-color:#4a90d9!important;box-shadow:0 0 0 3px rgba(74,144,217,.2)!important;background:#fff!important}
|
|
56
|
-
.tree-card.filter-match .tc-head{background:rgba(74,144,217,.06)}
|
|
57
|
-
.rp-card.filter-match{border-color:#4a90d9!important;box-shadow:0 0 0 3px rgba(74,144,217,.2)!important}
|
|
58
|
-
|
|
59
|
-
.panel-title{font-size:13px;font-weight:700;margin-bottom:14px;display:flex;justify-content:space-between;align-items:center;color:var(--text);letter-spacing:-.2px}
|
|
60
|
-
.panel-title .count{font-size:11px;color:var(--muted);font-weight:500}
|
|
61
|
-
|
|
62
|
-
/* Category section */
|
|
63
|
-
.cat-section{margin-bottom:16px}
|
|
64
|
-
.cat-header{font-size:11px;font-weight:600;padding:5px 10px;border-radius:5px;margin-bottom:8px;display:flex;justify-content:space-between;letter-spacing:.2px}
|
|
65
|
-
.cat-header.primary{background:linear-gradient(135deg,#e6f9f1,#d4f5e8);color:#00855a}
|
|
66
|
-
.cat-header.secondary{background:#f1f2f5;color:var(--muted)}
|
|
67
|
-
|
|
68
|
-
/* Element card */
|
|
69
|
-
.card{border:1px solid var(--border);border-radius:8px;margin-bottom:10px;overflow:hidden;box-shadow:var(--shadow-sm);transition:box-shadow .2s,transform .15s;cursor:default}
|
|
70
|
-
.card:hover{box-shadow:var(--shadow-md);transform:translateY(-1px)}
|
|
71
|
-
.card.active-filter{border-color:var(--link);box-shadow:0 0 0 2px rgba(9,132,227,.25)}
|
|
72
|
-
.card[data-tg="past"]{background:var(--past-bg);border-color:var(--past-border)}
|
|
73
|
-
.card[data-tg="current"]{background:var(--current-bg);border-color:var(--current-border);box-shadow:0 2px 8px rgba(240,192,0,.12)}
|
|
74
|
-
.card[data-tg="future"]{background:var(--future-bg);border-color:var(--future-border)}
|
|
75
|
-
.card.secondary{border-left:3px solid var(--secondary)}
|
|
76
|
-
.card-head{padding:8px 14px;font-size:12.5px;font-weight:600;display:flex;justify-content:space-between;align-items:center;gap:8px}
|
|
77
|
-
.card-head.completed{background:linear-gradient(135deg,#e6f9f1,#d4f5e8);color:#00855a}
|
|
78
|
-
.card-head.in_progress{background:linear-gradient(135deg,#fff8e6,#fff3d4);color:#b06d00}
|
|
79
|
-
.card-head.pending{background:#f8f9fb;color:var(--muted)}
|
|
80
|
-
.card-head.blocked{background:linear-gradient(135deg,#fff0ed,#ffe8e3);color:#c04030}
|
|
81
|
-
.card-head > span:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
82
|
-
.card-meta{display:flex;align-items:center;gap:6px;font-size:10px;font-weight:400;flex-shrink:0}
|
|
83
|
-
.card-type{background:rgba(0,0,0,.06);padding:2px 6px;border-radius:4px;font-size:9px;color:var(--muted);font-weight:500}
|
|
84
|
-
.card-src{font-size:9px;background:#e8f1fd;padding:2px 7px;border-radius:4px;color:var(--link);cursor:pointer;text-decoration:none;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500;transition:all .15s}
|
|
85
|
-
.card-src:hover{background:var(--link);color:#fff}
|
|
86
|
-
.card-summary{padding:6px 14px;font-size:11.5px;color:var(--light);line-height:1.5}
|
|
87
|
-
.card-tags{padding:3px 14px 8px}
|
|
88
|
-
.tag{display:inline-block;background:rgba(0,0,0,.05);padding:2px 7px;border-radius:4px;font-size:9.5px;color:var(--light);margin-right:4px;margin-bottom:2px;font-weight:500}
|
|
89
|
-
.card-children{border-top:1px solid rgba(0,0,0,.06);padding:4px 12px 8px}
|
|
90
|
-
.sub{font-size:11.5px;padding:4px 0;display:flex;align-items:center;gap:6px;color:var(--light);transition:background .15s;border-radius:4px;cursor:default}
|
|
91
|
-
.sub:hover{background:rgba(0,0,0,.03)}
|
|
92
|
-
|
|
93
|
-
/* Time badge */
|
|
94
|
-
.tbadge{font-size:9px;padding:1px 7px;border-radius:4px;font-weight:600;letter-spacing:.2px}
|
|
95
|
-
.tb-past{background:rgba(96,125,139,.12);color:#546e7a}
|
|
96
|
-
.tb-current{background:rgba(240,160,0,.12);color:#b06d00}
|
|
97
|
-
.tb-future{background:rgba(0,0,0,.06);color:var(--muted)}
|
|
98
|
-
|
|
99
|
-
/* Event stream */
|
|
100
|
-
.ev-item{padding:10px 12px;border-left:4px solid var(--border);margin-bottom:6px;border-radius:0 6px 6px 0;background:#fff;font-size:11.5px;box-shadow:var(--shadow-sm);transition:box-shadow .2s,transform .15s}
|
|
101
|
-
.ev-item:hover{box-shadow:var(--shadow-md)}
|
|
102
|
-
.ev-item.must{border-left-width:6px;background:#fffef7;box-shadow:0 2px 8px rgba(225,112,85,.1)}
|
|
103
|
-
.ev-item.t1{border-left-color:#e17055} .ev-item.t2{border-left-color:#6c5ce7}
|
|
104
|
-
.ev-item.t3{border-left-color:#0984e3} .ev-item.t4{border-left-color:#00b894}
|
|
105
|
-
.ev-item.t5{border-left-color:#f0a000} .ev-item.t6{border-left-color:#795548}
|
|
106
|
-
.ev-item.t7{border-left-color:#e84393}
|
|
107
|
-
.ev-head{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
|
|
108
|
-
.ev-title{font-weight:600;flex:1;line-height:1.4;font-size:12px}
|
|
109
|
-
.ev-type{font-size:9.5px;padding:2px 7px;border-radius:4px;color:#fff;font-weight:600;white-space:nowrap;letter-spacing:.3px}
|
|
110
|
-
.ev-t1{background:#e17055} .ev-t2{background:#6c5ce7} .ev-t3{background:#0984e3}
|
|
111
|
-
.ev-t4{background:#00b894} .ev-t5{background:#f0a000} .ev-t6{background:#795548} .ev-t7{background:#e84393}
|
|
112
|
-
.ev-meta{font-size:10px;color:var(--muted);margin-top:4px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
|
113
|
-
.ev-time{font-family:'SF Mono',Consolas,monospace;font-size:11px;color:var(--text);font-weight:600;background:rgba(0,0,0,.04);padding:1px 6px;border-radius:3px}
|
|
114
|
-
.ev-ref{color:var(--link);cursor:pointer;text-decoration:none;font-weight:500}
|
|
115
|
-
.ev-ref:hover{text-decoration:underline;color:#0066cc}
|
|
116
|
-
.ev-actor{font-style:italic;color:var(--light)}
|
|
117
|
-
.ev-tag{background:rgba(0,0,0,.05);padding:1px 5px;border-radius:3px;font-size:9px;color:var(--light)}
|
|
118
|
-
.ev-pdot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:3px;flex-shrink:0}
|
|
119
|
-
.ev-p1{background:var(--priority-must);box-shadow:0 0 4px rgba(225,112,85,.4)}
|
|
120
|
-
.ev-p2{background:var(--priority-should)} .ev-p3{background:var(--priority-can)}
|
|
121
|
-
.ev-day{font-size:11px;font-weight:700;padding:12px 0 6px;border-bottom:1.5px solid var(--border);margin-bottom:6px;color:var(--light);letter-spacing:.3px}
|
|
122
|
-
.ev-day:first-child{padding-top:0}
|
|
123
|
-
.ev-item.hl{animation:flash .8s ease-in-out 3;background:#fff9c4!important}
|
|
124
|
-
@keyframes flash{0%,100%{background:#fff9c4}50%{background:#ffe082}}
|
|
125
|
-
|
|
126
|
-
/* Footer — removed conventions bar */
|
|
130
|
+
.hover-scale {
|
|
131
|
+
transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease;
|
|
132
|
+
}
|
|
133
|
+
.hover-scale:hover { transform: scale(1.02); }
|
|
134
|
+
.hide-scrollbar::-webkit-scrollbar { display: none; }
|
|
135
|
+
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
127
136
|
|
|
128
|
-
/*
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#float.fr{left:auto;right:0;box-shadow:-8px 0 30px rgba(0,0,0,.18)}
|
|
132
|
-
#float.fr.hide{transform:translateX(110vw)}
|
|
133
|
-
#float-head{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--header-bg);color:#fff;font-size:13px;font-weight:600}
|
|
134
|
-
#float-body{flex:1;overflow-y:auto;padding:16px 20px;font-size:12.5px;line-height:1.8;white-space:pre-wrap;word-break:break-word;font-family:'SF Mono',Consolas,monospace;color:var(--text)}
|
|
135
|
-
#float-body::-webkit-scrollbar{width:5px}
|
|
136
|
-
#float-body::-webkit-scrollbar-thumb{background:#d0d5dd;border-radius:3px}
|
|
137
|
-
#float-close{background:none;border:none;color:#fff;font-size:22px;cursor:pointer;opacity:.8;transition:opacity .15s;padding:0 4px}
|
|
138
|
-
#float-close:hover{opacity:1}
|
|
139
|
-
#overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,.25);z-index:999;backdrop-filter:blur(2px)}
|
|
140
|
-
#overlay.hide{display:none}
|
|
141
|
-
|
|
142
|
-
/* Milestone tooltip */
|
|
143
|
-
.mtip{display:none;position:absolute;background:#fff;border:1px solid var(--border);border-radius:6px;padding:8px 12px;font-size:10.5px;box-shadow:var(--shadow-md);z-index:50;max-width:260px;line-height:1.5;left:100%;top:0;margin-left:10px}
|
|
144
|
-
.sub:hover .mtip{display:block}
|
|
145
|
-
|
|
146
|
-
.empty{text-align:center;padding:50px 20px;color:var(--muted)}
|
|
147
|
-
.empty h3{font-size:15px;margin-bottom:8px;font-weight:600}
|
|
148
|
-
.empty p{font-size:12px;color:var(--muted)}
|
|
149
|
-
|
|
150
|
-
/* Filter bar */
|
|
151
|
-
#filter-bar{display:none;padding:8px 12px;background:#e8f1fd;border:1px solid #b8d4f7;border-radius:8px;margin-bottom:12px;font-size:11px;align-items:center;gap:8px}
|
|
152
|
-
#filter-bar.show{display:flex}
|
|
153
|
-
#filter-bar .ftext{flex:1;color:var(--link);font-weight:500}
|
|
154
|
-
#filter-bar .fclear{cursor:pointer;background:var(--link);color:#fff;border:none;padding:3px 10px;border-radius:4px;font-size:10px;font-weight:600;transition:opacity .15s}
|
|
155
|
-
#filter-bar .fclear:hover{opacity:.85}
|
|
137
|
+
/* Scrollbar */
|
|
138
|
+
#left::-webkit-scrollbar, #right::-webkit-scrollbar { width: 5px; }
|
|
139
|
+
#left::-webkit-scrollbar-thumb, #right::-webkit-scrollbar-thumb { background: #d0d5dd; border-radius: 3px; }
|
|
156
140
|
|
|
157
|
-
/*
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.
|
|
162
|
-
.
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
/* Sort + filter toolbar */
|
|
193
|
-
#toolbar{display:flex;align-items:center;gap:8px;padding:6px 0;margin-bottom:6px;flex-wrap:wrap}
|
|
194
|
-
#toolbar .sort-group{display:flex;gap:0;border:1px solid #d5dce6;border-radius:6px;overflow:hidden}
|
|
195
|
-
#toolbar .sort-btn{padding:3px 10px;font-size:10px;border:none;background:#fff;color:var(--muted);cursor:pointer;font-weight:500;transition:all .15s}
|
|
196
|
-
#toolbar .sort-btn.on{background:var(--accent);color:#fff}
|
|
197
|
-
#toolbar .sort-btn+.sort-btn{border-left:1px solid #d5dce6}
|
|
198
|
-
#toolbar .sort-label{font-size:9px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.3px}
|
|
199
|
-
#toolbar .fclear{display:none;cursor:pointer;background:var(--link);color:#fff;border:none;padding:3px 10px;border-radius:4px;font-size:10px;font-weight:600}
|
|
200
|
-
#toolbar .fclear.show{display:inline-block}
|
|
201
|
-
#toolbar .fclear:hover{opacity:.85}
|
|
202
|
-
#toolbar .ftext{font-size:10px;color:var(--link);font-weight:500;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
141
|
+
/* Floating panel */
|
|
142
|
+
#float { position: fixed; top: 0; left: 0; width: 480px; max-width: 94vw; height: 100vh;
|
|
143
|
+
background: #fff; box-shadow: 8px 0 30px rgba(0,0,0,.18); z-index: 1000;
|
|
144
|
+
display: flex; flex-direction: column; transition: transform .3s cubic-bezier(.4,0,.2,1); }
|
|
145
|
+
#float.hide { transform: translateX(-110vw); }
|
|
146
|
+
#float.fr { left: auto; right: 0; box-shadow: -8px 0 30px rgba(0,0,0,.18); }
|
|
147
|
+
#float.fr.hide { transform: translateX(110vw); }
|
|
148
|
+
#float-head { display: flex; justify-content: space-between; align-items: center;
|
|
149
|
+
padding: 12px 16px; background: #111827; color: #fff; font-size: 13px; font-weight: 600; }
|
|
150
|
+
#float-body { flex: 1; overflow-y: auto; padding: 16px 20px; font-size: 12.5px;
|
|
151
|
+
line-height: 1.8; white-space: pre-wrap; word-break: break-word;
|
|
152
|
+
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; color: #191c1e; }
|
|
153
|
+
#float-body::-webkit-scrollbar { width: 5px; }
|
|
154
|
+
#float-body::-webkit-scrollbar-thumb { background: #d0d5dd; border-radius: 3px; }
|
|
155
|
+
#float-close { background: none; border: none; color: #fff; font-size: 22px;
|
|
156
|
+
cursor: pointer; opacity: .8; transition: opacity .15s; padding: 0 4px; }
|
|
157
|
+
#float-close:hover { opacity: 1; }
|
|
158
|
+
#overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
|
159
|
+
background: rgba(0,0,0,.25); z-index: 999; backdrop-filter: blur(2px); }
|
|
160
|
+
#overlay.hide { display: none; }
|
|
161
|
+
|
|
162
|
+
/* Token tooltip */
|
|
163
|
+
#token-tip { display: none; position: absolute; top: 100%; right: 0; margin-top: 8px;
|
|
164
|
+
background: #1a2332; color: #e8eaed; padding: 10px 14px; border-radius: 8px;
|
|
165
|
+
font-size: 11px; line-height: 1.6; z-index: 100;
|
|
166
|
+
box-shadow: 0 4px 20px rgba(0,0,0,.35); min-width: 200px; white-space: nowrap; }
|
|
167
|
+
#token-tip.show { display: block; }
|
|
168
|
+
#token-tip .tip-row { display: flex; justify-content: space-between; gap: 16px; padding: 2px 0; }
|
|
169
|
+
#token-tip .tip-row .tip-name { color: #b0bec5; }
|
|
170
|
+
#token-tip .tip-row .tip-val { color: #fff; font-weight: 600; font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; }
|
|
171
|
+
#token-tip .tip-divider { border-top: 1px solid rgba(255,255,255,.1); margin: 4px 0; }
|
|
172
|
+
|
|
173
|
+
/* Filter-connected panels */
|
|
174
|
+
body.filter-active #left, body.filter-active #right { background: #eef2f7; }
|
|
175
|
+
body.filter-active #left { border-right-color: #d5dce6; }
|
|
203
176
|
|
|
204
177
|
/* Event tag clickable */
|
|
205
|
-
.ev-tag{cursor:pointer;transition:all .15s}
|
|
206
|
-
.ev-tag:hover{background:
|
|
207
|
-
|
|
208
|
-
/* Roadmap version card */
|
|
209
|
-
.rp-card{border:1.5px solid #d5dce6;border-radius:10px;margin-bottom:10px;overflow:hidden;background:#fff;box-shadow:var(--shadow-sm);transition:box-shadow .2s,border-color .3s}
|
|
210
|
-
.rp-card:hover{box-shadow:var(--shadow-md)}
|
|
211
|
-
.rp-card.rp-done{background:#f0fdf6;border-color:#b7e4cf}
|
|
212
|
-
.rp-card.rp-mixed{background:#fffef7;border-color:#f0d000}
|
|
213
|
-
.rp-head{padding:10px 14px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;gap:8px;user-select:none;transition:background .15s;border-bottom:1px solid transparent}
|
|
214
|
-
.rp-card.open .rp-head{border-bottom-color:var(--border)}
|
|
215
|
-
.rp-head:hover{background:rgba(0,0,0,.02)}
|
|
216
|
-
.rp-head .rp-title{font-weight:600;font-size:12.5px;flex:1;min-width:0}
|
|
217
|
-
.rp-head .rp-meta{display:flex;align-items:center;gap:6px;font-size:10px;color:var(--muted)}
|
|
218
|
-
.rp-head .rp-ratio{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px}
|
|
219
|
-
.rp-head .rp-ratio.all-done{background:#d4f5e8;color:#00855a}
|
|
220
|
-
.rp-head .rp-ratio.partial{background:#fff3d4;color:#b06d00}
|
|
221
|
-
.rp-head .rp-arrow{font-size:10px;color:var(--muted);transition:transform .2s}
|
|
222
|
-
.rp-card.open .rp-arrow{transform:rotate(90deg)}
|
|
223
|
-
.rp-body{display:none}
|
|
224
|
-
.rp-card.open .rp-body{display:block}
|
|
225
|
-
.rp-card.semi .rp-body{display:block}
|
|
226
|
-
.rp-child{padding:7px 14px;border-bottom:1px dotted #eef0f4;font-size:11px;display:flex;align-items:center;gap:8px;transition:background .1s}
|
|
227
|
-
.rp-child:hover{background:rgba(0,0,0,.015)}
|
|
228
|
-
.rp-child:last-child{border-bottom:none}
|
|
229
|
-
.rp-child .rp-cicon{font-size:11px;flex-shrink:0}
|
|
230
|
-
.rp-child .rp-cname{flex:1;font-weight:500;color:var(--text)}
|
|
231
|
-
.rp-child .rp-ctime{font-size:9.5px;color:var(--muted);font-family:'SF Mono',Consolas,monospace;white-space:nowrap}
|
|
232
|
-
.rp-child .rp-clabel{font-size:9px;padding:1px 6px;border-radius:3px;font-weight:500;white-space:nowrap}
|
|
233
|
-
.rp-child .rp-clabel.done{background:#e6f9f1;color:#00855a}
|
|
234
|
-
.rp-child .rp-clabel.progress{background:#fff8e6;color:#b06d00}
|
|
235
|
-
.rp-child .rp-clabel.pending-label{background:#f1f2f5;color:var(--muted)}
|
|
236
|
-
.rp-more{cursor:pointer;padding:8px 12px;text-align:center;font-size:11px;color:var(--link);font-weight:500;border-top:1px dashed var(--border);transition:background .15s}
|
|
237
|
-
.rp-more:hover{background:#edf0f4;color:#0066cc}
|
|
238
|
-
|
|
239
|
-
/* Enhanced tree card */
|
|
240
|
-
.tree-card{border:1px solid var(--border);border-radius:8px;margin-bottom:8px;overflow:hidden;background:#fff;box-shadow:var(--shadow-sm);transition:box-shadow .2s,transform .15s,border-color .3s;cursor:pointer}
|
|
241
|
-
.tree-card:hover{box-shadow:var(--shadow-md);transform:translateY(-1px)}
|
|
242
|
-
.tree-card.secondary{border-left:3px solid var(--secondary)}
|
|
243
|
-
.tree-card[data-tg="past"]{background:var(--past-bg);border-color:var(--past-border)}
|
|
244
|
-
.tree-card[data-tg="current"]{background:var(--current-bg);border-color:var(--current-border)}
|
|
245
|
-
.tree-card[data-tg="future"]{background:var(--future-bg);border-color:var(--future-border)}
|
|
246
|
-
.tree-card .tc-head{padding:10px 14px;font-size:12px;font-weight:600;display:flex;justify-content:space-between;align-items:center;gap:8px}
|
|
247
|
-
.tree-card .tc-head > span:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
248
|
-
.tree-card .tc-meta{display:flex;align-items:center;gap:6px;font-size:9.5px;flex-shrink:0;color:var(--muted)}
|
|
249
|
-
.tree-card .tc-time{font-family:'SF Mono',Consolas,monospace;font-size:9.5px;color:var(--muted)}
|
|
250
|
-
.tc-head .tc-time{font-size:10px}
|
|
251
|
-
.tree-card .tc-summary{padding:4px 12px 8px;font-size:11px;color:var(--light);line-height:1.5}
|
|
252
|
-
.tree-card .tc-tags{padding:2px 12px 6px}
|
|
253
|
-
.tree-card .tc-src{padding:2px 12px 6px}
|
|
254
|
-
.tree-card .tc-src a{font-size:9.5px;background:#e8f1fd;padding:2px 7px;border-radius:4px;color:var(--link);cursor:pointer;text-decoration:none;transition:all .15s}
|
|
255
|
-
.tree-card .tc-src a:hover{background:var(--link);color:#fff}
|
|
256
|
-
.tree-card .tc-type{font-size:9px;padding:1px 6px;border-radius:4px;font-weight:500}
|
|
257
|
-
.tree-card .tc-type.status-done{background:#e6f9f1;color:#00855a}
|
|
258
|
-
.tree-card .tc-type.status-progress{background:#fff8e6;color:#b06d00}
|
|
259
|
-
.tree-card .tc-type.status-pending{background:#f1f2f5;color:var(--muted)}
|
|
260
|
-
|
|
261
|
-
/* Token display in header */
|
|
262
|
-
#token-today{font-size:10px;color:#9aa0ab;cursor:default;position:relative;white-space:nowrap}
|
|
263
|
-
#token-today:hover{color:#b8c0c8}
|
|
264
|
-
#token-tip{display:none;position:absolute;top:100%;right:0;margin-top:8px;background:#1a2332;color:#e8eaed;padding:10px 14px;border-radius:8px;font-size:11px;line-height:1.6;z-index:100;box-shadow:0 4px 20px rgba(0,0,0,.35);min-width:200px;white-space:nowrap}
|
|
265
|
-
#token-tip.show{display:block}
|
|
266
|
-
#token-tip .tip-row{display:flex;justify-content:space-between;gap:16px;padding:2px 0}
|
|
267
|
-
#token-tip .tip-row .tip-name{color:#b0bec5}
|
|
268
|
-
#token-tip .tip-row .tip-val{color:#fff;font-weight:600;font-family:'SF Mono',Consolas,monospace}
|
|
269
|
-
#token-tip .tip-divider{border-top:1px solid rgba(255,255,255,.1);margin:4px 0}
|
|
178
|
+
.ev-tag { cursor: pointer; transition: all .15s; }
|
|
179
|
+
.ev-tag:hover { background: #10B981; color: #fff; }
|
|
270
180
|
|
|
181
|
+
/* Thread card */
|
|
182
|
+
.th-card { border: 1.5px solid #c2c6d6; border-radius: 10px; margin-bottom: 10px;
|
|
183
|
+
overflow: hidden; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.06);
|
|
184
|
+
transition: box-shadow .2s; }
|
|
185
|
+
.th-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.08); }
|
|
186
|
+
.th-card.th-active { border-color: #4a90d9; box-shadow: 0 0 0 2px rgba(74,144,217,.3); }
|
|
187
|
+
.th-head { padding: 10px 14px; background: #f2f4f6; cursor: pointer;
|
|
188
|
+
display: flex; justify-content: space-between; align-items: center; gap: 8px;
|
|
189
|
+
user-select: none; transition: background .15s; }
|
|
190
|
+
.th-head:hover { background: #eceef0; }
|
|
191
|
+
.th-head .th-title { font-weight: 600; font-size: 12px; flex: 1; min-width: 0;
|
|
192
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
193
|
+
.th-head .th-count { font-size: 10px; color: #727785; white-space: nowrap;
|
|
194
|
+
background: rgba(0,0,0,.06); padding: 2px 8px; border-radius: 10px; }
|
|
195
|
+
.th-body { display: none; border-top: 1px solid #c2c6d6; }
|
|
196
|
+
.th-card.open .th-body { display: block; }
|
|
197
|
+
.th-card.semi .th-body { display: block; }
|
|
198
|
+
.th-body .ev-item { margin: 0; border-radius: 0; border-left-width: 3px; box-shadow: none; }
|
|
199
|
+
.th-body .ev-item:last-child { border-radius: 0 0 8px 0; }
|
|
200
|
+
|
|
201
|
+
/* Event items (non-threaded) */
|
|
202
|
+
.ev-item { padding: 10px 12px; border-left: 4px solid #c2c6d6; margin-bottom: 6px;
|
|
203
|
+
border-radius: 0 6px 6px 0; background: #fff; font-size: 11.5px;
|
|
204
|
+
box-shadow: 0 1px 3px rgba(0,0,0,.06); transition: box-shadow .2s, transform .15s; }
|
|
205
|
+
.ev-item:hover { box-shadow: 0 4px 12px rgba(0,0,0,.08); }
|
|
206
|
+
.ev-item.t1 { border-left-color: #e17055; } .ev-item.t2 { border-left-color: #6c5ce7; }
|
|
207
|
+
.ev-item.t3 { border-left-color: #0984e3; } .ev-item.t4 { border-left-color: #00b894; }
|
|
208
|
+
.ev-item.t5 { border-left-color: #f0a000; } .ev-item.t6 { border-left-color: #795548; }
|
|
209
|
+
.ev-item.t7 { border-left-color: #e84393; }
|
|
210
|
+
.ev-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
|
|
211
|
+
.ev-title { font-weight: 600; flex: 1; line-height: 1.4; font-size: 12px; }
|
|
212
|
+
.ev-type { font-size: 9.5px; padding: 2px 7px; border-radius: 4px; color: #fff;
|
|
213
|
+
font-weight: 600; white-space: nowrap; letter-spacing: .3px; }
|
|
214
|
+
.ev-t1 { background: #e17055; } .ev-t2 { background: #6c5ce7; }
|
|
215
|
+
.ev-t3 { background: #0984e3; } .ev-t4 { background: #00b894; }
|
|
216
|
+
.ev-t5 { background: #f0a000; } .ev-t6 { background: #795548; } .ev-t7 { background: #e84393; }
|
|
217
|
+
.ev-meta { font-size: 10px; color: #727785; margin-top: 4px; display: flex;
|
|
218
|
+
gap: 8px; flex-wrap: wrap; align-items: center; }
|
|
219
|
+
.ev-time { font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; font-size: 11px;
|
|
220
|
+
color: #191c1e; font-weight: 600; background: rgba(0,0,0,.04);
|
|
221
|
+
padding: 1px 6px; border-radius: 3px; }
|
|
222
|
+
.ev-ref { color: #0984e3; cursor: pointer; text-decoration: none; font-weight: 500; }
|
|
223
|
+
.ev-ref:hover { text-decoration: underline; color: #0066cc; }
|
|
224
|
+
.ev-actor { font-style: italic; color: #5a6072; }
|
|
225
|
+
.ev-tag { background: rgba(0,0,0,.05); padding: 1px 5px; border-radius: 3px;
|
|
226
|
+
font-size: 9px; color: #5a6072; }
|
|
227
|
+
.ev-day { font-size: 12px; font-weight: 700; padding: 12px 0 6px;
|
|
228
|
+
border-bottom: 1.5px solid #c2c6d6; margin-bottom: 6px;
|
|
229
|
+
color: #5a6072; letter-spacing: .3px; }
|
|
230
|
+
.ev-day:first-child { padding-top: 0; }
|
|
231
|
+
|
|
232
|
+
/* Thread more/collapse buttons */
|
|
233
|
+
.th-more { cursor: pointer; padding: 8px 12px; text-align: center; font-size: 11px;
|
|
234
|
+
color: #0984e3; font-weight: 500; background: #f8f9fb;
|
|
235
|
+
border-top: 1px dashed #c2c6d6; transition: background .15s; }
|
|
236
|
+
.th-more:hover { background: #edf0f4; color: #0066cc; }
|
|
237
|
+
.th-collapse { cursor: pointer; padding: 8px 12px; text-align: center; font-size: 11px;
|
|
238
|
+
color: #727785; font-weight: 500; background: #f8f9fb;
|
|
239
|
+
border-top: 1px solid #c2c6d6; transition: all .15s; }
|
|
240
|
+
.th-collapse:hover { background: #edf0f4; color: #5a6072; }
|
|
241
|
+
.th-typebar { font-size: 10px; color: #727785; padding: 4px 12px;
|
|
242
|
+
background: #f8f9fb; border-bottom: 1px solid #c2c6d6; }
|
|
243
|
+
|
|
244
|
+
/* Roadmap version cards (old format fallback) */
|
|
245
|
+
.rp-card { border: 1.5px solid #c2c6d6; border-radius: 10px; margin-bottom: 10px;
|
|
246
|
+
overflow: hidden; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.06);
|
|
247
|
+
transition: box-shadow .2s, border-color .3s; }
|
|
248
|
+
.rp-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.08); }
|
|
249
|
+
.rp-card.rp-done { background: #f0fdf6; border-color: #b7e4cf; }
|
|
250
|
+
.rp-card.rp-mixed { background: #fffef7; border-color: #f0d000; }
|
|
251
|
+
.rp-head { padding: 10px 14px; cursor: pointer; display: flex;
|
|
252
|
+
justify-content: space-between; align-items: center; gap: 8px;
|
|
253
|
+
user-select: none; transition: background .15s; border-bottom: 1px solid transparent; }
|
|
254
|
+
.rp-card.open .rp-head { border-bottom-color: #c2c6d6; }
|
|
255
|
+
.rp-head:hover { background: rgba(0,0,0,.02); }
|
|
256
|
+
.rp-head .rp-title { font-weight: 600; font-size: 12.5px; flex: 1; min-width: 0; }
|
|
257
|
+
.rp-body { display: none; }
|
|
258
|
+
.rp-card.open .rp-body { display: block; }
|
|
259
|
+
.rp-card.semi .rp-body { display: block; }
|
|
260
|
+
.rp-child { padding: 7px 14px; border-bottom: 1px dotted #eef0f4; font-size: 11px;
|
|
261
|
+
display: flex; align-items: center; gap: 8px; transition: background .1s; }
|
|
262
|
+
.rp-child:hover { background: rgba(0,0,0,.015); }
|
|
263
|
+
.rp-child:last-child { border-bottom: none; }
|
|
264
|
+
.rp-more { cursor: pointer; padding: 8px 12px; text-align: center; font-size: 11px;
|
|
265
|
+
color: #0984e3; font-weight: 500; border-top: 1px dashed #c2c6d6; transition: background .15s; }
|
|
266
|
+
.rp-more:hover { background: #edf0f4; color: #0066cc; }
|
|
267
|
+
|
|
268
|
+
/* Tree cards */
|
|
269
|
+
.tree-card { border: 1px solid #c2c6d6; border-radius: 8px; margin-bottom: 8px;
|
|
270
|
+
overflow: hidden; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.06);
|
|
271
|
+
transition: box-shadow .2s, transform .15s, border-color .3s; cursor: pointer; }
|
|
272
|
+
.tree-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.08); transform: translateY(-1px); }
|
|
273
|
+
.tree-card.filter-match { border-color: #4a90d9 !important;
|
|
274
|
+
box-shadow: 0 0 0 3px rgba(74,144,217,.2) !important; background: #fff !important; }
|
|
275
|
+
.tc-head { padding: 10px 14px; font-size: 12px; font-weight: 600;
|
|
276
|
+
display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
|
277
|
+
.tc-head > span:first-child { flex: 1; min-width: 0; overflow: hidden;
|
|
278
|
+
text-overflow: ellipsis; white-space: nowrap; }
|
|
279
|
+
.tc-meta { display: flex; align-items: center; gap: 6px; font-size: 9.5px;
|
|
280
|
+
flex-shrink: 0; color: #727785; }
|
|
281
|
+
.tc-time { font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; font-size: 9.5px; color: #727785; }
|
|
282
|
+
.tc-summary { padding: 4px 12px 8px; font-size: 11px; color: #5a6072; line-height: 1.5; }
|
|
283
|
+
.tc-tags { padding: 2px 12px 6px; }
|
|
284
|
+
.tc-src { padding: 2px 12px 6px; }
|
|
285
|
+
.tc-src a { font-size: 9.5px; background: #e8f1fd; padding: 2px 7px; border-radius: 4px;
|
|
286
|
+
color: #0984e3; cursor: pointer; text-decoration: none; transition: all .15s; }
|
|
287
|
+
.tc-src a:hover { background: #0984e3; color: #fff; }
|
|
288
|
+
.tc-type { font-size: 9px; padding: 1px 6px; border-radius: 4px; font-weight: 500; }
|
|
289
|
+
.tc-type.status-done { background: #e6f9f1; color: #00855a; }
|
|
290
|
+
.tc-type.status-progress { background: #fff8e6; color: #b06d00; }
|
|
291
|
+
.tc-type.status-pending { background: #f1f2f5; color: #727785; }
|
|
292
|
+
.tree-card.secondary { border-left: 3px solid #6c5ce7; }
|
|
293
|
+
|
|
294
|
+
/* Tag bar */
|
|
295
|
+
#tag-bar { padding: 8px 0; margin-bottom: 10px; border-bottom: 1px solid #c2c6d6; }
|
|
296
|
+
.tag-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; padding: 2px 0; }
|
|
297
|
+
.tag-row .tag-cat { font-size: 9px; color: #727785; font-weight: 600; white-space: nowrap;
|
|
298
|
+
padding: 2px 6px 2px 0; min-width: 32px; text-align: right; }
|
|
299
|
+
.tag-row .tag-btn { font-size: 10px; padding: 2px 8px; border-radius: 10px;
|
|
300
|
+
border: 1px solid #e0e3e8; background: #fff; cursor: pointer; color: #5a6072;
|
|
301
|
+
transition: all .15s; white-space: nowrap; font-weight: 500; }
|
|
302
|
+
.tag-row .tag-btn em { font-style: normal; font-size: 8px; color: #727785; margin-left: 2px; }
|
|
303
|
+
.tag-row .tag-btn:hover { border-color: #10B981; color: #10B981; background: #f0fdf9; }
|
|
304
|
+
.tag-row .tag-btn.on { background: #10B981; color: #fff; border-color: #10B981; font-weight: 600; }
|
|
305
|
+
.tag-row .tag-btn.on em { color: rgba(255,255,255,.7); }
|
|
306
|
+
.tag-row .tag-more { font-size: 9px; color: #0984e3; padding: 2px 4px; cursor: pointer; text-decoration: underline; }
|
|
307
|
+
.tag-row .tag-more:hover { color: #0066cc; }
|
|
308
|
+
|
|
309
|
+
/* Category header */
|
|
310
|
+
.cat-header { font-size: 11px; font-weight: 600; padding: 5px 10px; border-radius: 5px;
|
|
311
|
+
margin-bottom: 8px; display: flex; justify-content: space-between; letter-spacing: .2px; }
|
|
312
|
+
.cat-header.primary { background: linear-gradient(135deg,#e6f9f1,#d4f5e8); color: #00855a; }
|
|
313
|
+
.cat-header.secondary { background: #f1f2f5; color: #727785; }
|
|
314
|
+
.cat-section { margin-bottom: 16px; }
|
|
315
|
+
|
|
316
|
+
/* Tag chip */
|
|
317
|
+
.tag { display: inline-block; background: rgba(0,0,0,.05); padding: 2px 7px; border-radius: 4px;
|
|
318
|
+
font-size: 9.5px; color: #5a6072; margin-right: 4px; margin-bottom: 2px; font-weight: 500; }
|
|
319
|
+
|
|
320
|
+
/* Empty state */
|
|
321
|
+
.empty { text-align: center; padding: 50px 20px; color: #727785; }
|
|
322
|
+
|
|
323
|
+
/* Filter bar (inside toolbar area) */
|
|
324
|
+
#toolbar .fclear { display: none; cursor: pointer; background: #0984e3; color: #fff;
|
|
325
|
+
border: none; padding: 3px 10px; border-radius: 4px; font-size: 10px; font-weight: 600; }
|
|
326
|
+
#toolbar .fclear.show { display: inline-block; }
|
|
327
|
+
#toolbar .fclear:hover { opacity: .85; }
|
|
328
|
+
#toolbar .sort-btn.on { background: #10B981; color: #fff; border-color: #10B981; }
|
|
329
|
+
#toolbar .ftext { font-size: 10px; color: #0984e3; font-weight: 500;
|
|
330
|
+
max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
331
|
+
|
|
332
|
+
.rp-head .rp-meta { display: flex; align-items: center; gap: 6px; font-size: 10px; color: #727785; }
|
|
333
|
+
.rp-head .rp-ratio { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 10px; }
|
|
334
|
+
.rp-head .rp-ratio.all-done { background: #d4f5e8; color: #00855a; }
|
|
335
|
+
.rp-head .rp-ratio.partial { background: #fff3d4; color: #b06d00; }
|
|
336
|
+
.rp-head .rp-arrow { font-size: 10px; color: #727785; transition: transform .2s; }
|
|
337
|
+
.rp-card.open .rp-arrow { transform: rotate(90deg); }
|
|
338
|
+
.rp-child .rp-cicon { font-size: 11px; flex-shrink: 0; }
|
|
339
|
+
.rp-child .rp-cname { flex: 1; font-weight: 500; color: #191c1e; }
|
|
340
|
+
.rp-child .rp-ctime { font-size: 9.5px; color: #727785;
|
|
341
|
+
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace; white-space: nowrap; }
|
|
342
|
+
.rp-child .rp-clabel { font-size: 9px; padding: 1px 6px; border-radius: 3px; font-weight: 500; white-space: nowrap; }
|
|
343
|
+
.rp-child .rp-clabel.done { background: #e6f9f1; color: #00855a; }
|
|
344
|
+
.rp-child .rp-clabel.progress { background: #fff8e6; color: #b06d00; }
|
|
345
|
+
.rp-child .rp-clabel.pending-label { background: #f1f2f5; color: #727785; }
|
|
346
|
+
|
|
347
|
+
/* Responsive stacking */
|
|
348
|
+
@media (max-width: 768px) {
|
|
349
|
+
#main { flex-direction: column; }
|
|
350
|
+
#left { width: 100%; border-right: none; border-bottom: 1px solid #c2c6d6; flex: none; max-height: 50vh; }
|
|
351
|
+
#right { width: 100%; flex: 1; }
|
|
352
|
+
}
|
|
271
353
|
</style>
|
|
272
354
|
</head>
|
|
273
|
-
<body>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
<div class="
|
|
278
|
-
</
|
|
279
|
-
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
<button class="on" data-c="
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
355
|
+
<body class="bg-surface text-on-surface font-body-md h-screen overflow-hidden flex flex-col relative">
|
|
356
|
+
|
|
357
|
+
<!-- TopNavBar -->
|
|
358
|
+
<nav class="bg-surface border-b border-outline-variant flex justify-between items-center w-full px-lg h-16 shrink-0 relative z-10">
|
|
359
|
+
<div class="flex items-center gap-xl h-full">
|
|
360
|
+
<div class="font-headline-md text-headline-md font-extrabold text-primary shrink-0">Sandtable IDE</div>
|
|
361
|
+
<div class="flex overflow-x-auto hide-scrollbar h-full items-end" id="nav-tabs">
|
|
362
|
+
<button class="px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale" data-c="all">All</button>
|
|
363
|
+
<button class="px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale" data-c="roadmap">Roadmap</button>
|
|
364
|
+
<button class="px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale" data-c="decision">Decisions</button>
|
|
365
|
+
<button class="px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale" data-c="spec">Specs</button>
|
|
366
|
+
<button class="px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale" data-c="convention">Conventions</button>
|
|
367
|
+
<button class="px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale" data-c="ops">Ops</button>
|
|
368
|
+
<button class="px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale" data-c="archive">Archive</button>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
<div class="flex items-center gap-md shrink-0">
|
|
372
|
+
<span class="font-label-caps text-label-caps text-on-surface-variant relative" id="token-today">
|
|
373
|
+
Usage: --<span id="token-tip"></span>
|
|
374
|
+
</span>
|
|
375
|
+
<span class="w-2 h-2 rounded-full bg-emerald-500 shadow-[0_0_6px_rgba(16,185,129,.4)] animate-pulse"></span>
|
|
376
|
+
</div>
|
|
377
|
+
</nav>
|
|
378
|
+
|
|
379
|
+
<!-- Briefing Bar -->
|
|
380
|
+
<div class="bg-surface-container-low border-b border-outline-variant px-lg py-sm flex items-center shrink-0 relative z-10">
|
|
381
|
+
<div class="flex items-center gap-sm">
|
|
382
|
+
<span class="material-symbols-outlined text-on-surface-variant text-[18px]">info</span>
|
|
383
|
+
<span class="font-code-md text-code-md text-on-surface-variant" id="btext">Loading...</span>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<!-- Main Split -->
|
|
388
|
+
<main class="flex-1 flex overflow-hidden relative z-10" id="main">
|
|
389
|
+
<!-- Left Column (40%) -->
|
|
390
|
+
<section class="w-[40%] max-md:w-full flex flex-col border-r border-outline-variant bg-surface-container-lowest overflow-y-auto" id="left">
|
|
391
|
+
<!-- Track Switcher (hidden when no tracks) -->
|
|
392
|
+
<div class="px-lg py-md flex gap-sm bg-surface-bright border-b border-outline-variant shrink-0 hidden" id="track-switcher">
|
|
393
|
+
<!-- dynamically filled -->
|
|
394
|
+
</div>
|
|
395
|
+
<!-- Category Tabs (secondary nav inside left panel) -->
|
|
396
|
+
<div class="px-lg py-md flex gap-sm bg-surface-bright border-b border-outline-variant shrink-0 hidden" id="tree-tabs-inline">
|
|
397
|
+
<!-- dynamically filled -->
|
|
398
|
+
</div>
|
|
399
|
+
<!-- Tree Content -->
|
|
400
|
+
<div class="flex-1 overflow-y-auto p-lg" id="tree">
|
|
401
|
+
<div class="empty"><h3 class="font-headline-sm text-headline-sm mb-sm">Loading...</h3><p class="font-body-md text-body-md text-on-surface-variant">Fetching data...</p></div>
|
|
295
402
|
</div>
|
|
296
|
-
<div id="tree"></div>
|
|
297
403
|
</section>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
404
|
+
|
|
405
|
+
<!-- Right Column (60%) -->
|
|
406
|
+
<section class="w-[60%] max-md:w-full flex flex-col bg-surface-bright overflow-y-auto" id="right">
|
|
407
|
+
<!-- Filter Bar -->
|
|
408
|
+
<div class="h-14 border-b border-outline-variant flex items-center justify-between px-lg bg-surface-container-lowest shrink-0">
|
|
409
|
+
<div class="flex items-center gap-md">
|
|
410
|
+
<span class="material-symbols-outlined text-on-surface-variant">filter_list</span>
|
|
411
|
+
<span class="font-title-md text-title-md text-on-surface">Event Stream</span>
|
|
412
|
+
<span class="font-code-sm text-code-sm text-on-surface-variant" id="evcnt"></span>
|
|
413
|
+
</div>
|
|
414
|
+
<div class="flex gap-sm" id="toolbar">
|
|
415
|
+
<span class="font-code-sm text-code-sm text-on-surface-variant flex items-center gap-xs">
|
|
416
|
+
<span class="material-symbols-outlined text-[16px]">sort</span>
|
|
417
|
+
</span>
|
|
418
|
+
<button class="text-on-surface-variant hover:text-on-surface font-code-sm text-code-sm border border-outline-variant px-sm py-xs rounded hover:bg-surface-container-low transition-colors hover-scale sort-btn on" data-mode="grouped">By Category</button>
|
|
419
|
+
<button class="text-on-surface-variant hover:text-on-surface font-code-sm text-code-sm border border-outline-variant px-sm py-xs rounded hover:bg-surface-container-low transition-colors hover-scale sort-btn" data-mode="track">By Track</button>
|
|
420
|
+
<button class="text-on-surface-variant hover:text-on-surface font-code-sm text-code-sm border border-outline-variant px-sm py-xs rounded hover:bg-surface-container-low transition-colors hover-scale sort-btn" data-mode="flat">By Time</button>
|
|
421
|
+
<span class="ftext"></span>
|
|
422
|
+
<button class="fclear">Clear</button>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
<!-- Tag Bar -->
|
|
426
|
+
<div class="px-lg" id="tag-bar"></div>
|
|
427
|
+
<!-- Events Content -->
|
|
428
|
+
<div class="flex-1 overflow-y-auto px-lg pb-lg" id="events"></div>
|
|
303
429
|
</section>
|
|
304
|
-
</
|
|
430
|
+
</main>
|
|
305
431
|
|
|
432
|
+
<!-- Floating Preview Panel -->
|
|
306
433
|
<div id="overlay" class="hide"></div>
|
|
307
434
|
<div id="float" class="hide">
|
|
308
|
-
<div id="float-head"><span id="ftitle"
|
|
309
|
-
<div id="float-body"
|
|
435
|
+
<div id="float-head"><span id="ftitle">Preview</span><button id="float-close">×</button></div>
|
|
436
|
+
<div id="float-body">Loading...</div>
|
|
310
437
|
</div>
|
|
311
438
|
|
|
312
439
|
<script>
|
|
313
440
|
// ====== DATA ======
|
|
314
441
|
var T = null; // timeline data
|
|
442
|
+
var R = null; // roadmap data (tracks + phases)
|
|
315
443
|
var activeCat = 'all';
|
|
316
|
-
var
|
|
317
|
-
var
|
|
318
|
-
var
|
|
319
|
-
var
|
|
320
|
-
var
|
|
444
|
+
var activeTrack = null; // current track filter (null = All)
|
|
445
|
+
var activeDocFilter = null;
|
|
446
|
+
var activeTagFilter = null;
|
|
447
|
+
var expandedThreads = {};
|
|
448
|
+
var expandedRpCards = {};
|
|
449
|
+
var expandedMilestones = {}; // track milestone accordion state
|
|
450
|
+
var sortMode = 'grouped'; // 'grouped' | 'track' | 'flat'
|
|
321
451
|
|
|
322
452
|
var TAG_CATS = {
|
|
323
|
-
"模块":["CLI","dashboard","builder","server","skill","npm","扫描器"],
|
|
324
|
-
"主题":["事件流","token","UX","安装方案","安全","编码","配置","IDE适配","双视图","设计原则","libero"],
|
|
325
|
-
"阶段":["v0.3","MVP","里程碑","集成","方向决策"],
|
|
326
|
-
"动作":["架构","设计","实现","调研","文档","流程","教训","命令","IDE","init","uninstall","summarize","scan"]
|
|
453
|
+
"模块": ["CLI","dashboard","builder","server","skill","npm","扫描器"],
|
|
454
|
+
"主题": ["事件流","token","UX","安装方案","安全","编码","配置","IDE适配","双视图","设计原则","libero"],
|
|
455
|
+
"阶段": ["v0.3","MVP","里程碑","集成","方向决策"],
|
|
456
|
+
"动作": ["架构","设计","实现","调研","文档","流程","教训","命令","IDE","init","uninstall","summarize","scan"]
|
|
327
457
|
};
|
|
328
|
-
|
|
329
|
-
function evHasTag(ev,tag){return ev.tags&&ev.tags.indexOf(tag)!==-1}
|
|
330
|
-
function setTagFilter(tag){if(activeTagFilter===tag){activeTagFilter=null}else{activeTagFilter=tag};renderTagBar();renderEvents()}
|
|
331
|
-
function renderTagBar(){
|
|
332
|
-
var bar=document.getElementById("tag-bar");
|
|
333
|
-
if(!T||!T.events){bar.innerHTML="";return}
|
|
334
|
-
var allEvs=T.events||[],tagCount={};
|
|
335
|
-
for(var i=0;i<allEvs.length;i++){var tags=allEvs[i].tags||[];for(var j=0;j<tags.length;j++)tagCount[tags[j]]=(tagCount[tags[j]]||0)+1}
|
|
336
|
-
var catTags={};
|
|
337
|
-
for(var tag in tagCount){var c=catOfTag(tag);if(!catTags[c])catTags[c]=[];catTags[c].push({tag:tag,count:tagCount[tag]})}
|
|
338
|
-
var catOrder=["模块","主题","阶段","动作","其他"];
|
|
339
|
-
var h="";
|
|
340
|
-
for(var ci=0;ci<catOrder.length;ci++){
|
|
341
|
-
var cn=catOrder[ci],items=catTags[cn];
|
|
342
|
-
if(!items||!items.length)continue;
|
|
343
|
-
items.sort(function(a,b){return b.count-a.count});
|
|
344
|
-
var expand=!!expandedTagCats[cn];
|
|
345
|
-
var limit=expand?items.length:Math.min(3,items.length);
|
|
346
|
-
h+="<div class=\"tag-row\"><span class=\"tag-cat\">"+cn+"</span>";
|
|
347
|
-
for(var ii=0;ii<limit;ii++){
|
|
348
|
-
var item=items[ii],isOn=activeTagFilter===item.tag;
|
|
349
|
-
h+="<span class=\"tag-btn"+(isOn?" on":"")+"\" data-tag=\""+esc(item.tag)+"\" onclick=\"setTagFilter('"+esc(item.tag)+"');event.stopPropagation()\">"+esc(item.tag)+" <em>"+item.count+"</em></span>";
|
|
350
|
-
}
|
|
351
|
-
if(items.length>3&&!expand)h+="<span class=\"tag-more\" onclick=\"toggleTagCat('"+esc(cn)+"');event.stopPropagation()\">+"+(items.length-3)+"</span>";
|
|
352
|
-
if(expand)h+="<span class=\"tag-more\" onclick=\"toggleTagCat('"+esc(cn)+"');event.stopPropagation()\">收起</span>";
|
|
353
|
-
h+="</div>";
|
|
354
|
-
}
|
|
355
|
-
bar.innerHTML=h;
|
|
356
|
-
}
|
|
458
|
+
var ETYPES = {'对齐与拍板':['ev-t1','t1'],'规格演进':['ev-t2','t2'],'代码变更':['ev-t3','t3'],'测试与质量':['ev-t4','t4'],'审批与交接':['ev-t5','t5'],'运维与基建':['ev-t6','t6'],'教训沉淀':['ev-t7','t7']};
|
|
357
459
|
|
|
358
|
-
var expandedTagCats = {};
|
|
359
|
-
function toggleTagCat(cn){
|
|
360
|
-
if(expandedTagCats[cn]){expandedTagCats[cn]=false}else{expandedTagCats[cn]=true}
|
|
361
|
-
renderTagBar();
|
|
362
|
-
}
|
|
363
460
|
var CLABEL = {roadmap:'路线图与进度',todo:'待办清单',decision:'决策记录',spec:'业务规格',convention:'协作纪律',ops:'运维与基建',archive:'历史档案',template:'工具模板',unknown:'未分类'};
|
|
364
461
|
var PRIMARY = {roadmap:1,decision:1,todo:1};
|
|
365
462
|
var ETYPE = {phase:'roadmap',milestone:'roadmap',task:'roadmap',subtask:'roadmap',roadmap:'roadmap',backlog:'todo',todo:'todo',conclusion:'roadmap',decision:'decision',refactor:'decision',spec:'spec',intent:'spec',prompt:'spec',convention:'convention',agent:'ops',runbook:'ops',optimization:'roadmap',journal:'archive',handover:'archive',plan_doc:'archive',template:'template'};
|
|
366
|
-
|
|
367
|
-
function
|
|
368
|
-
|
|
369
|
-
var
|
|
370
|
-
function
|
|
371
|
-
function
|
|
372
|
-
|
|
373
|
-
function
|
|
374
|
-
var
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
var
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
var names=vs.inProgress.slice(0,3).join('、');
|
|
400
|
-
parts.push(v+' '+vs.done+'/'+vs.total+' 完成'+(names?','+names+'等推进中':''));
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return parts.join(';')+'。'||'暂无简报';
|
|
463
|
+
|
|
464
|
+
function normCat(c) { if(!c) return 'unknown'; var m = {conventions:'convention',specs:'spec',decisions:'decision',plans:'roadmap',journals:'archive',journal:'archive'}; return m[c]||c; }
|
|
465
|
+
function normTg(tg) { if(!tg||tg==='archived') return 'past'; return tg; }
|
|
466
|
+
function esc(s) { if(!s) return ''; var d = document.createElement('div'); d.textContent = String(s); return d.innerHTML; }
|
|
467
|
+
function catOf(el) { return normCat(el.category || ETYPE[el.elementType] || "unknown"); }
|
|
468
|
+
function catOfTag(tag) { for(var c in TAG_CATS) { if(TAG_CATS[c].indexOf(tag)!==-1) return c; } return "其他"; }
|
|
469
|
+
function evHasTag(ev, tag) { return ev.tags && ev.tags.indexOf(tag)!==-1; }
|
|
470
|
+
function extractVersion(name) {
|
|
471
|
+
var m = name.match(/\(v\d+\.\d+\)/);
|
|
472
|
+
if(m) return m[0].replace(/[()]/g,"");
|
|
473
|
+
m = name.match(/\(([A-D])\)/);
|
|
474
|
+
if(m) return "v0.3";
|
|
475
|
+
return "其他";
|
|
476
|
+
}
|
|
477
|
+
function versionOrder(v) {
|
|
478
|
+
if(v==="v0.1") return 1;
|
|
479
|
+
if(v==="v0.2") return 2;
|
|
480
|
+
if(v==="v0.3") return 3;
|
|
481
|
+
return 99;
|
|
482
|
+
}
|
|
483
|
+
function fmtTime(ts) {
|
|
484
|
+
if(!ts) return "";
|
|
485
|
+
var d = new Date(ts);
|
|
486
|
+
var mm = String(d.getMonth()+1).padStart(2,'0');
|
|
487
|
+
var dd = String(d.getDate()).padStart(2,'0');
|
|
488
|
+
var hh = String(d.getHours()).padStart(2,'0');
|
|
489
|
+
var mi = String(d.getMinutes()).padStart(2,'0');
|
|
490
|
+
return mm+'-'+dd+' '+hh+':'+mi;
|
|
491
|
+
}
|
|
492
|
+
function hasChildCat(el, cat) {
|
|
493
|
+
if(catOf(el)===cat) return true;
|
|
494
|
+
if(el.children) for(var i=0;i<el.children.length;i++) { if(hasChildCat(el.children[i],cat)) return true; }
|
|
495
|
+
return false;
|
|
404
496
|
}
|
|
405
497
|
|
|
406
498
|
// ====== LOAD ======
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
499
|
+
Promise.all([
|
|
500
|
+
fetch('../data/timeline.json').then(function(r){ return r.json(); }),
|
|
501
|
+
fetch('../data/roadmap.json').then(function(r){ return r.json(); }).catch(function(){ return null; })
|
|
502
|
+
]).then(function(results) {
|
|
503
|
+
T = results[0];
|
|
504
|
+
R = results[1];
|
|
505
|
+
document.getElementById('btext').textContent = generateBrief();
|
|
506
|
+
renderAll();
|
|
507
|
+
setupNavTabs();
|
|
414
508
|
setupToolbar();
|
|
415
509
|
loadTokenToday();
|
|
416
|
-
}).catch(function(e){
|
|
417
|
-
document.getElementById('btext').textContent = '
|
|
510
|
+
}).catch(function(e) {
|
|
511
|
+
document.getElementById('btext').textContent = 'Load failed: ' + e.message;
|
|
418
512
|
console.error(e);
|
|
419
513
|
});
|
|
420
514
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
for(var j=0;j<btns.length;j++)btns[j].classList.remove("on");
|
|
427
|
-
this.classList.add("on");
|
|
428
|
-
activeCat = this.getAttribute("data-c");
|
|
429
|
-
renderTree();
|
|
430
|
-
});
|
|
431
|
-
}
|
|
515
|
+
function renderAll() {
|
|
516
|
+
renderTrackSwitcher();
|
|
517
|
+
renderInlineTabs();
|
|
518
|
+
renderTree();
|
|
519
|
+
renderEvents();
|
|
432
520
|
}
|
|
433
521
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
522
|
+
function generateBrief() {
|
|
523
|
+
// Use roadmap.json brief if available
|
|
524
|
+
if(R && R.brief) return R.brief;
|
|
525
|
+
// Fallback: generate from timeline elements
|
|
526
|
+
if(!T || !T.elements) return 'Loading...';
|
|
527
|
+
var els = T.elements||[];
|
|
528
|
+
var allMilestones = [];
|
|
529
|
+
for(var i=0;i<els.length;i++) {
|
|
530
|
+
var children = els[i].children||[];
|
|
531
|
+
for(var j=0;j<children.length;j++) {
|
|
532
|
+
if(children[j].elementType==="milestone") allMilestones.push(children[j]);
|
|
533
|
+
}
|
|
444
534
|
}
|
|
445
|
-
var
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
document.body.classList.remove("filter-active");
|
|
453
|
-
renderTree();
|
|
454
|
-
renderTagBar();
|
|
455
|
-
renderEvents();
|
|
456
|
-
});
|
|
535
|
+
var verStats = {};
|
|
536
|
+
for(var i=0;i<allMilestones.length;i++) {
|
|
537
|
+
var ch=allMilestones[i], ver=extractVersion(ch.name);
|
|
538
|
+
if(!verStats[ver]) verStats[ver]={total:0,done:0,inProgress:[]};
|
|
539
|
+
verStats[ver].total++;
|
|
540
|
+
if(ch.status==="completed"||ch.status==="cancelled") verStats[ver].done++;
|
|
541
|
+
else verStats[ver].inProgress.push(ch.name.replace(/\s*\([^)]*\)\s*/g,'').replace(/^M\d+:\s*/,''));
|
|
457
542
|
}
|
|
543
|
+
var versions = Object.keys(verStats).sort(function(a,b){ return versionOrder(b)-versionOrder(a); });
|
|
544
|
+
var parts = [];
|
|
545
|
+
for(var i=0;i<versions.length;i++) {
|
|
546
|
+
var v=versions[i], vs=verStats[v];
|
|
547
|
+
if(vs.done===vs.total) parts.push(v+' '+vs.done+'/'+vs.total+' done');
|
|
548
|
+
else { var names=vs.inProgress.slice(0,3).join(', '); parts.push(v+' '+vs.done+'/'+vs.total+(names?' ('+names+')':'')); }
|
|
549
|
+
}
|
|
550
|
+
var brief = parts.join(' | ') || 'No data';
|
|
551
|
+
if(R && R.currentNode) brief += ' | Current: '+R.currentNode;
|
|
552
|
+
return brief;
|
|
458
553
|
}
|
|
459
554
|
|
|
460
|
-
// ======
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if(
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
var
|
|
488
|
-
var
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
555
|
+
// ====== TRACK SWITCHER ======
|
|
556
|
+
function renderTrackSwitcher() {
|
|
557
|
+
var container = document.getElementById('track-switcher');
|
|
558
|
+
var tracks = (R && R.tracks) ? R.tracks : [];
|
|
559
|
+
if(!tracks.length) { container.classList.add('hidden'); return; }
|
|
560
|
+
container.classList.remove('hidden');
|
|
561
|
+
|
|
562
|
+
var h = '';
|
|
563
|
+
// "All" button
|
|
564
|
+
h += '<button class="px-md py-sm rounded font-title-md text-title-md hover-scale ' +
|
|
565
|
+
(activeTrack===null
|
|
566
|
+
? 'bg-primary-container text-on-primary-container'
|
|
567
|
+
: 'border border-outline-variant text-on-surface-variant hover:bg-surface-container-low') +
|
|
568
|
+
'" data-track="">All</button>';
|
|
569
|
+
// Track buttons
|
|
570
|
+
for(var i=0;i<tracks.length;i++) {
|
|
571
|
+
var t = tracks[i];
|
|
572
|
+
var isActive = activeTrack===t.id;
|
|
573
|
+
h += '<button class="px-md py-sm rounded font-title-md text-title-md hover-scale ' +
|
|
574
|
+
(isActive
|
|
575
|
+
? 'bg-primary-container text-on-primary-container'
|
|
576
|
+
: 'border border-outline-variant text-on-surface-variant hover:bg-surface-container-low') +
|
|
577
|
+
'" data-track="'+esc(t.id)+'">'+esc(t.label||t.id)+'</button>';
|
|
578
|
+
}
|
|
579
|
+
container.innerHTML = h;
|
|
580
|
+
|
|
581
|
+
// Bind clicks
|
|
582
|
+
var btns = container.querySelectorAll('button[data-track]');
|
|
583
|
+
for(var i=0;i<btns.length;i++) {
|
|
584
|
+
btns[i].addEventListener('click', function() {
|
|
585
|
+
var tid = this.getAttribute('data-track');
|
|
586
|
+
activeTrack = tid || null;
|
|
587
|
+
renderAll();
|
|
588
|
+
});
|
|
589
|
+
}
|
|
492
590
|
}
|
|
493
591
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
592
|
+
// ====== INLINE CATEGORY TABS ======
|
|
593
|
+
function renderInlineTabs() {
|
|
594
|
+
var container = document.getElementById('tree-tabs-inline');
|
|
595
|
+
var categories = ['all','roadmap','todo','decision','spec','convention','ops','archive'];
|
|
596
|
+
var labels = {all:'All',roadmap:'Roadmap',todo:'Todo',decision:'Decisions',spec:'Specs',convention:'Conventions',ops:'Ops',archive:'Archive'};
|
|
597
|
+
container.classList.remove('hidden');
|
|
598
|
+
var h = '';
|
|
599
|
+
for(var i=0;i<categories.length;i++) {
|
|
600
|
+
var c = categories[i];
|
|
601
|
+
var isOn = activeCat===c;
|
|
602
|
+
h += '<button class="px-md py-sm rounded font-title-md text-title-md hover-scale ' +
|
|
603
|
+
(isOn
|
|
604
|
+
? 'bg-primary-container text-on-primary-container'
|
|
605
|
+
: 'border border-outline-variant text-on-surface-variant hover:bg-surface-container-low') +
|
|
606
|
+
'" data-c="'+c+'">'+labels[c]+'</button>';
|
|
500
607
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
608
|
+
container.innerHTML = h;
|
|
609
|
+
|
|
610
|
+
// Bind clicks
|
|
611
|
+
var btns = container.querySelectorAll('button[data-c]');
|
|
612
|
+
for(var i=0;i<btns.length;i++) {
|
|
613
|
+
btns[i].addEventListener('click', function() {
|
|
614
|
+
activeCat = this.getAttribute('data-c');
|
|
615
|
+
renderAll();
|
|
616
|
+
});
|
|
505
617
|
}
|
|
506
|
-
return ver;
|
|
507
618
|
}
|
|
508
619
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
620
|
+
// ====== NAV TABS (top bar — mirror and sync) ======
|
|
621
|
+
function setupNavTabs() {
|
|
622
|
+
var btns = document.querySelectorAll('#nav-tabs button[data-c]');
|
|
623
|
+
for(var i=0;i<btns.length;i++) {
|
|
624
|
+
btns[i].addEventListener('click', function() {
|
|
625
|
+
activeCat = this.getAttribute('data-c');
|
|
626
|
+
renderAll();
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
syncNavTabs();
|
|
630
|
+
}
|
|
631
|
+
function syncNavTabs() {
|
|
632
|
+
var navBtns = document.querySelectorAll('#nav-tabs button[data-c]');
|
|
633
|
+
for(var i=0;i<navBtns.length;i++) {
|
|
634
|
+
var btn = navBtns[i];
|
|
635
|
+
if(btn.getAttribute('data-c')===activeCat) {
|
|
636
|
+
btn.className = 'px-md pb-md pt-sm font-title-md text-title-md text-primary border-b-2 border-primary whitespace-nowrap hover-scale';
|
|
637
|
+
} else {
|
|
638
|
+
btn.className = 'px-md pb-md pt-sm font-title-md text-title-md text-on-surface-variant whitespace-nowrap hover:text-on-surface border-b-2 border-transparent hover-scale';
|
|
517
639
|
}
|
|
518
640
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
641
|
+
// Also refresh inline tabs
|
|
642
|
+
var inlineBtns = document.querySelectorAll('#tree-tabs-inline button[data-c]');
|
|
643
|
+
for(var i=0;i<inlineBtns.length;i++) {
|
|
644
|
+
var ib = inlineBtns[i];
|
|
645
|
+
if(ib.getAttribute('data-c')===activeCat) {
|
|
646
|
+
ib.className = 'px-md py-sm rounded font-title-md text-title-md hover-scale bg-primary-container text-on-primary-container';
|
|
647
|
+
} else {
|
|
648
|
+
ib.className = 'px-md py-sm rounded font-title-md text-title-md hover-scale border border-outline-variant text-on-surface-variant hover:bg-surface-container-low';
|
|
523
649
|
}
|
|
524
650
|
}
|
|
525
|
-
return fmtTime(latest);
|
|
526
651
|
}
|
|
527
652
|
|
|
528
|
-
|
|
653
|
+
// ====== TREE ======
|
|
654
|
+
function renderTree() {
|
|
655
|
+
syncNavTabs();
|
|
529
656
|
var els = T.elements||[];
|
|
530
|
-
var roadmapEls=[], otherEls=[];
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
657
|
+
var roadmapEls = [], otherEls = [];
|
|
658
|
+
|
|
659
|
+
// Check if we have tracks data from roadmap.json
|
|
660
|
+
var hasTracks = R && R.tracks && R.tracks.length > 0;
|
|
661
|
+
|
|
662
|
+
for(var i=0;i<els.length;i++) {
|
|
663
|
+
var el = els[i];
|
|
664
|
+
if(el.category==="roadmap" || el.elementType==="phase" || el.elementType==="milestone") {
|
|
534
665
|
roadmapEls.push(el);
|
|
535
|
-
}else{
|
|
536
|
-
if(activeCat!=="all" && catOf(el)!==activeCat && !hasChildCat(el,activeCat))continue;
|
|
666
|
+
} else {
|
|
667
|
+
if(activeCat!=="all" && catOf(el)!==activeCat && !hasChildCat(el,activeCat)) continue;
|
|
537
668
|
otherEls.push(el);
|
|
538
669
|
}
|
|
539
670
|
}
|
|
540
671
|
|
|
541
|
-
var total=otherEls.length;
|
|
542
|
-
var h=
|
|
543
|
-
|
|
544
|
-
// ===== Roadmap section
|
|
545
|
-
if((activeCat==="all" || activeCat==="roadmap")
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
672
|
+
var total = otherEls.length;
|
|
673
|
+
var h = '';
|
|
674
|
+
|
|
675
|
+
// ===== Roadmap section =====
|
|
676
|
+
if((activeCat==="all" || activeCat==="roadmap")) {
|
|
677
|
+
if(hasTracks) {
|
|
678
|
+
// NEW: Render from tracks data
|
|
679
|
+
h += renderTracks();
|
|
680
|
+
for(var ti=0;ti<R.tracks.length;ti++) {
|
|
681
|
+
var phases = R.tracks[ti].phases||[];
|
|
682
|
+
total += phases.length;
|
|
683
|
+
}
|
|
684
|
+
} else if(roadmapEls.length>0) {
|
|
685
|
+
// OLD: Render from timeline elements (fallback)
|
|
686
|
+
h += renderRoadmapLegacy(roadmapEls);
|
|
687
|
+
for(var ri=0;ri<roadmapEls.length;ri++) {
|
|
688
|
+
if(roadmapEls[ri].children) total += roadmapEls[ri].children.length;
|
|
689
|
+
else total += 1;
|
|
690
|
+
}
|
|
550
691
|
}
|
|
551
692
|
|
|
552
|
-
//
|
|
553
|
-
var unmatchedCount=countUnmatchedEvents();
|
|
554
|
-
var branchNode={
|
|
555
|
-
id:"virtual-branch-optimizations",
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
date:new Date().toISOString().substring(0,10)
|
|
693
|
+
// Branch optimizations catch-all
|
|
694
|
+
var unmatchedCount = countUnmatchedEvents();
|
|
695
|
+
var branchNode = {
|
|
696
|
+
id:"virtual-branch-optimizations", kind:"primary", elementType:"catch-all", category:"roadmap",
|
|
697
|
+
name:"Branch Optimizations", status:"in_progress", timeGroup:"current",
|
|
698
|
+
timeLabel:"Bug fixes / micro-tweaks / scattered opts",
|
|
699
|
+
source:{file:"__unmatched__",title:"Branch Optimizations"},
|
|
700
|
+
summary:"Catch-all node: collects all events not linked to a specific roadmap milestone",
|
|
701
|
+
tags:["catch-all"], children:[], order:999,
|
|
702
|
+
date: new Date().toISOString().substring(0,10)
|
|
563
703
|
};
|
|
564
|
-
h+='<div class="cat-section"><div class="cat-header primary"><span
|
|
565
|
-
h+=renderTreeCard(branchNode);
|
|
566
|
-
h+=
|
|
567
|
-
total+=1;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
total+=1;
|
|
704
|
+
h += '<div class="cat-section"><div class="cat-header primary"><span>Branch Optimizations</span><span>'+unmatchedCount+' events</span></div>';
|
|
705
|
+
h += renderTreeCard(branchNode);
|
|
706
|
+
h += '</div>';
|
|
707
|
+
total += 1;
|
|
708
|
+
|
|
709
|
+
// Todo section
|
|
710
|
+
if(activeCat==="all") {
|
|
711
|
+
var todoNode = {
|
|
712
|
+
id:"virtual-todo-list", kind:"primary", elementType:"todo", category:"todo",
|
|
713
|
+
name:"Todo List", status:"pending", timeGroup:"future",
|
|
714
|
+
timeLabel:"Discovered but unresolved optimization points",
|
|
715
|
+
source:{file:"__todo__",title:"Todo List"},
|
|
716
|
+
summary:"Records all discovered but unresolved optimization points and todo items",
|
|
717
|
+
tags:["todo"], children:[], order:998,
|
|
718
|
+
date: new Date().toISOString().substring(0,10)
|
|
719
|
+
};
|
|
720
|
+
h += '<div class="cat-section"><div class="cat-header primary"><span>Todo List</span><span>Pending</span></div>';
|
|
721
|
+
h += renderTreeCard(todoNode);
|
|
722
|
+
h += '</div>';
|
|
723
|
+
total += 1;
|
|
724
|
+
}
|
|
586
725
|
}
|
|
587
726
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
727
|
+
// ===== Other categories =====
|
|
728
|
+
if(activeCat==="all" || activeCat!=="roadmap") {
|
|
729
|
+
var groups = {}, order = [];
|
|
730
|
+
for(var i=0;i<otherEls.length;i++) {
|
|
731
|
+
var c = catOf(otherEls[i]);
|
|
732
|
+
if(!groups[c]) { groups[c]=[]; order.push(c); }
|
|
593
733
|
groups[c].push(otherEls[i]);
|
|
594
734
|
}
|
|
595
|
-
for(var k in groups){
|
|
596
|
-
groups[k].sort(function(a,b){
|
|
597
|
-
if(a.date&&b.date)return b.date.localeCompare(a.date);
|
|
598
|
-
return(b.order||0)-(a.order||0)
|
|
599
|
-
});
|
|
735
|
+
for(var k in groups) {
|
|
736
|
+
groups[k].sort(function(a,b) { if(a.date&&b.date) return b.date.localeCompare(a.date); return (b.order||0)-(a.order||0); });
|
|
600
737
|
}
|
|
601
|
-
order.sort(function(a,b){
|
|
602
|
-
var pa=PRIMARY[a]?0:1,pb=PRIMARY[b]?0:1;
|
|
603
|
-
if(pa!==pb)return pa-pb;
|
|
604
|
-
if(a==="unknown")return 1;if(b==="unknown")return -1;
|
|
738
|
+
order.sort(function(a,b) {
|
|
739
|
+
var pa=PRIMARY[a]?0:1, pb=PRIMARY[b]?0:1;
|
|
740
|
+
if(pa!==pb) return pa-pb;
|
|
741
|
+
if(a==="unknown") return 1; if(b==="unknown") return -1;
|
|
605
742
|
return 0;
|
|
606
743
|
});
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
h+=renderTreeCard(items[j]);
|
|
613
|
-
}
|
|
614
|
-
h+="</div>";
|
|
744
|
+
for(var i=0;i<order.length;i++) {
|
|
745
|
+
var g=order[i], items=groups[g], label=CLABEL[g]||g, isP=PRIMARY[g];
|
|
746
|
+
h += '<div class="cat-section"><div class="cat-header '+(isP?"primary":"secondary")+'"><span>'+esc(label)+'</span><span>'+items.length+' items</span></div>';
|
|
747
|
+
for(var j=0;j<items.length;j++) { h += renderTreeCard(items[j]); }
|
|
748
|
+
h += '</div>';
|
|
615
749
|
}
|
|
616
750
|
}
|
|
617
751
|
|
|
618
|
-
if(total===0
|
|
619
|
-
|
|
620
|
-
|
|
752
|
+
if(total===0 && h==='') {
|
|
753
|
+
document.getElementById('tree').innerHTML = '<div class="empty"><h3 class="font-headline-sm text-headline-sm mb-sm">No content</h3><p class="font-body-md text-body-md text-on-surface-variant">Select another category or run build to refresh</p></div>';
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
document.getElementById('tree').innerHTML = h;
|
|
621
757
|
bindDocClicks();
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
758
|
+
|
|
759
|
+
// Bind rp-more buttons for legacy roadmap
|
|
760
|
+
(function() {
|
|
761
|
+
var mores = document.querySelectorAll('.rp-more');
|
|
762
|
+
for(var i=0;i<mores.length;i++) {
|
|
763
|
+
mores[i].onclick = function(e) {
|
|
626
764
|
e.stopPropagation();
|
|
627
|
-
var card=this.parentNode.parentNode;
|
|
628
|
-
var ver=card.getAttribute(
|
|
629
|
-
if(ver)expandedRpCards[ver]=true;
|
|
765
|
+
var card = this.parentNode.parentNode;
|
|
766
|
+
var ver = card.getAttribute('data-ver');
|
|
767
|
+
if(ver) expandedRpCards[ver]=true;
|
|
630
768
|
renderTree();
|
|
631
769
|
};
|
|
632
770
|
}
|
|
633
771
|
})();
|
|
634
772
|
}
|
|
635
773
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
774
|
+
// ====== NEW: Render tracks from roadmap.json ======
|
|
775
|
+
function renderTracks() {
|
|
776
|
+
var tracks = R.tracks;
|
|
777
|
+
var h = '';
|
|
778
|
+
for(var ti=0;ti<tracks.length;ti++) {
|
|
779
|
+
var track = tracks[ti];
|
|
780
|
+
// Filter by activeTrack
|
|
781
|
+
if(activeTrack && track.id !== activeTrack) continue;
|
|
782
|
+
|
|
783
|
+
h += '<div class="mb-lg">';
|
|
784
|
+
h += '<h4 class="font-label-caps text-label-caps text-on-surface-variant mb-md tracking-wider">'+esc(track.label||track.id)+'</h4>';
|
|
785
|
+
|
|
786
|
+
var phases = track.phases||[];
|
|
787
|
+
for(var pi=0;pi<phases.length;pi++) {
|
|
788
|
+
h += renderTrackPhase(track, phases[pi]);
|
|
789
|
+
}
|
|
790
|
+
h += '</div>';
|
|
791
|
+
}
|
|
792
|
+
return h;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function renderTrackPhase(track, phase) {
|
|
796
|
+
var tasks = phase.tasks||[];
|
|
797
|
+
var completed = 0;
|
|
798
|
+
for(var i=0;i<tasks.length;i++) {
|
|
799
|
+
if(tasks[i].status==='completed') completed++;
|
|
800
|
+
}
|
|
801
|
+
var allDone = completed===tasks.length;
|
|
802
|
+
var milId = 'ms-'+esc(phase.id);
|
|
803
|
+
var isOpen = !!expandedMilestones[phase.id];
|
|
804
|
+
|
|
805
|
+
// Status color
|
|
806
|
+
var statusColor = allDone ? 'text-emerald-500' : (completed>0 ? 'text-amber-500' : 'text-slate-400');
|
|
807
|
+
var statusDot = allDone ? 'bg-emerald-500' : (completed>0 ? 'bg-amber-500' : 'bg-slate-400');
|
|
808
|
+
var statusText = allDone ? 'Completed' : (phase.status==='in_progress' ? 'In Progress' : 'Pending');
|
|
809
|
+
var borderLeft = allDone ? 'border-l-emerald-500' : (phase.status==='in_progress' ? 'border-l-amber-500' : 'border-l-slate-300');
|
|
810
|
+
|
|
811
|
+
var h = '<div class="bg-surface-container-lowest border border-outline-variant rounded-lg overflow-hidden border-l-4 '+borderLeft+' relative shadow-sm hover:shadow-md transition-shadow mb-md cursor-pointer" onclick="toggleMilestone(\''+esc(phase.id)+'\')">';
|
|
812
|
+
// Header
|
|
813
|
+
h += '<div class="p-md border-b border-outline-variant bg-surface-bright flex justify-between items-center hover:bg-surface-container-low transition-colors">';
|
|
814
|
+
h += '<div>';
|
|
815
|
+
h += '<h3 class="font-headline-sm text-headline-sm text-on-surface flex items-center gap-sm">';
|
|
816
|
+
h += '<span class="material-symbols-outlined '+statusColor+'">flag</span>';
|
|
817
|
+
h += esc(phase.id)+' · '+esc(phase.name);
|
|
818
|
+
h += '</h3>';
|
|
819
|
+
h += '</div>';
|
|
820
|
+
h += '<div class="flex items-center gap-sm">';
|
|
821
|
+
h += '<span class="w-2 h-2 rounded-full '+statusDot+'"></span>';
|
|
822
|
+
h += '<span class="font-code-sm text-code-sm text-on-surface-variant">'+statusText+', '+completed+'/'+tasks.length+'</span>';
|
|
823
|
+
h += '<span class="material-symbols-outlined text-on-surface-variant transition-transform duration-300" id="icon-'+milId+'">'+(isOpen?'expand_less':'expand_more')+'</span>';
|
|
824
|
+
h += '</div>';
|
|
825
|
+
h += '</div>';
|
|
826
|
+
|
|
827
|
+
// Body (tasks)
|
|
828
|
+
h += '<div class="p-md flex flex-col gap-sm accordion-content'+(isOpen?'':' collapsed')+'" id="'+milId+'">';
|
|
829
|
+
for(var i=0;i<tasks.length;i++) {
|
|
830
|
+
h += renderTrackSubtask(tasks[i], phase);
|
|
831
|
+
}
|
|
832
|
+
h += '</div>';
|
|
833
|
+
h += '</div>';
|
|
834
|
+
return h;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function renderTrackSubtask(task, phase) {
|
|
838
|
+
var isDone = task.status==='completed';
|
|
839
|
+
var isNext = task.status==='in_progress';
|
|
840
|
+
var iconName = isDone ? 'check_circle' : 'radio_button_unchecked';
|
|
841
|
+
var iconColor = isDone ? 'text-emerald-500' : (isNext ? 'text-primary' : 'text-outline-design');
|
|
842
|
+
var textStyle = isDone ? 'line-through text-on-surface-variant' : (isNext ? 'font-semibold text-on-surface' : 'text-on-surface');
|
|
843
|
+
var rowExtra = isNext ? 'bg-primary-fixed border border-primary pulse-border shadow-sm' : 'hover:bg-surface-container-low';
|
|
844
|
+
|
|
845
|
+
var h = '<div class="flex items-center justify-between p-sm rounded group transition-colors hover-scale '+rowExtra+'">';
|
|
846
|
+
h += '<div class="flex items-center gap-md">';
|
|
847
|
+
h += '<span class="material-symbols-outlined '+iconColor+'">'+iconName+'</span>';
|
|
848
|
+
h += '<span class="font-body-md text-body-md '+textStyle+'">'+esc(task.id)+' '+esc(task.title)+'</span>';
|
|
849
|
+
h += '</div>';
|
|
850
|
+
h += '<div class="flex items-center gap-sm">';
|
|
851
|
+
if(isNext) {
|
|
852
|
+
h += '<span class="font-label-caps text-label-caps bg-primary text-on-primary px-xs rounded animate-pulse shadow-inner">NEXT</span>';
|
|
853
|
+
}
|
|
854
|
+
if(task.actor) {
|
|
855
|
+
h += '<span class="font-code-sm text-code-sm bg-surface-variant text-on-surface-variant px-sm py-xs rounded">'+esc(task.actor)+'</span>';
|
|
856
|
+
}
|
|
857
|
+
h += '</div>';
|
|
858
|
+
h += '</div>';
|
|
859
|
+
return h;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function toggleMilestone(id) {
|
|
863
|
+
if(expandedMilestones[id]) {
|
|
864
|
+
expandedMilestones[id] = false;
|
|
865
|
+
} else {
|
|
866
|
+
expandedMilestones[id] = true;
|
|
867
|
+
}
|
|
868
|
+
renderTree();
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// ====== LEGACY: Render roadmap from timeline elements (fallback) ======
|
|
872
|
+
function renderRoadmapLegacy(roadmapEls) {
|
|
873
|
+
var verGroups={}, verOrder=[];
|
|
874
|
+
for(var i=0;i<roadmapEls.length;i++) {
|
|
639
875
|
var el=roadmapEls[i];
|
|
640
876
|
var children=el.children||[];
|
|
641
|
-
for(var j=0;j<children.length;j++){
|
|
877
|
+
for(var j=0;j<children.length;j++) {
|
|
642
878
|
var ch=children[j];
|
|
643
879
|
var ver=extractVersion(ch.name);
|
|
644
|
-
if(!verGroups[ver]){verGroups[ver]=[];verOrder.push(ver)}
|
|
880
|
+
if(!verGroups[ver]){verGroups[ver]=[];verOrder.push(ver);}
|
|
645
881
|
verGroups[ver].push(ch);
|
|
646
882
|
}
|
|
647
883
|
}
|
|
648
884
|
verOrder.sort(function(a,b){return versionOrder(a)-versionOrder(b)});
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
verGroups[v].sort(function(a,b){
|
|
652
|
-
var da=a.date||"",db=b.date||"";
|
|
653
|
-
return db.localeCompare(da);
|
|
654
|
-
});
|
|
885
|
+
for(var v in verGroups) {
|
|
886
|
+
verGroups[v].sort(function(a,b){var da=a.date||"",db=b.date||"";return db.localeCompare(da);});
|
|
655
887
|
}
|
|
656
|
-
|
|
657
888
|
var vgList=[];
|
|
658
|
-
for(var vi=0;vi<verOrder.length;vi++){
|
|
889
|
+
for(var vi=0;vi<verOrder.length;vi++) {
|
|
659
890
|
var v=verOrder[vi],g=verGroups[v];
|
|
660
891
|
vgList.push({ver:v,children:g});
|
|
661
892
|
}
|
|
662
|
-
// Sort by version descending (v0.3 → v0.2 → v0.1)
|
|
663
893
|
vgList.sort(function(a,b){return versionOrder(b.ver)-versionOrder(a.ver)});
|
|
664
|
-
|
|
665
894
|
var top3={};
|
|
666
|
-
for(var i=0;i<Math.min(3,vgList.length);i++){top3[vgList[i].ver]=true}
|
|
895
|
+
for(var i=0;i<Math.min(3,vgList.length);i++){top3[vgList[i].ver]=true;}
|
|
667
896
|
|
|
668
|
-
// Derive title parts from phase element name
|
|
669
897
|
var phaseName="";
|
|
670
|
-
for(var i=0;i<roadmapEls.length;i++){
|
|
671
|
-
if(roadmapEls[i].elementType==="phase"){phaseName=roadmapEls[i].name;break}
|
|
898
|
+
for(var i=0;i<roadmapEls.length;i++) {
|
|
899
|
+
if(roadmapEls[i].elementType==="phase"){phaseName=roadmapEls[i].name;break;}
|
|
672
900
|
}
|
|
673
901
|
|
|
674
902
|
var h="";
|
|
675
|
-
for(var i=0;i<vgList.length;i++){
|
|
903
|
+
for(var i=0;i<vgList.length;i++) {
|
|
676
904
|
var vg=vgList[i],children=vg.children,ver=vg.ver;
|
|
677
905
|
var done=0,total=children.length;
|
|
678
906
|
for(var j=0;j<children.length;j++){
|
|
679
907
|
if(children[j].status==="completed"||children[j].status==="cancelled")done++;
|
|
680
908
|
}
|
|
681
909
|
var allDone=(done===total);
|
|
682
|
-
var ratio=Math.round(done/total*100);
|
|
683
|
-
|
|
684
910
|
var stateCls=allDone?"rp-done":(done>0?"rp-mixed":"");
|
|
685
911
|
var ratioCls=allDone?"all-done":(done>0?"partial":"");
|
|
686
912
|
var ratioText=done+"/"+total;
|
|
687
|
-
|
|
688
913
|
var isTop3=!!top3[ver];
|
|
689
914
|
var manualOpen=!!expandedRpCards[ver];
|
|
690
915
|
var isSemi=isTop3 && !allDone && !manualOpen;
|
|
691
916
|
var isOpen=manualOpen;
|
|
692
|
-
|
|
693
917
|
var title=getVersionTitle(ver,children,phaseName);
|
|
694
918
|
var upTime=getVersionUpdateTime(ver,children);
|
|
695
919
|
|
|
696
|
-
|
|
697
|
-
h+='<div class="rp-card '+stateCls+(isSemi?" semi":"")+(isOpen?" open":"")+(isFilterMatch?" filter-match":"")+'" data-ver="'+esc(ver)+'">';
|
|
920
|
+
h+='<div class="rp-card '+stateCls+(isSemi?" semi":"")+(isOpen?" open":"")+'" data-ver="'+esc(ver)+'">';
|
|
698
921
|
h+='<div class="rp-head">';
|
|
699
|
-
h+='<span class="rp-arrow" onclick="toggleRpCard(this);event.stopPropagation()"
|
|
700
|
-
h+='<span class="rp-title" onclick="setDocFilter(\''+esc(ver)+'\');event.stopPropagation()" title="
|
|
922
|
+
h+='<span class="rp-arrow" onclick="toggleRpCard(this);event.stopPropagation()">▶</span>';
|
|
923
|
+
h+='<span class="rp-title" onclick="setDocFilter(\''+esc(ver)+'\');event.stopPropagation()" title="Filter events for this version">'+esc(title)+'</span>';
|
|
701
924
|
h+='<span class="rp-meta"><span class="rp-ratio '+ratioCls+'">'+ratioText+'</span><span class="tc-time">'+esc(upTime)+'</span></span>';
|
|
702
925
|
h+='</div><div class="rp-body">';
|
|
703
|
-
|
|
704
|
-
if(isSemi){
|
|
926
|
+
if(isSemi) {
|
|
705
927
|
var incChildren=[],comChildren=[];
|
|
706
|
-
for(var j=0;j<children.length;j++){
|
|
707
|
-
if(children[j].status==="completed"||children[j].status==="cancelled")
|
|
708
|
-
|
|
709
|
-
}else{
|
|
710
|
-
incChildren.push(children[j]);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
for(var j=0;j<incChildren.length;j++){
|
|
714
|
-
h+=renderRoadmapChild(incChildren[j]);
|
|
715
|
-
}
|
|
716
|
-
if(comChildren.length>0){
|
|
717
|
-
h+='<div class="rp-more">\u25bc 查看全部 '+total+' 里程碑(含 '+comChildren.length+' 已完成)</div>';
|
|
718
|
-
}
|
|
719
|
-
}else{
|
|
720
|
-
for(var j=0;j<children.length;j++){
|
|
721
|
-
h+=renderRoadmapChild(children[j]);
|
|
928
|
+
for(var j=0;j<children.length;j++) {
|
|
929
|
+
if(children[j].status==="completed"||children[j].status==="cancelled") comChildren.push(children[j]);
|
|
930
|
+
else incChildren.push(children[j]);
|
|
722
931
|
}
|
|
932
|
+
for(var j=0;j<incChildren.length;j++) h+=renderRoadmapChild(incChildren[j]);
|
|
933
|
+
if(comChildren.length>0) h+='<div class="rp-more">▼ View all '+total+' milestones ('+comChildren.length+' completed)</div>';
|
|
934
|
+
} else {
|
|
935
|
+
for(var j=0;j<children.length;j++) h+=renderRoadmapChild(children[j]);
|
|
723
936
|
}
|
|
724
|
-
h+=
|
|
937
|
+
h+='</div></div>';
|
|
725
938
|
}
|
|
726
939
|
return h;
|
|
727
940
|
}
|
|
728
941
|
|
|
729
|
-
function toggleRpCard(el){
|
|
942
|
+
function toggleRpCard(el) {
|
|
730
943
|
var card=el.parentNode.parentNode;
|
|
731
944
|
var ver=card.getAttribute("data-ver");
|
|
732
|
-
if(expandedRpCards[ver]){expandedRpCards[ver]=false}
|
|
733
|
-
else{expandedRpCards[ver]=true}
|
|
945
|
+
if(expandedRpCards[ver]){expandedRpCards[ver]=false;}
|
|
946
|
+
else{expandedRpCards[ver]=true;}
|
|
734
947
|
renderTree();
|
|
735
948
|
}
|
|
736
949
|
|
|
737
|
-
function renderRoadmapChild(ch){
|
|
950
|
+
function renderRoadmapChild(ch) {
|
|
738
951
|
var done=ch.status==="completed"||ch.status==="cancelled";
|
|
739
|
-
var icon=done?"
|
|
952
|
+
var icon=done?"✅":(ch.status==="in_progress"?"⏳":"⬜");
|
|
740
953
|
var labelCls=done?"done":(ch.status==="in_progress"?"progress":"pending-label");
|
|
741
|
-
var labelText=done?"
|
|
954
|
+
var labelText=done?"Done":(ch.status==="in_progress"?"In Progress":"Pending");
|
|
742
955
|
var time=fmtTime(ch.date)||ch.timeLabel||"";
|
|
743
956
|
var ver=extractVersion(ch.name);
|
|
744
957
|
var h='<div class="rp-child" data-ver="'+esc(ver)+'" data-doc="'+(ch.source&&ch.source.file?esc(ch.source.file):"")+'">';
|
|
@@ -746,342 +959,613 @@ function renderRoadmapChild(ch){
|
|
|
746
959
|
h+='<span class="rp-cname">'+esc(ch.name||ch.id)+'</span>';
|
|
747
960
|
h+='<span class="rp-ctime">'+esc(time)+'</span>';
|
|
748
961
|
h+='<span class="rp-clabel '+labelCls+'">'+labelText+'</span>';
|
|
749
|
-
h+=
|
|
962
|
+
h+='</div>';
|
|
750
963
|
return h;
|
|
751
964
|
}
|
|
752
965
|
|
|
753
|
-
function
|
|
966
|
+
function getVersionTitle(ver,children,phaseName) {
|
|
967
|
+
if(phaseName) {
|
|
968
|
+
var parts=phaseName.split(/[·;;]/);
|
|
969
|
+
for(var i=0;i<parts.length;i++) {
|
|
970
|
+
if(parts[i].indexOf(ver)!==-1) return parts[i].trim();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
var first=children[0];
|
|
974
|
+
if(first) {
|
|
975
|
+
var nm=first.name.replace(/\s*\([^)]*\)\s*/g,"").replace(/^M\d+:\s*/,"");
|
|
976
|
+
return ver+" · "+nm;
|
|
977
|
+
}
|
|
978
|
+
return ver;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function getVersionUpdateTime(ver,children) {
|
|
982
|
+
var latest="";
|
|
983
|
+
if(T&&T.events) {
|
|
984
|
+
for(var i=0;i<T.events.length;i++) {
|
|
985
|
+
var ev=T.events[i];
|
|
986
|
+
if(ev.tags&&ev.tags.indexOf(ver)!==-1) {
|
|
987
|
+
if(!latest||ev.timestamp>latest) latest=ev.timestamp;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if(!latest) {
|
|
992
|
+
for(var i=0;i<children.length;i++) {
|
|
993
|
+
var d=children[i].date;
|
|
994
|
+
if(d&&(!latest||d>latest)) latest=d;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return fmtTime(latest);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// ====== TREE CARDS (for non-roadmap elements) ======
|
|
1001
|
+
function renderTreeCard(el) {
|
|
754
1002
|
var isDoc=!!(el.source&&el.source.file);
|
|
755
1003
|
var tg=normTg(el.timeGroup);
|
|
756
1004
|
var time=fmtTime(el.date)||el.timeLabel||"";
|
|
757
|
-
var unk=catOf(el)==="unknown"?'<span style="background:#fff3e0;color:#e65100;font-size:9px;padding:1px 5px;border-radius:3px"
|
|
1005
|
+
var unk=catOf(el)==="unknown"?'<span style="background:#fff3e0;color:#e65100;font-size:9px;padding:1px 5px;border-radius:3px">unclassified</span>':"";
|
|
758
1006
|
|
|
759
|
-
// "新建"/"更新" badge for doc-type cards without status
|
|
760
1007
|
var docBadge="";
|
|
761
|
-
if(isDoc || !el.status){
|
|
1008
|
+
if(isDoc || !el.status) {
|
|
762
1009
|
var elDate=el.date||"",updDate=T&&T.updated?T.updated:"";
|
|
763
1010
|
var daysOld=99;
|
|
764
|
-
if(elDate&&updDate){daysOld=(new Date(updDate)-new Date(elDate))/(86400000)}
|
|
765
|
-
if(daysOld<=1)docBadge='<span class="tc-type status-done"
|
|
766
|
-
else if(daysOld<=3)docBadge='<span class="tc-type status-progress"
|
|
1011
|
+
if(elDate&&updDate){daysOld=(new Date(updDate)-new Date(elDate))/(86400000);}
|
|
1012
|
+
if(daysOld<=1) docBadge='<span class="tc-type status-done">New</span>';
|
|
1013
|
+
else if(daysOld<=3) docBadge='<span class="tc-type status-progress">Updated</span>';
|
|
767
1014
|
}
|
|
768
1015
|
|
|
769
|
-
var isFilterMatch=activeDocFilter && (isDoc&&el.source.file===activeDocFilter || (el.tags&&el.tags.indexOf(activeDocFilter)!==-1));
|
|
1016
|
+
var isFilterMatch = activeDocFilter && (isDoc&&el.source.file===activeDocFilter || (el.tags&&el.tags.indexOf(activeDocFilter)!==-1));
|
|
770
1017
|
var h='<div class="tree-card'+(isFilterMatch?" filter-match":"")+'" data-tg="'+esc(tg)+'" data-doc="'+(isDoc?esc(el.source.file):"")+'" onclick="onTreeCardClick(event,\''+esc(isDoc?el.source.file:"")+'\')">';
|
|
771
1018
|
|
|
772
1019
|
h+='<div class="tc-head">';
|
|
773
|
-
if(!isDoc && el.status){
|
|
774
|
-
h+=
|
|
1020
|
+
if(!isDoc && el.status) {
|
|
1021
|
+
h+='<span>'+sicon(el.status)+' '+esc(el.name)+'</span>';
|
|
775
1022
|
var stCls=el.status==="completed"?"status-done":(el.status==="in_progress"?"status-progress":"status-pending");
|
|
776
|
-
h+='<span class="tc-type '+stCls+'">'+(el.status==="completed"?"
|
|
777
|
-
}else{
|
|
778
|
-
h+=
|
|
1023
|
+
h+='<span class="tc-type '+stCls+'">'+(el.status==="completed"?"Done":(el.status==="in_progress"?"In Progress":el.status))+'</span>';
|
|
1024
|
+
} else {
|
|
1025
|
+
h+='<span>'+esc(el.name)+'</span>';
|
|
779
1026
|
h+='<span class="tc-meta">'+unk+docBadge+'<span class="tc-time">'+esc(time)+'</span></span>';
|
|
780
1027
|
}
|
|
781
|
-
h+=
|
|
1028
|
+
h+='</div>';
|
|
782
1029
|
|
|
783
1030
|
h+='<div class="tc-meta" style="padding:0 12px 4px">';
|
|
784
|
-
if(el.status)h+='<span class="tc-time">'+esc(time)+'</span>';
|
|
785
|
-
if(el.tags&&el.tags.length){
|
|
786
|
-
for(var i=0;i<el.tags.length;i++)
|
|
787
|
-
h+='<span class="tag">'+esc(el.tags[i])+'</span>';
|
|
788
|
-
}
|
|
1031
|
+
if(el.status) h+='<span class="tc-time">'+esc(time)+'</span>';
|
|
1032
|
+
if(el.tags&&el.tags.length) {
|
|
1033
|
+
for(var i=0;i<el.tags.length;i++) h+='<span class="tag">'+esc(el.tags[i])+'</span>';
|
|
789
1034
|
}
|
|
790
|
-
h+=
|
|
791
|
-
|
|
792
|
-
if(el.summary)h+='<div class="tc-summary">'+esc(el.summary)+'</div>';
|
|
1035
|
+
h+='</div>';
|
|
793
1036
|
|
|
794
|
-
if(
|
|
1037
|
+
if(el.summary) h+='<div class="tc-summary">'+esc(el.summary)+'</div>';
|
|
1038
|
+
if(isDoc) {
|
|
795
1039
|
h+='<div class="tc-src"><a href="/'+esc(el.source.file)+'" onclick="preview(event,\''+esc(el.source.file)+'\',\'left\');event.stopPropagation()" title="'+esc(el.source.file)+'">'+esc(el.source.file.substring(0,45))+'</a></div>';
|
|
796
1040
|
}
|
|
797
|
-
|
|
798
|
-
h+="</div>";
|
|
1041
|
+
h+='</div>';
|
|
799
1042
|
return h;
|
|
800
1043
|
}
|
|
801
1044
|
|
|
802
|
-
function
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1045
|
+
function sicon(s) {
|
|
1046
|
+
var map = {completed:'✅',in_progress:'⏳',pending:'⬜',blocked:'🚫',cancelled:'❌'};
|
|
1047
|
+
return map[s]||'⬜';
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// ====== DOC FILTER ======
|
|
1051
|
+
function setDocFilter(doc) {
|
|
1052
|
+
if(activeDocFilter===doc) { activeDocFilter=null; }
|
|
1053
|
+
else { activeDocFilter=doc; }
|
|
1054
|
+
var ft = document.querySelector('#toolbar .ftext');
|
|
1055
|
+
var fc = document.querySelector('#toolbar .fclear');
|
|
1056
|
+
if(activeDocFilter) {
|
|
1057
|
+
var label = activeDocFilter==='__unmatched__'?'Branch Opts (unclassified)':activeDocFilter==='__todo__'?'Todo List':activeDocFilter;
|
|
1058
|
+
ft.textContent = 'Filter: '+label; fc.classList.add('show');
|
|
1059
|
+
document.body.classList.add('filter-active');
|
|
1060
|
+
} else {
|
|
1061
|
+
ft.textContent = ''; fc.classList.remove('show');
|
|
1062
|
+
document.body.classList.remove('filter-active');
|
|
814
1063
|
}
|
|
815
1064
|
renderTree();
|
|
816
1065
|
renderEvents();
|
|
817
1066
|
}
|
|
818
1067
|
|
|
819
|
-
function onTreeCardClick(e,doc){
|
|
820
|
-
if(e.target.closest(
|
|
821
|
-
if(doc){setDocFilter(doc)}
|
|
1068
|
+
function onTreeCardClick(e, doc) {
|
|
1069
|
+
if(e.target.closest('a')) return;
|
|
1070
|
+
if(doc) { setDocFilter(doc); }
|
|
822
1071
|
}
|
|
823
1072
|
|
|
824
|
-
function bindDocClicks(){
|
|
825
|
-
var rpChildren=document.querySelectorAll(
|
|
826
|
-
for(var i=0;i<rpChildren.length;i++){
|
|
827
|
-
rpChildren[i].addEventListener(
|
|
1073
|
+
function bindDocClicks() {
|
|
1074
|
+
var rpChildren = document.querySelectorAll('.rp-child');
|
|
1075
|
+
for(var i=0;i<rpChildren.length;i++) {
|
|
1076
|
+
rpChildren[i].addEventListener('click', function(e) {
|
|
828
1077
|
e.stopPropagation();
|
|
829
|
-
var ver=this.getAttribute(
|
|
830
|
-
if(ver){setDocFilter(ver);return}
|
|
831
|
-
var doc=this.getAttribute(
|
|
832
|
-
if(doc)setDocFilter(doc);
|
|
1078
|
+
var ver = this.getAttribute('data-ver');
|
|
1079
|
+
if(ver) { setDocFilter(ver); return; }
|
|
1080
|
+
var doc = this.getAttribute('data-doc');
|
|
1081
|
+
if(doc) setDocFilter(doc);
|
|
833
1082
|
});
|
|
834
1083
|
}
|
|
835
1084
|
}
|
|
836
1085
|
|
|
837
|
-
// ======
|
|
838
|
-
|
|
839
|
-
var
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if(ev.title&&ev.title.indexOf(doc)!==-1)return true;
|
|
848
|
-
return false;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
function evHasAnyElementMatch(ev){
|
|
852
|
-
if(!T||!T.elements)return false;
|
|
853
|
-
for(var i=0;i<T.elements.length;i++){
|
|
854
|
-
var el=T.elements[i];
|
|
855
|
-
if(el.source&&el.source.file){
|
|
856
|
-
if(ev.ref&&ev.ref.doc===el.source.file)return true;
|
|
857
|
-
}
|
|
858
|
-
var elVer=extractVersion(el.name||'');
|
|
859
|
-
if(elVer&&ev.tags&&ev.tags.indexOf(elVer)!==-1)return true;
|
|
860
|
-
if(elVer&&ev.title&&ev.title.indexOf(elVer)!==-1)return true;
|
|
861
|
-
if(el.children){
|
|
862
|
-
for(var j=0;j<el.children.length;j++){
|
|
863
|
-
var ch=el.children[j];
|
|
864
|
-
if(ch.source&&ch.source.file){
|
|
865
|
-
if(ev.ref&&ev.ref.doc===ch.source.file)return true;
|
|
866
|
-
}
|
|
867
|
-
var chVer=extractVersion(ch.name||'');
|
|
868
|
-
if(chVer&&ev.tags&&ev.tags.indexOf(chVer)!==-1)return true;
|
|
869
|
-
if(chVer&&ev.title&&ev.title.indexOf(chVer)!==-1)return true;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
1086
|
+
// ====== TOOLBAR ======
|
|
1087
|
+
function setupToolbar() {
|
|
1088
|
+
var sortBtns = document.querySelectorAll('#toolbar .sort-btn');
|
|
1089
|
+
for(var i=0;i<sortBtns.length;i++) {
|
|
1090
|
+
sortBtns[i].addEventListener('click', function() {
|
|
1091
|
+
for(var j=0;j<sortBtns.length;j++) sortBtns[j].classList.remove('on');
|
|
1092
|
+
this.classList.add('on');
|
|
1093
|
+
sortMode = this.getAttribute('data-mode');
|
|
1094
|
+
renderEvents();
|
|
1095
|
+
});
|
|
872
1096
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1097
|
+
var fclear = document.querySelector('#toolbar .fclear');
|
|
1098
|
+
if(fclear) {
|
|
1099
|
+
fclear.addEventListener('click', function() {
|
|
1100
|
+
activeDocFilter = null;
|
|
1101
|
+
activeTagFilter = null;
|
|
1102
|
+
this.classList.remove('show');
|
|
1103
|
+
document.querySelector('#toolbar .ftext').textContent = '';
|
|
1104
|
+
document.body.classList.remove('filter-active');
|
|
1105
|
+
renderTree();
|
|
1106
|
+
renderTagBar();
|
|
1107
|
+
renderEvents();
|
|
1108
|
+
});
|
|
881
1109
|
}
|
|
882
|
-
return c;
|
|
883
1110
|
}
|
|
884
1111
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
var
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
if(ev.ref.
|
|
1112
|
+
// ====== EVENTS ======
|
|
1113
|
+
function renderSingleEv(ev) {
|
|
1114
|
+
var tinfo = ETYPES[ev.type]||['ev-t3','t3'];
|
|
1115
|
+
var refs = '';
|
|
1116
|
+
if(ev.ref) {
|
|
1117
|
+
if(ev.ref.commit) refs += '<code style="font-size:10px;background:#f5f5f5;padding:0 3px;border-radius:2px">'+esc(ev.ref.commit)+'</code> ';
|
|
1118
|
+
if(ev.ref.doc) refs += '<a class="ev-ref" data-d="'+esc(ev.ref.doc)+'" href="/'+esc(ev.ref.doc)+'" onclick="preview(event,\''+esc(ev.ref.doc)+'\',\'right\')">'+esc(ev.ref.doc.substring(0,35))+'</a> ';
|
|
891
1119
|
}
|
|
892
|
-
var tags='';
|
|
893
|
-
if(ev.tags&&ev.tags.length)
|
|
894
|
-
|
|
895
|
-
|
|
1120
|
+
var tags = '';
|
|
1121
|
+
if(ev.tags&&ev.tags.length) {
|
|
1122
|
+
for(var ti=0;ti<ev.tags.length;ti++) {
|
|
1123
|
+
tags += '<span class="ev-tag" data-tag="'+esc(ev.tags[ti])+'" onclick="setTagFilter(\''+esc(ev.tags[ti])+'\');event.stopPropagation()">'+esc(ev.tags[ti])+'</span>';
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
var time = fmtTime(ev.timestamp||'');
|
|
896
1127
|
return '<div class="ev-item '+tinfo[1]+'">'+
|
|
897
1128
|
'<div class="ev-head"><span class="ev-title">'+esc(ev.title)+'</span><span class="ev-type '+tinfo[0]+'">'+esc(ev.type||'')+'</span></div>'+
|
|
898
1129
|
'<div class="ev-meta">'+(time?'<span class="ev-time">'+esc(time)+'</span>':'')+refs+tags+'</div>'+
|
|
899
1130
|
'</div>';
|
|
900
1131
|
}
|
|
901
1132
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1133
|
+
// Assign track to an event based on tags/ref
|
|
1134
|
+
function guessEventTrack(ev) {
|
|
1135
|
+
if(!R||!R.tracks||!R.tracks.length) return null;
|
|
1136
|
+
var tracks = R.tracks;
|
|
1137
|
+
for(var i=0;i<tracks.length;i++) {
|
|
1138
|
+
var t = tracks[i];
|
|
1139
|
+
if(ev.tags && ev.tags.indexOf(t.id)!==-1) return t.id;
|
|
1140
|
+
if(ev.title && ev.title.indexOf(t.id+' ')!==-1) return t.id;
|
|
1141
|
+
if(ev.title && ev.title.indexOf(t.id+'.')!==-1) return t.id;
|
|
1142
|
+
// Check phases
|
|
1143
|
+
var phases = t.phases||[];
|
|
1144
|
+
for(var j=0;j<phases.length;j++) {
|
|
1145
|
+
if(ev.tags && ev.tags.indexOf(phases[j].id)!==-1) return t.id;
|
|
1146
|
+
if(ev.title && ev.title.indexOf(phases[j].id)!==-1) return t.id;
|
|
1147
|
+
// Check tasks
|
|
1148
|
+
var tasks = phases[j].tasks||[];
|
|
1149
|
+
for(var k=0;k<tasks.length;k++) {
|
|
1150
|
+
if(ev.tags && ev.tags.indexOf(tasks[k].id)!==-1) return t.id;
|
|
1151
|
+
if(ev.title && ev.title.indexOf(tasks[k].id)!==-1) return t.id;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function renderEvents() {
|
|
1159
|
+
var container = document.getElementById('events');
|
|
1160
|
+
var evs = T ? T.events||[] : [];
|
|
1161
|
+
if(!evs.length) {
|
|
1162
|
+
container.innerHTML = '<div class="empty"><h3 class="font-headline-sm text-headline-sm mb-sm">No events</h3><p class="font-body-md text-body-md text-on-surface-variant">Run build to refresh event log</p></div>';
|
|
1163
|
+
renderTagBar();
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Apply doc filter
|
|
1168
|
+
if(activeDocFilter) {
|
|
1169
|
+
var f=[];
|
|
1170
|
+
for(var i=0;i<evs.length;i++) { if(evMatchesDoc(evs[i],activeDocFilter)) f.push(evs[i]); }
|
|
1171
|
+
evs=f;
|
|
1172
|
+
}
|
|
908
1173
|
// Apply tag filter
|
|
909
|
-
if(activeTagFilter)
|
|
910
|
-
|
|
1174
|
+
if(activeTagFilter) {
|
|
1175
|
+
var f=[];
|
|
1176
|
+
for(var i=0;i<evs.length;i++) { if(evHasTag(evs[i],activeTagFilter)) f.push(evs[i]); }
|
|
1177
|
+
evs=f;
|
|
1178
|
+
}
|
|
1179
|
+
if(evs.length===0) {
|
|
1180
|
+
container.innerHTML = '<div class="empty"><h3 class="font-headline-sm text-headline-sm mb-sm">No matching events</h3><p class="font-body-md text-body-md text-on-surface-variant">Try clearing filters or run build to refresh</p></div>';
|
|
1181
|
+
renderTagBar();
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
911
1185
|
// Build doc name map
|
|
912
1186
|
var docNameMap={};
|
|
913
|
-
if(T&&T.elements)
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1187
|
+
if(T&&T.elements) {
|
|
1188
|
+
for(var ei=0;ei<T.elements.length;ei++) {
|
|
1189
|
+
var el=T.elements[ei];
|
|
1190
|
+
if(el.source&&el.source.file&&el.name) docNameMap[el.source.file]=el.name;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// FLAT mode: all events by time
|
|
1195
|
+
if(sortMode==='flat') {
|
|
1196
|
+
evs.sort(function(a,b){ return (b.timestamp||"").localeCompare(a.timestamp||""); });
|
|
1197
|
+
var h='', curDay='';
|
|
1198
|
+
for(var i=0;i<evs.length;i++) {
|
|
919
1199
|
var dk3=(evs[i].timestamp||"").substring(0,10);
|
|
920
|
-
if(dk3!==curDay){curDay=dk3;h+=
|
|
1200
|
+
if(dk3!==curDay){ curDay=dk3; h+='<div class="ev-day">'+esc(curDay)+'</div>'; }
|
|
921
1201
|
h+=renderSingleEv(evs[i]);
|
|
922
1202
|
}
|
|
923
|
-
container.innerHTML=h;
|
|
1203
|
+
container.innerHTML = h;
|
|
924
1204
|
renderTagBar();
|
|
925
|
-
document.getElementById(
|
|
1205
|
+
document.getElementById('evcnt').textContent = evs.length+' events';
|
|
926
1206
|
return;
|
|
927
1207
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1208
|
+
|
|
1209
|
+
// TRACK mode: group events by guessed track, then within track by thread
|
|
1210
|
+
if(sortMode==='track' && R && R.tracks && R.tracks.length) {
|
|
1211
|
+
var trackGroups = {};
|
|
1212
|
+
var untracked = [];
|
|
1213
|
+
for(var i=0;i<evs.length;i++) {
|
|
1214
|
+
var tk = guessEventTrack(evs[i]);
|
|
1215
|
+
if(tk) {
|
|
1216
|
+
if(!trackGroups[tk]) trackGroups[tk] = [];
|
|
1217
|
+
trackGroups[tk].push(evs[i]);
|
|
1218
|
+
} else {
|
|
1219
|
+
untracked.push(evs[i]);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
var h = '';
|
|
1223
|
+
var trackOrder = [];
|
|
1224
|
+
for(var i=0;i<R.tracks.length;i++) { trackOrder.push(R.tracks[i].id); }
|
|
1225
|
+
for(var ti=0;ti<trackOrder.length;ti++) {
|
|
1226
|
+
var tid = trackOrder[ti];
|
|
1227
|
+
var tevs = trackGroups[tid]||[];
|
|
1228
|
+
if(!tevs.length) continue;
|
|
1229
|
+
h += renderTrackEventGroup(tid, tevs, docNameMap);
|
|
1230
|
+
}
|
|
1231
|
+
if(untracked.length) {
|
|
1232
|
+
h += renderTrackEventGroup('Other', untracked, docNameMap);
|
|
1233
|
+
}
|
|
1234
|
+
container.innerHTML = h;
|
|
1235
|
+
renderTagBar();
|
|
1236
|
+
document.getElementById('evcnt').textContent = evs.length+' events';
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// GROUPED mode (default): merge events by thread/doc
|
|
1241
|
+
var docThreads={}, orphanThreads={}, singles=[];
|
|
1242
|
+
for(var i=0;i<evs.length;i++) {
|
|
1243
|
+
var ev=evs[i];
|
|
1244
|
+
if(ev.ref&&ev.ref.doc) {
|
|
1245
|
+
if(!docThreads[ev.ref.doc]) docThreads[ev.ref.doc]=[];
|
|
1246
|
+
docThreads[ev.ref.doc].push(ev);
|
|
1247
|
+
} else if(ev.threadId) {
|
|
1248
|
+
if(!orphanThreads[ev.threadId]) orphanThreads[ev.threadId]=[];
|
|
1249
|
+
orphanThreads[ev.threadId].push(ev);
|
|
1250
|
+
} else {
|
|
1251
|
+
singles.push(ev);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
for(var d in docThreads) { docThreads[d].sort(function(a,b){ return (b.timestamp||"").localeCompare(a.timestamp||""); }); }
|
|
1255
|
+
for(var t in orphanThreads) { orphanThreads[t].sort(function(a,b){ return (b.timestamp||"").localeCompare(a.timestamp||""); }); }
|
|
1256
|
+
singles.sort(function(a,b){ return (b.timestamp||"").localeCompare(a.timestamp||""); });
|
|
1257
|
+
|
|
935
1258
|
var merged=[];
|
|
936
|
-
for(var doc in docThreads){
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1259
|
+
for(var doc in docThreads) {
|
|
1260
|
+
merged.push({tk:"doc:"+doc, te:docThreads[doc], title:docNameMap[doc]||doc.replace(/^.*[\\\/]/,"").replace(/\.md$/i,""), dataDoc:doc, ts:docThreads[doc][0].timestamp});
|
|
1261
|
+
}
|
|
1262
|
+
for(var tid in orphanThreads) {
|
|
1263
|
+
merged.push({tk:"tid:"+tid, te:orphanThreads[tid], title:"Thread: "+tid, dataDoc:null, ts:orphanThreads[tid][0].timestamp});
|
|
1264
|
+
}
|
|
1265
|
+
merged.sort(function(a,b){ return (b.ts||"").localeCompare(a.ts||""); });
|
|
1266
|
+
|
|
940
1267
|
var top3={};
|
|
941
|
-
for(var ti=0;ti<Math.min(3,merged.length);ti++){top3[merged[ti].tk]=true}
|
|
942
|
-
|
|
943
|
-
function rtc(tk,te,title,dataDoc,isSemi){
|
|
944
|
-
var tc2={};
|
|
945
|
-
var
|
|
946
|
-
var
|
|
947
|
-
var
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
r
|
|
951
|
-
r+=
|
|
952
|
-
r+=
|
|
953
|
-
r+=
|
|
954
|
-
|
|
1268
|
+
for(var ti=0;ti<Math.min(3,merged.length);ti++) { top3[merged[ti].tk]=true; }
|
|
1269
|
+
|
|
1270
|
+
function rtc(tk, te, title, dataDoc, isSemi) {
|
|
1271
|
+
var tc2={};
|
|
1272
|
+
for(var i=0;i<te.length;i++) { var tt=te[i].type; tc2[tt]=(tc2[tt]||0)+1; }
|
|
1273
|
+
var ts=[]; for(var tk2 in tc2) { ts.push(tk2+"\u00d7"+tc2[tk2]); }
|
|
1274
|
+
var manualOpen=!!expandedThreads[tk];
|
|
1275
|
+
var cls=(isSemi&&!manualOpen)?"semi":(manualOpen?"open":"");
|
|
1276
|
+
if(manualOpen) expandedThreads[tk]=true;
|
|
1277
|
+
var r='<div class="th-card '+cls+'" data-thread="'+esc(tk)+'"'+(dataDoc?' data-doc="'+esc(dataDoc)+'"':'')+'>';
|
|
1278
|
+
r+='<div class="th-head" onclick="toggleThread(this)">';
|
|
1279
|
+
r+='<span style="font-size:10px;color:#727785;transition:transform .2s;display:inline-block">▶</span>';
|
|
1280
|
+
r+='<span class="th-title">'+esc(title)+'</span>';
|
|
1281
|
+
r+='<span class="th-count">'+te.length+' events</span>';
|
|
1282
|
+
r+='</div><div class="th-body">';
|
|
1283
|
+
r+='<div class="th-typebar">'+esc(ts.join(" · "))+'</div>';
|
|
1284
|
+
if(isSemi&&!manualOpen) {
|
|
955
1285
|
r+=renderSingleEv(te[0]);
|
|
956
|
-
if(te.length>1)r+=
|
|
957
|
-
}else{
|
|
958
|
-
for(var i=0;i<te.length;i++)r+=renderSingleEv(te[i]);
|
|
959
|
-
r+=
|
|
1286
|
+
if(te.length>1) r+='<div class="th-more" onclick="expandSemi(this);event.stopPropagation()">▼ View all '+te.length+' events</div>';
|
|
1287
|
+
} else {
|
|
1288
|
+
for(var i=0;i<te.length;i++) r+=renderSingleEv(te[i]);
|
|
1289
|
+
r+='<div class="th-collapse" onclick="collapseThread(this);event.stopPropagation()">▲ Collapse</div>';
|
|
960
1290
|
}
|
|
961
|
-
r+=
|
|
1291
|
+
r+='</div></div>';
|
|
1292
|
+
return r;
|
|
962
1293
|
}
|
|
963
|
-
|
|
964
|
-
var h=
|
|
965
|
-
for(var mi=0;mi<merged.length;mi++){var m=merged[mi];h+=rtc(m.tk,m.te,m.title,m.dataDoc,!!top3[m.tk])}
|
|
966
|
-
for(var ui=0;ui<singles.length;ui++)h+=renderSingleEv(singles[ui]);
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1294
|
+
|
|
1295
|
+
var h='';
|
|
1296
|
+
for(var mi=0;mi<merged.length;mi++) { var m=merged[mi]; h+=rtc(m.tk,m.te,m.title,m.dataDoc,!!top3[m.tk]); }
|
|
1297
|
+
for(var ui=0;ui<singles.length;ui++) h+=renderSingleEv(singles[ui]);
|
|
1298
|
+
|
|
1299
|
+
container.innerHTML = h;
|
|
1300
|
+
if(activeDocFilter) {
|
|
1301
|
+
var mc = container.querySelector('.th-card[data-doc="'+CSS.escape(activeDocFilter)+'"]');
|
|
1302
|
+
if(mc) { mc.classList.add('th-active'); mc.scrollIntoView({behavior:'smooth',block:'center'}); }
|
|
971
1303
|
}
|
|
972
1304
|
renderTagBar();
|
|
973
|
-
document.getElementById(
|
|
1305
|
+
document.getElementById('evcnt').textContent = evs.length+' events';
|
|
974
1306
|
}
|
|
975
1307
|
|
|
1308
|
+
function renderTrackEventGroup(trackId, tevs, docNameMap) {
|
|
1309
|
+
// Within a track, group by thread/doc
|
|
1310
|
+
var docThreads={}, singles=[];
|
|
1311
|
+
for(var i=0;i<tevs.length;i++) {
|
|
1312
|
+
var ev=tevs[i];
|
|
1313
|
+
if(ev.ref&&ev.ref.doc) {
|
|
1314
|
+
if(!docThreads[ev.ref.doc]) docThreads[ev.ref.doc]=[];
|
|
1315
|
+
docThreads[ev.ref.doc].push(ev);
|
|
1316
|
+
} else {
|
|
1317
|
+
singles.push(ev);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
for(var d in docThreads) { docThreads[d].sort(function(a,b){ return (b.timestamp||"").localeCompare(a.timestamp||""); }); }
|
|
1321
|
+
singles.sort(function(a,b){ return (b.timestamp||"").localeCompare(a.timestamp||""); });
|
|
1322
|
+
|
|
1323
|
+
// Get track label
|
|
1324
|
+
var trackLabel = trackId;
|
|
1325
|
+
if(R&&R.tracks) {
|
|
1326
|
+
for(var i=0;i<R.tracks.length;i++) {
|
|
1327
|
+
if(R.tracks[i].id===trackId) { trackLabel = R.tracks[i].label||trackId; break; }
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
976
1330
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1331
|
+
var h = '<div class="mb-lg">';
|
|
1332
|
+
h += '<h4 class="font-label-caps text-label-caps text-on-surface-variant mb-md tracking-wider">'+esc(trackLabel)+' ('+tevs.length+' events)</h4>';
|
|
1333
|
+
h += '<div class="flex flex-col gap-sm border-l border-outline-variant ml-sm pl-lg relative">';
|
|
1334
|
+
|
|
1335
|
+
for(var d in docThreads) {
|
|
1336
|
+
var te = docThreads[d];
|
|
1337
|
+
var title = docNameMap[d]||d.replace(/^.*[\\\/]/,"").replace(/\.md$/i,"");
|
|
1338
|
+
for(var i=0;i<te.length;i++) {
|
|
1339
|
+
h += '<div class="relative py-xs">';
|
|
1340
|
+
h += '<span class="absolute -left-[25px] top-2 w-2 h-2 rounded-full bg-outline-design outline outline-4 outline-surface-bright"></span>';
|
|
1341
|
+
h += '<div class="bg-surface-container-lowest border border-outline-variant p-md rounded hover:shadow-md transition-all hover-scale">';
|
|
1342
|
+
h += '<div class="flex justify-between items-start mb-xs">';
|
|
1343
|
+
h += '<span class="font-code-md text-code-md text-on-surface flex items-center gap-xs">';
|
|
1344
|
+
if(te[i].ref&&te[i].ref.commit) h += '<span class="material-symbols-outlined text-tertiary-container text-[16px]">commit</span>';
|
|
1345
|
+
h += esc(te[i].title)+'</span>';
|
|
1346
|
+
h += '<span class="font-code-sm text-code-sm text-on-surface-variant">'+esc(fmtTime(te[i].timestamp||''))+'</span>';
|
|
1347
|
+
h += '</div>';
|
|
1348
|
+
if(te[i].type) {
|
|
1349
|
+
h += '<div class="flex flex-wrap gap-xs mt-sm">';
|
|
1350
|
+
h += '<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] uppercase tracking-wider font-code-sm bg-surface-container-low text-on-surface-variant border border-outline-variant">'+esc(te[i].type)+'</span>';
|
|
1351
|
+
if(te[i].tags) for(var j=0;j<Math.min(3,te[i].tags.length);j++) {
|
|
1352
|
+
h += '<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] uppercase tracking-wider font-code-sm bg-surface-container-low text-on-surface-variant border border-outline-variant">'+esc(te[i].tags[j])+'</span>';
|
|
1353
|
+
}
|
|
1354
|
+
h += '</div>';
|
|
1355
|
+
}
|
|
1356
|
+
h += '</div></div>';
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
for(var i=0;i<singles.length;i++) {
|
|
1360
|
+
h += '<div class="relative py-xs">';
|
|
1361
|
+
h += '<span class="absolute -left-[25px] top-2 w-2 h-2 rounded-full bg-outline-design outline outline-4 outline-surface-bright"></span>';
|
|
1362
|
+
h += '<div class="bg-surface-container-lowest border border-outline-variant p-md rounded hover:shadow-md transition-all hover-scale">';
|
|
1363
|
+
h += '<div class="flex justify-between items-start mb-xs">';
|
|
1364
|
+
h += '<span class="font-code-md text-code-md text-on-surface">'+esc(singles[i].title)+'</span>';
|
|
1365
|
+
h += '<span class="font-code-sm text-code-sm text-on-surface-variant">'+esc(fmtTime(singles[i].timestamp||''))+'</span>';
|
|
1366
|
+
h += '</div>';
|
|
1367
|
+
if(singles[i].type) {
|
|
1368
|
+
h += '<div class="flex flex-wrap gap-xs mt-sm">';
|
|
1369
|
+
h += '<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] uppercase tracking-wider font-code-sm bg-surface-container-low text-on-surface-variant border border-outline-variant">'+esc(singles[i].type)+'</span>';
|
|
1370
|
+
if(singles[i].tags) for(var j=0;j<Math.min(3,singles[i].tags.length);j++) {
|
|
1371
|
+
h += '<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] uppercase tracking-wider font-code-sm bg-surface-container-low text-on-surface-variant border border-outline-variant">'+esc(singles[i].tags[j])+'</span>';
|
|
1372
|
+
}
|
|
1373
|
+
h += '</div>';
|
|
1374
|
+
}
|
|
1375
|
+
h += '</div></div>';
|
|
983
1376
|
}
|
|
1377
|
+
h += '</div></div>';
|
|
1378
|
+
return h;
|
|
984
1379
|
}
|
|
985
1380
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
var
|
|
989
|
-
if(
|
|
990
|
-
card.classList.
|
|
991
|
-
|
|
1381
|
+
// Thread controls
|
|
1382
|
+
function toggleThread(headEl) {
|
|
1383
|
+
var card = headEl.parentNode;
|
|
1384
|
+
if(card.classList.contains('open')) { card.classList.remove('open'); }
|
|
1385
|
+
else { card.classList.add('open'); }
|
|
1386
|
+
}
|
|
1387
|
+
function expandSemi(el) {
|
|
1388
|
+
var card = el.parentNode.parentNode;
|
|
1389
|
+
var tk = card.getAttribute('data-thread');
|
|
1390
|
+
if(tk) expandedThreads[tk]=true;
|
|
1391
|
+
card.classList.remove('semi');
|
|
1392
|
+
card.classList.add('open');
|
|
992
1393
|
renderEvents();
|
|
993
1394
|
}
|
|
1395
|
+
function collapseThread(el) {
|
|
1396
|
+
var card = el.parentNode.parentNode;
|
|
1397
|
+
card.classList.remove('open');
|
|
1398
|
+
}
|
|
994
1399
|
|
|
995
|
-
function
|
|
996
|
-
|
|
997
|
-
|
|
1400
|
+
function evMatchesDoc(ev, doc) {
|
|
1401
|
+
if(!doc) return true;
|
|
1402
|
+
if(doc==='__unmatched__') return !evHasAnyElementMatch(ev);
|
|
1403
|
+
if(doc==='__todo__') return ev.tags&&ev.tags.indexOf('todo')!==-1;
|
|
1404
|
+
if(ev.ref&&ev.ref.doc===doc) return true;
|
|
1405
|
+
if(ev.tags&&ev.tags.indexOf(doc)!==-1) return true;
|
|
1406
|
+
if(ev.title&&ev.title.indexOf(doc)!==-1) return true;
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function evHasAnyElementMatch(ev) {
|
|
1411
|
+
if(!T||!T.elements) return false;
|
|
1412
|
+
for(var i=0;i<T.elements.length;i++) {
|
|
1413
|
+
var el=T.elements[i];
|
|
1414
|
+
if(el.source&&el.source.file) { if(ev.ref&&ev.ref.doc===el.source.file) return true; }
|
|
1415
|
+
var elVer=extractVersion(el.name||'');
|
|
1416
|
+
if(elVer&&ev.tags&&ev.tags.indexOf(elVer)!==-1) return true;
|
|
1417
|
+
if(elVer&&ev.title&&ev.title.indexOf(elVer)!==-1) return true;
|
|
1418
|
+
if(el.children) {
|
|
1419
|
+
for(var j=0;j<el.children.length;j++) {
|
|
1420
|
+
var ch=el.children[j];
|
|
1421
|
+
if(ch.source&&ch.source.file) { if(ev.ref&&ev.ref.doc===ch.source.file) return true; }
|
|
1422
|
+
var chVer=extractVersion(ch.name||'');
|
|
1423
|
+
if(chVer&&ev.tags&&ev.tags.indexOf(chVer)!==-1) return true;
|
|
1424
|
+
if(chVer&&ev.title&&ev.title.indexOf(chVer)!==-1) return true;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
return false;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function countUnmatchedEvents() {
|
|
1432
|
+
if(!T||!T.events) return 0;
|
|
1433
|
+
var c=0;
|
|
1434
|
+
for(var i=0;i<T.events.length;i++) { if(!evHasAnyElementMatch(T.events[i])) c++; }
|
|
1435
|
+
return c;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// ====== TAG FILTER ======
|
|
1439
|
+
function setTagFilter(tag) {
|
|
1440
|
+
if(activeTagFilter===tag) { activeTagFilter=null; }
|
|
1441
|
+
else { activeTagFilter=tag; }
|
|
1442
|
+
renderTagBar();
|
|
1443
|
+
renderEvents();
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
var expandedTagCats = {};
|
|
1447
|
+
function toggleTagCat(cn) {
|
|
1448
|
+
if(expandedTagCats[cn]) { expandedTagCats[cn]=false; }
|
|
1449
|
+
else { expandedTagCats[cn]=true; }
|
|
1450
|
+
renderTagBar();
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function renderTagBar() {
|
|
1454
|
+
var bar = document.getElementById('tag-bar');
|
|
1455
|
+
if(!T||!T.events) { bar.innerHTML=''; return; }
|
|
1456
|
+
var allEvs=T.events||[], tagCount={};
|
|
1457
|
+
for(var i=0;i<allEvs.length;i++) {
|
|
1458
|
+
var tags=allEvs[i].tags||[];
|
|
1459
|
+
for(var j=0;j<tags.length;j++) tagCount[tags[j]]=(tagCount[tags[j]]||0)+1;
|
|
1460
|
+
}
|
|
1461
|
+
var catTags={};
|
|
1462
|
+
for(var tag in tagCount) {
|
|
1463
|
+
var c=catOfTag(tag);
|
|
1464
|
+
if(!catTags[c]) catTags[c]=[];
|
|
1465
|
+
catTags[c].push({tag:tag,count:tagCount[tag]});
|
|
1466
|
+
}
|
|
1467
|
+
var catOrder=["模块","主题","阶段","动作","其他"];
|
|
1468
|
+
var h='';
|
|
1469
|
+
for(var ci=0;ci<catOrder.length;ci++) {
|
|
1470
|
+
var cn=catOrder[ci], items=catTags[cn];
|
|
1471
|
+
if(!items||!items.length) continue;
|
|
1472
|
+
items.sort(function(a,b){ return b.count-a.count; });
|
|
1473
|
+
var expand=!!expandedTagCats[cn];
|
|
1474
|
+
var limit=expand?items.length:Math.min(3,items.length);
|
|
1475
|
+
h+='<div class="tag-row"><span class="tag-cat">'+cn+'</span>';
|
|
1476
|
+
for(var ii=0;ii<limit;ii++) {
|
|
1477
|
+
var item=items[ii], isOn=activeTagFilter===item.tag;
|
|
1478
|
+
h+='<span class="tag-btn'+(isOn?' on':'')+'" data-tag="'+esc(item.tag)+'" onclick="setTagFilter(\''+esc(item.tag)+'\');event.stopPropagation()">'+esc(item.tag)+' <em>'+item.count+'</em></span>';
|
|
1479
|
+
}
|
|
1480
|
+
if(items.length>3&&!expand) h+='<span class="tag-more" onclick="toggleTagCat(\''+esc(cn)+'\');event.stopPropagation()">+'+(items.length-3)+'</span>';
|
|
1481
|
+
if(expand) h+='<span class="tag-more" onclick="toggleTagCat(\''+esc(cn)+'\');event.stopPropagation()">Collapse</span>';
|
|
1482
|
+
h+='</div>';
|
|
1483
|
+
}
|
|
1484
|
+
bar.innerHTML = h;
|
|
998
1485
|
}
|
|
999
1486
|
|
|
1000
1487
|
// ====== FLOATING PREVIEW ======
|
|
1001
|
-
function preview(e,path,from){
|
|
1488
|
+
function preview(e, path, from) {
|
|
1002
1489
|
e.preventDefault();
|
|
1003
|
-
var float=document.getElementById('float'),overlay=document.getElementById('overlay');
|
|
1004
|
-
var needFr=(from==='left');
|
|
1005
|
-
var hasFr=float.classList.contains('fr');
|
|
1006
|
-
|
|
1007
|
-
document.getElementById('ftitle').textContent=path;
|
|
1008
|
-
document.getElementById('float-body').textContent='
|
|
1009
|
-
fetch('/'+encodeURI(path)).then(function(r)
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}).
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1490
|
+
var float = document.getElementById('float'), overlay = document.getElementById('overlay');
|
|
1491
|
+
var needFr = (from==='left');
|
|
1492
|
+
var hasFr = float.classList.contains('fr');
|
|
1493
|
+
|
|
1494
|
+
document.getElementById('ftitle').textContent = path;
|
|
1495
|
+
document.getElementById('float-body').textContent = 'Loading...';
|
|
1496
|
+
fetch('/'+encodeURI(path)).then(function(r) {
|
|
1497
|
+
if(!r.ok) throw Error('HTTP '+r.status);
|
|
1498
|
+
return r.text();
|
|
1499
|
+
}).then(function(t) {
|
|
1500
|
+
t = t.replace(/<!--[\s\S]*?-->/g,'').trim();
|
|
1501
|
+
document.getElementById('float-body').textContent = t||'(empty file)';
|
|
1502
|
+
}).catch(function(err) {
|
|
1503
|
+
document.getElementById('float-body').textContent = 'Load failed: '+err.message;
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
if(!float.classList.contains('hide') && needFr!==hasFr) {
|
|
1016
1507
|
float.classList.add('hide');
|
|
1017
|
-
setTimeout(function(){
|
|
1018
|
-
if(needFr){float.classList.add('fr')}else{float.classList.remove('fr')}
|
|
1508
|
+
setTimeout(function() {
|
|
1509
|
+
if(needFr) { float.classList.add('fr'); } else { float.classList.remove('fr'); }
|
|
1019
1510
|
float.classList.remove('hide');
|
|
1020
|
-
},300);
|
|
1021
|
-
}else{
|
|
1022
|
-
if(needFr){float.classList.add('fr')}else{float.classList.remove('fr')}
|
|
1511
|
+
}, 300);
|
|
1512
|
+
} else {
|
|
1513
|
+
if(needFr) { float.classList.add('fr'); } else { float.classList.remove('fr'); }
|
|
1023
1514
|
float.classList.remove('hide');
|
|
1024
1515
|
}
|
|
1025
1516
|
overlay.classList.remove('hide');
|
|
1026
1517
|
}
|
|
1027
1518
|
|
|
1028
|
-
document.getElementById('float-close').addEventListener('click',function(){
|
|
1519
|
+
document.getElementById('float-close').addEventListener('click', function() {
|
|
1029
1520
|
document.getElementById('float').classList.add('hide');
|
|
1030
1521
|
document.getElementById('overlay').classList.add('hide');
|
|
1031
1522
|
});
|
|
1032
|
-
document.getElementById('overlay').addEventListener('click',function(){
|
|
1523
|
+
document.getElementById('overlay').addEventListener('click', function() {
|
|
1033
1524
|
document.getElementById('float').classList.add('hide');
|
|
1034
1525
|
document.getElementById('overlay').classList.add('hide');
|
|
1035
1526
|
});
|
|
1036
1527
|
|
|
1037
1528
|
// ====== TOKEN PANEL ======
|
|
1038
|
-
function loadTokenToday(){
|
|
1039
|
-
fetch('../data/token-summary.json').then(function(r){return r.json()}).then(function(d){
|
|
1529
|
+
function loadTokenToday() {
|
|
1530
|
+
fetch('../data/token-summary.json').then(function(r) { return r.json(); }).then(function(d) {
|
|
1040
1531
|
renderTokenToday(d);
|
|
1041
|
-
}).catch(function(){
|
|
1042
|
-
document.getElementById('token-today').childNodes[0].textContent='
|
|
1532
|
+
}).catch(function() {
|
|
1533
|
+
document.getElementById('token-today').childNodes[0].textContent = 'Usage: --';
|
|
1043
1534
|
});
|
|
1044
1535
|
}
|
|
1045
1536
|
|
|
1046
|
-
function renderTokenToday(d){
|
|
1047
|
-
var today=new Date().toISOString().substring(0,10);
|
|
1048
|
-
var td=d&&d.byDate?d.byDate[today]:null;
|
|
1049
|
-
var todayTokens=td?td.tokens:0;
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
var h='';
|
|
1062
|
-
if(d&&d.totalTokens>0){
|
|
1063
|
-
h+='<div class="tip-row"><span class="tip-name">今日</span><span class="tip-val">'+(todayTokens?todayTokens.toLocaleString()+' tk':'--')+'</span></div>';
|
|
1064
|
-
h+='<div class="tip-row"><span class="tip-name">累计</span><span class="tip-val">'+d.totalTokens.toLocaleString()+' tk</span></div>';
|
|
1065
|
-
h+='<div class="tip-row"><span class="tip-name">估算费用</span><span class="tip-val">¥'+(d.totalCost||0).toFixed(3)+'</span></div>';
|
|
1066
|
-
if(d.bySkill){
|
|
1537
|
+
function renderTokenToday(d) {
|
|
1538
|
+
var today = new Date().toISOString().substring(0,10);
|
|
1539
|
+
var td = d&&d.byDate ? d.byDate[today] : null;
|
|
1540
|
+
var todayTokens = td ? td.tokens : 0;
|
|
1541
|
+
|
|
1542
|
+
var label = document.getElementById('token-today');
|
|
1543
|
+
label.childNodes[0].textContent = 'Usage: '+(todayTokens ? todayTokens.toLocaleString()+' tk' : '--');
|
|
1544
|
+
|
|
1545
|
+
var tip = document.getElementById('token-tip');
|
|
1546
|
+
var h = '';
|
|
1547
|
+
if(d&&d.totalTokens>0) {
|
|
1548
|
+
h += '<div class="tip-row"><span class="tip-name">Today</span><span class="tip-val">'+(todayTokens?todayTokens.toLocaleString()+' tk':'--')+'</span></div>';
|
|
1549
|
+
h += '<div class="tip-row"><span class="tip-name">Total</span><span class="tip-val">'+d.totalTokens.toLocaleString()+' tk</span></div>';
|
|
1550
|
+
h += '<div class="tip-row"><span class="tip-name">Est. Cost</span><span class="tip-val">$'+(d.totalCost||0).toFixed(3)+'</span></div>';
|
|
1551
|
+
if(d.bySkill) {
|
|
1067
1552
|
var skills=[];
|
|
1068
|
-
for(var k in d.bySkill){skills.push({name:k,tokens:d.bySkill[k].tokens||0,calls:d.bySkill[k].calls||0})}
|
|
1069
|
-
skills.sort(function(a,b){return b.tokens-a.tokens});
|
|
1070
|
-
if(skills.length>0){
|
|
1071
|
-
h+='<div class="tip-divider"></div>';
|
|
1072
|
-
for(var i=0;i<skills.length;i++){
|
|
1073
|
-
h+='<div class="tip-row"><span class="tip-name">'+esc(skills[i].name)+'</span><span class="tip-val">'+skills[i].tokens.toLocaleString()+' tk
|
|
1553
|
+
for(var k in d.bySkill) { skills.push({name:k,tokens:d.bySkill[k].tokens||0,calls:d.bySkill[k].calls||0}); }
|
|
1554
|
+
skills.sort(function(a,b){ return b.tokens-a.tokens; });
|
|
1555
|
+
if(skills.length>0) {
|
|
1556
|
+
h += '<div class="tip-divider"></div>';
|
|
1557
|
+
for(var i=0;i<skills.length;i++) {
|
|
1558
|
+
h += '<div class="tip-row"><span class="tip-name">'+esc(skills[i].name)+'</span><span class="tip-val">'+skills[i].tokens.toLocaleString()+' tk x'+skills[i].calls+'</span></div>';
|
|
1074
1559
|
}
|
|
1075
1560
|
}
|
|
1076
1561
|
}
|
|
1077
|
-
}else{
|
|
1078
|
-
h+='<div class="tip-row"><span class="tip-name"
|
|
1562
|
+
} else {
|
|
1563
|
+
h += '<div class="tip-row"><span class="tip-name">No data</span></div>';
|
|
1079
1564
|
}
|
|
1080
|
-
tip.innerHTML=h;
|
|
1565
|
+
tip.innerHTML = h;
|
|
1081
1566
|
|
|
1082
|
-
|
|
1083
|
-
label.
|
|
1084
|
-
label.onmouseleave=function(){tip.classList.remove('show')};
|
|
1567
|
+
label.onmouseenter = function() { tip.classList.add('show'); };
|
|
1568
|
+
label.onmouseleave = function() { tip.classList.remove('show'); };
|
|
1085
1569
|
}
|
|
1086
1570
|
</script>
|
|
1087
1571
|
</body>
|