@tenyokjs/devdevian-cli 0.7.1-alpha.1 → 0.7.2-alpha.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 +4 -0
- package/VERSIONS.md +6 -0
- package/lib/conversation-links.mjs +13 -0
- package/lib/tui-data.mjs +4 -2
- package/lib/tui.mjs +52 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,10 @@ In full-text reading (**Space** on a reply or remark), **/** opens local text se
|
|
|
69
69
|
|
|
70
70
|
Active fields explain their input: Space stays a space, Enter adds a line in multiline fields, and Tab leaves the field. Project feedback accepts text and URLs; dedicated website/repository/additional links are in **People & links**. Additional links use one `Label | https://example.com` per line. Context hints stay below the editor on larger terminals and use the status/hint lines on compact ones. **Ctrl+S** checks the complete form and opens a preview; drafts autosave locally and publication still requires explicit confirmation.
|
|
71
71
|
|
|
72
|
+
Multiline fields show the current logical line and total line count. While editing, the command bar gives its space to the form; the caret stays visible as you add lines or move through the text. Previous lines remain part of the same field, including after Enter. On short terminals only a window of a long message is visible; use ↑/↓ to revisit earlier lines or Ctrl+E for the external editor.
|
|
73
|
+
|
|
74
|
+
Conversation overviews and publication previews display Demo/Repository URL fields alongside any labeled links, with duplicate destinations shown once.
|
|
75
|
+
|
|
72
76
|
## Meet Devi
|
|
73
77
|
|
|
74
78
|
Devi is the Devdevian mascot: a small ghost with a laptop and an orbit. The terminal uses colored ASCII art, so it needs no image protocol or image downloads. A compact drawing is used when space is limited; authentication codes, addresses and controls take priority over decoration.
|
package/VERSIONS.md
CHANGED
|
@@ -8,6 +8,12 @@ Every published npm artifact has a unique immutable version. We record each vers
|
|
|
8
8
|
|
|
9
9
|
**0.7.0 focuses on everyday terminal use**: discoverable quick actions, staged forms and resumable reading.
|
|
10
10
|
|
|
11
|
+
## 0.7.2-alpha.1 — Visible links and multiline writing
|
|
12
|
+
|
|
13
|
+
- Conversation overviews now include Demo and Repository URL fields alongside labeled links. Duplicate URLs appear once, preserving a supplied label; publication previews show the same destinations.
|
|
14
|
+
- Multiline editing follows the caret and displays the logical line position/count. Enter retains the preceding visible line when space permits. While editing, the inactive command bar yields its rows to the form, including at 60×18.
|
|
15
|
+
- Real terminal/API regressions cover saved Demo URLs together with labeled links, newline visibility, cursor movement and editing in compact terminals. No API/schema or credential changes.
|
|
16
|
+
|
|
11
17
|
## 0.7.1-alpha.1 — Clearer forms
|
|
12
18
|
|
|
13
19
|
- Added visible, keyboard-focusable Back / Next controls to staged forms. Tab reaches the controls at step boundaries, arrows choose an available action and Enter activates it. The final action is Preview; publication remains a separate confirmation. Ctrl+N/P shortcuts remain available.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Keep legacy URL fields visible alongside labeled links, without repeating a URL.
|
|
2
|
+
export function conversationLinks(post = {}) {
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
return [
|
|
5
|
+
...(post.links || []),
|
|
6
|
+
...(post.demo_url ? [{ label: "Demo", url: post.demo_url }] : []),
|
|
7
|
+
...(post.repo_url ? [{ label: "Repository", url: post.repo_url }] : []),
|
|
8
|
+
].filter((link) => {
|
|
9
|
+
if (!link.url || seen.has(link.url)) return false;
|
|
10
|
+
seen.add(link.url);
|
|
11
|
+
return true;
|
|
12
|
+
});
|
|
13
|
+
}
|
package/lib/tui-data.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadCatalog, isCatalog } from "./catalog.mjs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { conversationLinks } from "./conversation-links.mjs";
|
|
3
4
|
export function assertSnippet(code, hash) {
|
|
4
5
|
if (
|
|
5
6
|
createHash("sha256")
|
|
@@ -288,11 +289,12 @@ export function contentLines(route, data) {
|
|
|
288
289
|
.split("\n")
|
|
289
290
|
.map((line, index) => String(index + 1).padStart(4) + " " + line),
|
|
290
291
|
);
|
|
291
|
-
|
|
292
|
+
const links = conversationLinks(p);
|
|
293
|
+
if (links.length)
|
|
292
294
|
lines.push(
|
|
293
295
|
"",
|
|
294
296
|
"LINKS",
|
|
295
|
-
...
|
|
297
|
+
...links.map((link) => link.label + " · " + link.url),
|
|
296
298
|
);
|
|
297
299
|
if (p.archived_at || p.locked_at)
|
|
298
300
|
lines.push("", "Replies are paused for this conversation.");
|
package/lib/tui.mjs
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
import { contextActions } from "./context-actions.mjs";
|
|
56
56
|
import { editExternal } from "./external-editor.mjs";
|
|
57
57
|
import { markdownRows } from "./rich-text.mjs";
|
|
58
|
+
import { conversationLinks } from "./conversation-links.mjs";
|
|
58
59
|
import { problemMessage } from "./problems.mjs";
|
|
59
60
|
import { ReviewWorkspace, codeRange } from "./review-workspace.mjs";
|
|
60
61
|
import {
|
|
@@ -306,9 +307,14 @@ function FormView({ form, active, cursor, width, height, navFocus }) {
|
|
|
306
307
|
navRows = step && height >= 3 ? 1 : 0;
|
|
307
308
|
const editorHeight = Math.max(1, height - helpRows - navRows);
|
|
308
309
|
let selectedLine = rows.length;
|
|
310
|
+
let caretRow = null;
|
|
309
311
|
fields.forEach((field, index) => {
|
|
310
312
|
if (step && !step.indexes.includes(index)) return;
|
|
311
313
|
if (index === active) selectedLine = rows.length;
|
|
314
|
+
const value = form.values[field.key] || "";
|
|
315
|
+
const lineNumber = field.multiline
|
|
316
|
+
? graphemes(value).slice(0, cursor).join("").split("\n").length
|
|
317
|
+
: 0;
|
|
312
318
|
rows.push({
|
|
313
319
|
text: (index === active && !navFocus ? "› " : " ") + field.label,
|
|
314
320
|
color: index === active && !navFocus ? colors.purple : colors.muted,
|
|
@@ -324,7 +330,6 @@ function FormView({ form, active, cursor, width, height, navFocus }) {
|
|
|
324
330
|
color: index === active && !navFocus ? colors.blue : colors.text,
|
|
325
331
|
});
|
|
326
332
|
} else {
|
|
327
|
-
const value = form.values[field.key] || "";
|
|
328
333
|
const text =
|
|
329
334
|
index === active && !navFocus
|
|
330
335
|
? cursorLines(value, cursor, width - 3).lines
|
|
@@ -339,12 +344,18 @@ function FormView({ form, active, cursor, width, height, navFocus }) {
|
|
|
339
344
|
const visible = field.multiline
|
|
340
345
|
? Math.min(
|
|
341
346
|
index === active
|
|
342
|
-
? Math.max(1, Math.min(12, editorHeight -
|
|
347
|
+
? Math.max(1, Math.min(12, editorHeight - 1))
|
|
343
348
|
: 3,
|
|
344
349
|
Math.max(2, text.length),
|
|
345
350
|
)
|
|
346
351
|
: 1,
|
|
347
352
|
start = index === active ? Math.max(0, caretLine - visible + 1) : 0;
|
|
353
|
+
if (index === active && !navFocus && field.multiline) {
|
|
354
|
+
rows.at(-1).text += ` · line ${lineNumber}/${value.split("\n").length}`;
|
|
355
|
+
// Follow the caret, rather than anchoring the viewport to the field label.
|
|
356
|
+
// A newline keeps its preceding row visible whenever the terminal has room.
|
|
357
|
+
caretRow = rows.length + caretLine - start;
|
|
358
|
+
}
|
|
348
359
|
rows.push(
|
|
349
360
|
...text.slice(start, start + visible).map((line) => ({
|
|
350
361
|
text: " " + line,
|
|
@@ -369,13 +380,15 @@ function FormView({ form, active, cursor, width, height, navFocus }) {
|
|
|
369
380
|
color: colors.blue,
|
|
370
381
|
bold: true,
|
|
371
382
|
});
|
|
372
|
-
|
|
383
|
+
let offset = Math.max(
|
|
373
384
|
0,
|
|
374
385
|
Math.min(
|
|
375
386
|
selectedLine - (editorHeight >= 4 ? 1 : 0),
|
|
376
387
|
rows.length - editorHeight,
|
|
377
388
|
),
|
|
378
389
|
);
|
|
390
|
+
if (caretRow !== null && caretRow >= offset + editorHeight)
|
|
391
|
+
offset = caretRow - editorHeight + 1;
|
|
379
392
|
return h(
|
|
380
393
|
Box,
|
|
381
394
|
{ flexDirection: "column", height, overflow: "hidden" },
|
|
@@ -554,12 +567,11 @@ function documentRows(route, data, width, colors) {
|
|
|
554
567
|
...markdownRows(fence + "\n" + post.code + "\n" + fence, width, colors),
|
|
555
568
|
);
|
|
556
569
|
}
|
|
557
|
-
|
|
570
|
+
const links = conversationLinks(post);
|
|
571
|
+
if (links.length)
|
|
558
572
|
rows.push(
|
|
559
573
|
{ text: "LINKS", bold: true },
|
|
560
|
-
...
|
|
561
|
-
wrap(link.label + " · " + link.url, width),
|
|
562
|
-
),
|
|
574
|
+
...links.flatMap((link) => wrap(link.label + " · " + link.url, width)),
|
|
563
575
|
);
|
|
564
576
|
if (post.archived_at || post.locked_at)
|
|
565
577
|
rows.push({
|
|
@@ -708,7 +720,8 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
708
720
|
height = Math.max(1, rows - 1),
|
|
709
721
|
compact = width < 90 || effectiveAppearance.sidebar === "hidden",
|
|
710
722
|
sidebar = compact ? 0 : 25,
|
|
711
|
-
|
|
723
|
+
editingForm = !!form && !preview && !picker,
|
|
724
|
+
bodyHeight = Math.max(1, height - (editingForm ? 6 : 9)),
|
|
712
725
|
contentWidth = Math.max(1, width - sidebar - 6),
|
|
713
726
|
contentHeight = Math.max(1, bodyHeight - 6);
|
|
714
727
|
const key = JSON.stringify(route),
|
|
@@ -3150,6 +3163,10 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
3150
3163
|
? [payload.title || payload.name, ""]
|
|
3151
3164
|
: []),
|
|
3152
3165
|
payload.body || payload.reason || payload.description || "",
|
|
3166
|
+
...conversationLinks(payload).flatMap((link) => [
|
|
3167
|
+
"",
|
|
3168
|
+
link.label + " · " + link.url,
|
|
3169
|
+
]),
|
|
3153
3170
|
...(preview.catalog
|
|
3154
3171
|
? [
|
|
3155
3172
|
"",
|
|
@@ -3614,31 +3631,33 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
3614
3631
|
width,
|
|
3615
3632
|
color: error ? colors.red : notice ? colors.green : colors.muted,
|
|
3616
3633
|
}),
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3634
|
+
editingForm
|
|
3635
|
+
? null
|
|
3636
|
+
: h(
|
|
3637
|
+
Box,
|
|
3638
|
+
{
|
|
3639
|
+
height: 3,
|
|
3640
|
+
borderStyle: colors.frame,
|
|
3641
|
+
borderColor: focus === "prompt" ? colors.blue : colors.line,
|
|
3642
|
+
paddingX: 1,
|
|
3643
|
+
overflow: "hidden",
|
|
3644
|
+
},
|
|
3645
|
+
h(Text, { color: colors.purple, bold: true }, "› "),
|
|
3646
|
+
focus === "prompt"
|
|
3647
|
+
? h(InputText, {
|
|
3648
|
+
value: command,
|
|
3649
|
+
cursor: commandCursor,
|
|
3650
|
+
width: Math.max(1, width - 6),
|
|
3651
|
+
})
|
|
3652
|
+
: h(
|
|
3653
|
+
Text,
|
|
3654
|
+
{ color: colors.muted },
|
|
3655
|
+
clip(
|
|
3656
|
+
"Search or /command · Press / to type · X actions · , appearance",
|
|
3657
|
+
width - 6,
|
|
3658
|
+
),
|
|
3659
|
+
),
|
|
3660
|
+
),
|
|
3642
3661
|
h(TextLine, { text: hint, width, color: colors.muted }),
|
|
3643
3662
|
),
|
|
3644
3663
|
);
|