@khanglvm/relay 0.6.0 → 0.8.0
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/docs/AGENT.md +20 -4
- package/package.json +1 -1
- package/skills/relay/SKILL.md +2 -0
- package/src/cli.js +314 -3
- package/src/server.js +89 -11
- package/src/spec.js +1 -1
- package/src/ui/annotate.css +93 -1
- package/src/ui/annotate.js +259 -11
- package/src/ui/app.js +60 -16
- package/src/ui/blocks.css +4 -0
- package/src/ui/blocks.js +54 -1
- package/src/ui/style.css +8 -2
package/src/ui/blocks.js
CHANGED
|
@@ -103,6 +103,55 @@
|
|
|
103
103
|
return out;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// GFM pipe tables — a header row of "| a | b |" cells immediately followed
|
|
107
|
+
// by a separator row of dashes (with optional ":" alignment markers).
|
|
108
|
+
// Rendered as a real .blk-table so it matches the dedicated table block.
|
|
109
|
+
function splitRow(row) {
|
|
110
|
+
const trimmed = row.trim().replace(/^\|/, '').replace(/\|$/, '');
|
|
111
|
+
return trimmed.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
|
|
112
|
+
}
|
|
113
|
+
function isSepRow(row) {
|
|
114
|
+
if (!row || row.indexOf('|') === -1) return false;
|
|
115
|
+
const cells = splitRow(row);
|
|
116
|
+
return cells.length > 0 && cells.every((c) => /^:?-{1,}:?$/.test(c));
|
|
117
|
+
}
|
|
118
|
+
function isTableStart(idx) {
|
|
119
|
+
const head = lines[idx];
|
|
120
|
+
return (
|
|
121
|
+
idx + 1 < lines.length &&
|
|
122
|
+
head.indexOf('|') !== -1 &&
|
|
123
|
+
!isSepRow(head) &&
|
|
124
|
+
isSepRow(lines[idx + 1])
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function consumeTable() {
|
|
128
|
+
const headers = splitRow(lines[i]);
|
|
129
|
+
const aligns = splitRow(lines[i + 1]).map((c) => {
|
|
130
|
+
const left = c.startsWith(':');
|
|
131
|
+
const right = c.endsWith(':');
|
|
132
|
+
return left && right ? 'center' : right ? 'right' : left ? 'left' : '';
|
|
133
|
+
});
|
|
134
|
+
i += 2;
|
|
135
|
+
const body = [];
|
|
136
|
+
while (i < lines.length && !/^\s*$/.test(lines[i]) && lines[i].indexOf('|') !== -1 && !isSepRow(lines[i])) {
|
|
137
|
+
body.push(splitRow(lines[i]));
|
|
138
|
+
i++;
|
|
139
|
+
}
|
|
140
|
+
const ncols = headers.length;
|
|
141
|
+
const alignAttr = (ci) => (aligns[ci] ? ` style="text-align:${aligns[ci]}"` : '');
|
|
142
|
+
const cell = (tag, ci, text) => `<${tag}${alignAttr(ci)}>${mdInline(esc(text == null ? '' : text))}</${tag}>`;
|
|
143
|
+
let out = '<div class="md-tablewrap"><table class="blk-table md-table"><thead><tr>';
|
|
144
|
+
for (let c = 0; c < ncols; c++) out += cell('th', c, headers[c]);
|
|
145
|
+
out += '</tr></thead><tbody>';
|
|
146
|
+
for (const r of body) {
|
|
147
|
+
out += '<tr>';
|
|
148
|
+
for (let c = 0; c < ncols; c++) out += cell('td', c, r[c]);
|
|
149
|
+
out += '</tr>';
|
|
150
|
+
}
|
|
151
|
+
out += '</tbody></table></div>';
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
106
155
|
while (i < lines.length) {
|
|
107
156
|
const line = lines[i];
|
|
108
157
|
|
|
@@ -140,6 +189,9 @@
|
|
|
140
189
|
continue;
|
|
141
190
|
}
|
|
142
191
|
|
|
192
|
+
// GFM pipe table (header row + dash separator)
|
|
193
|
+
if (isTableStart(i)) { html += consumeTable(); continue; }
|
|
194
|
+
|
|
143
195
|
// lists
|
|
144
196
|
if (/^(\s*)([-*]|\d+\.)\s+/.test(line)) { html += consumeList(); continue; }
|
|
145
197
|
|
|
@@ -155,7 +207,8 @@
|
|
|
155
207
|
!/^\s*---+\s*$/.test(lines[i]) &&
|
|
156
208
|
!/^#{1,3}\s+/.test(lines[i]) &&
|
|
157
209
|
!/^\s*>\s?/.test(lines[i]) &&
|
|
158
|
-
!/^(\s*)([-*]|\d+\.)\s+/.test(lines[i])
|
|
210
|
+
!/^(\s*)([-*]|\d+\.)\s+/.test(lines[i]) &&
|
|
211
|
+
!isTableStart(i)
|
|
159
212
|
) {
|
|
160
213
|
buf.push(lines[i]);
|
|
161
214
|
i++;
|
package/src/ui/style.css
CHANGED
|
@@ -73,7 +73,7 @@ body {
|
|
|
73
73
|
color: var(--fg);
|
|
74
74
|
font: 16px/1.6 var(--sans);
|
|
75
75
|
-webkit-font-smoothing: antialiased;
|
|
76
|
-
transition: background 200ms var(--ease), color 200ms var(--ease);
|
|
76
|
+
transition: background 200ms var(--ease), color 200ms var(--ease), padding 240ms var(--ease);
|
|
77
77
|
}
|
|
78
78
|
.wrap { max-width: 860px; margin: 0 auto; padding: 32px 20px 28px; }
|
|
79
79
|
|
|
@@ -193,7 +193,7 @@ input[type="text"]:focus, textarea:focus {
|
|
|
193
193
|
textarea { min-height: 90px; resize: vertical; }
|
|
194
194
|
|
|
195
195
|
.qnotewrap { margin-top: 10px; }
|
|
196
|
-
.qnotewrap .qnote { font-size: 0.88rem; padding: 8px 12px; }
|
|
196
|
+
.qnotewrap .qnote { font-size: 0.88rem; padding: 8px 12px; min-height: 52px; resize: vertical; }
|
|
197
197
|
|
|
198
198
|
.errmsg { color: var(--danger); font-size: 0.85rem; margin: 8px 0 0; display: none; }
|
|
199
199
|
.card.error .errmsg { display: block; }
|
|
@@ -220,8 +220,14 @@ textarea { min-height: 90px; resize: vertical; }
|
|
|
220
220
|
position: sticky; top: 0;
|
|
221
221
|
background: var(--danger); color: var(--danger-fg);
|
|
222
222
|
text-align: center; padding: 9px 16px; font-size: 0.9rem;
|
|
223
|
+
line-height: 1.45;
|
|
223
224
|
z-index: 10; display: none;
|
|
225
|
+
border-bottom: 1px solid transparent;
|
|
224
226
|
}
|
|
227
|
+
/* Calm status notes (timeout hand-back / lost connection). Never the red error
|
|
228
|
+
state — the board stays usable, so these only inform. */
|
|
229
|
+
.banner.info { background: var(--accent-soft); color: var(--accent); }
|
|
230
|
+
.banner.warn { background: var(--bg-sunken); color: var(--fg-2); border-bottom-color: var(--border-strong); }
|
|
225
231
|
|
|
226
232
|
/* Live-update toast: flagged after a `rly update` reload (see app.js).
|
|
227
233
|
Fixed top-center, accent-soft bg, accent text; JS auto-removes it. */
|