@tenyokjs/devdevian-cli 0.5.1-alpha.1 → 0.6.1-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 +30 -2
- package/VERSIONS.md +17 -1
- package/bin/devdevian.mjs +13 -1
- package/lib/catalog.mjs +611 -0
- package/lib/client.mjs +14 -3
- package/lib/context-actions.mjs +2 -1
- package/lib/drafts.mjs +11 -4
- package/lib/help.mjs +40 -0
- package/lib/picker.mjs +34 -12
- package/lib/tui-data.mjs +10 -2
- package/lib/tui-model.mjs +3 -1
- package/lib/tui.mjs +357 -32
- package/package.json +1 -1
package/lib/tui.mjs
CHANGED
|
@@ -48,6 +48,18 @@ import { editExternal } from "./external-editor.mjs";
|
|
|
48
48
|
import { markdownRows } from "./rich-text.mjs";
|
|
49
49
|
import { problemMessage } from "./problems.mjs";
|
|
50
50
|
import { ReviewWorkspace, codeRange } from "./review-workspace.mjs";
|
|
51
|
+
import {
|
|
52
|
+
catalogFields,
|
|
53
|
+
initialCatalog,
|
|
54
|
+
catalogDocument,
|
|
55
|
+
prepareCatalog,
|
|
56
|
+
publishCatalog,
|
|
57
|
+
saveCalendar,
|
|
58
|
+
validateFilter,
|
|
59
|
+
localTime,
|
|
60
|
+
viewerZone,
|
|
61
|
+
webURL,
|
|
62
|
+
} from "./catalog.mjs";
|
|
51
63
|
const h = React.createElement;
|
|
52
64
|
const itemsOf = (value) => (Array.isArray(value) ? value : value?.items || []);
|
|
53
65
|
const stamp = (value) => {
|
|
@@ -74,6 +86,8 @@ const labelOf = (route) =>
|
|
|
74
86
|
community: "Community",
|
|
75
87
|
feedback: "Code feedback",
|
|
76
88
|
thread: "Reply thread",
|
|
89
|
+
project: "Project",
|
|
90
|
+
event: "Event",
|
|
77
91
|
}[route.view] ||
|
|
78
92
|
"Workspace";
|
|
79
93
|
function openBrowser(url) {
|
|
@@ -92,6 +106,8 @@ function openBrowser(url) {
|
|
|
92
106
|
child.unref();
|
|
93
107
|
}
|
|
94
108
|
function fieldsFor(form) {
|
|
109
|
+
if (["catalog", "catalogFilter", "calendar"].includes(form.kind))
|
|
110
|
+
return catalogFields(form);
|
|
95
111
|
if (form.kind === "appearance") return appearanceFields;
|
|
96
112
|
if (form.kind === "publication")
|
|
97
113
|
return [
|
|
@@ -331,7 +347,15 @@ function listRows(route, data, index, width, colors, density = "comfortable") {
|
|
|
331
347
|
let title = item.title || item.name || "",
|
|
332
348
|
meta = "",
|
|
333
349
|
body = "";
|
|
334
|
-
if (route.view === "
|
|
350
|
+
if (route.view === "projects") {
|
|
351
|
+
title = item.name;
|
|
352
|
+
meta = `${item.stage} · #${item.topic} · ${item.founder_name}`;
|
|
353
|
+
body = item.tagline || item.description;
|
|
354
|
+
} else if (route.view === "events") {
|
|
355
|
+
title = (item.cancelled_at ? "[Cancelled] " : "") + item.title;
|
|
356
|
+
meta = `${stamp(item.start_at)} · ${item.organizer_name}`;
|
|
357
|
+
body = item.description;
|
|
358
|
+
} else if (route.view === "drafts") {
|
|
335
359
|
title = item.form.values.title || item.form.title;
|
|
336
360
|
meta = "Local draft · " + stamp(item.savedAt);
|
|
337
361
|
body =
|
|
@@ -423,6 +447,8 @@ function listRows(route, data, index, width, colors, density = "comfortable") {
|
|
|
423
447
|
});
|
|
424
448
|
}
|
|
425
449
|
function documentRows(route, data, width, colors) {
|
|
450
|
+
if (route.view === "project" || route.view === "event")
|
|
451
|
+
return markdownRows(catalogDocument(route, data), width, colors);
|
|
426
452
|
if (route.view !== "post" || (route.tab && route.tab !== "overview"))
|
|
427
453
|
return contentLines(route, data).flatMap((line) => wrap(line, width));
|
|
428
454
|
const post = data?.post;
|
|
@@ -804,6 +830,20 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
804
830
|
await queueDraft(() => drafts.remove(draftIdentity(value)));
|
|
805
831
|
}
|
|
806
832
|
async function restoreDraft(item) {
|
|
833
|
+
if (
|
|
834
|
+
item.form.kind === "catalog" &&
|
|
835
|
+
item.form.catalogProgress?.completedId
|
|
836
|
+
) {
|
|
837
|
+
navigate({
|
|
838
|
+
view: item.form.entity,
|
|
839
|
+
id: item.form.catalogProgress.completedId,
|
|
840
|
+
page: 1,
|
|
841
|
+
});
|
|
842
|
+
setNotice(
|
|
843
|
+
"This draft was already published. Opened the existing publication.",
|
|
844
|
+
);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
807
847
|
await task(async () => {
|
|
808
848
|
const saved = await drafts.read(item.id),
|
|
809
849
|
next = { ...saved.form, draftId: item.id };
|
|
@@ -842,6 +882,8 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
842
882
|
}
|
|
843
883
|
}
|
|
844
884
|
}
|
|
885
|
+
if (next.kind === "catalog")
|
|
886
|
+
next.topics = itemsOf(await api("/topics")).map((t) => [t.id, t.name]);
|
|
845
887
|
openForm(next);
|
|
846
888
|
setNotice(
|
|
847
889
|
"Restored local draft. Check the code selection before publishing.",
|
|
@@ -1059,6 +1101,128 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1059
1101
|
setCommandCursor(0);
|
|
1060
1102
|
}
|
|
1061
1103
|
}
|
|
1104
|
+
async function composeCatalog(entity, editing = false) {
|
|
1105
|
+
await task(async () => {
|
|
1106
|
+
const row = editing ? await api(`/${entity}s/${route.id}`) : {};
|
|
1107
|
+
if (editing && row.user_id !== user.id)
|
|
1108
|
+
throw Error("Only the owner can edit this publication.");
|
|
1109
|
+
const topics = itemsOf(await api("/topics")).map((t) => [t.id, t.name]);
|
|
1110
|
+
const values = initialCatalog(entity, row);
|
|
1111
|
+
if (!topics.some(([id]) => id === values.topic))
|
|
1112
|
+
values.topic = topics[0]?.[0] || "web";
|
|
1113
|
+
openForm({
|
|
1114
|
+
kind: "catalog",
|
|
1115
|
+
entity,
|
|
1116
|
+
entityId: row.id,
|
|
1117
|
+
revision: row.revision,
|
|
1118
|
+
existing: row,
|
|
1119
|
+
title: (editing ? "Edit your " : "Create a ") + entity,
|
|
1120
|
+
topics,
|
|
1121
|
+
values,
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
async function catalogFilter() {
|
|
1126
|
+
await task(async () => {
|
|
1127
|
+
const entity = route.view === "projects" ? "project" : "event";
|
|
1128
|
+
const topics = itemsOf(await api("/topics")).map((t) => [t.id, t.name]);
|
|
1129
|
+
openForm({
|
|
1130
|
+
kind: "catalogFilter",
|
|
1131
|
+
entity,
|
|
1132
|
+
title: "Filter " + route.view,
|
|
1133
|
+
topics,
|
|
1134
|
+
values: {
|
|
1135
|
+
query: route.query || "",
|
|
1136
|
+
topic: route.topic || "",
|
|
1137
|
+
stage: route.stage || "",
|
|
1138
|
+
author: route.author || "",
|
|
1139
|
+
status: route.status || "",
|
|
1140
|
+
date: route.date || "",
|
|
1141
|
+
timezone: route.timezone || viewerZone(),
|
|
1142
|
+
mine: route.mine || "",
|
|
1143
|
+
order: route.order || (entity === "project" ? "newest" : "earliest"),
|
|
1144
|
+
},
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
function catalogAction(id) {
|
|
1149
|
+
const row = current?.project || current?.event;
|
|
1150
|
+
if (!row) return;
|
|
1151
|
+
if (id === "edit") {
|
|
1152
|
+
composeCatalog(route.view, true);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (id === "conversation") {
|
|
1156
|
+
navigate({ view: "post", id: row.post_id, tab: "overview", page: 1 });
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (id === "calendar") {
|
|
1160
|
+
openForm({
|
|
1161
|
+
kind: "calendar",
|
|
1162
|
+
eventId: row.id,
|
|
1163
|
+
title: "Export calendar",
|
|
1164
|
+
values: { path: `devdevian-event-${row.id}.ics` },
|
|
1165
|
+
});
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (id === "attend") {
|
|
1169
|
+
const attending = !row.viewer_attending;
|
|
1170
|
+
askConfirm(
|
|
1171
|
+
attending ? "Attend this event?" : "Leave this event?",
|
|
1172
|
+
row.title,
|
|
1173
|
+
() =>
|
|
1174
|
+
task(async () => {
|
|
1175
|
+
await api(`/events/${row.id}/attendance`, {
|
|
1176
|
+
method: "PUT",
|
|
1177
|
+
body: { attending },
|
|
1178
|
+
});
|
|
1179
|
+
invalidate(
|
|
1180
|
+
attending
|
|
1181
|
+
? "You're attending. Your browser account sees the same update."
|
|
1182
|
+
: "You left the event.",
|
|
1183
|
+
);
|
|
1184
|
+
}),
|
|
1185
|
+
);
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (id === "lifecycle") {
|
|
1189
|
+
if (row.user_id !== user.id) {
|
|
1190
|
+
setError("Only the organizer can manage this event.");
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const action = row.cancelled_at ? "restore" : "cancel";
|
|
1194
|
+
askConfirm(
|
|
1195
|
+
action === "cancel" ? "Cancel your event?" : "Restore your event?",
|
|
1196
|
+
"Attendees will receive the event update.",
|
|
1197
|
+
() =>
|
|
1198
|
+
task(async () => {
|
|
1199
|
+
await api(`/events/${row.id}/lifecycle`, {
|
|
1200
|
+
method: "POST",
|
|
1201
|
+
body: { action, revision: row.revision },
|
|
1202
|
+
});
|
|
1203
|
+
invalidate("Event updated.");
|
|
1204
|
+
}),
|
|
1205
|
+
);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const target =
|
|
1209
|
+
id === "website"
|
|
1210
|
+
? row.url
|
|
1211
|
+
: id === "source"
|
|
1212
|
+
? row.repo
|
|
1213
|
+
: id === "meeting"
|
|
1214
|
+
? row.url
|
|
1215
|
+
: config.origin +
|
|
1216
|
+
(route.view === "project"
|
|
1217
|
+
? "/app#post/" + row.post_id
|
|
1218
|
+
: "/app#events/" + row.id);
|
|
1219
|
+
try {
|
|
1220
|
+
openBrowser(webURL(target));
|
|
1221
|
+
setNotice("Opened in your browser.");
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
setError(error.message);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1062
1226
|
function openAppearance() {
|
|
1063
1227
|
openForm({
|
|
1064
1228
|
kind: "appearance",
|
|
@@ -1234,6 +1398,28 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1234
1398
|
}
|
|
1235
1399
|
async function prepare() {
|
|
1236
1400
|
if (!form) return;
|
|
1401
|
+
if (form.kind === "catalogFilter") {
|
|
1402
|
+
await task(async () => {
|
|
1403
|
+
const values = validateFilter(form.values);
|
|
1404
|
+
setForm(null);
|
|
1405
|
+
navigate({ view: form.entity + "s", ...values, page: 1 });
|
|
1406
|
+
});
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (form.kind === "calendar") {
|
|
1410
|
+
await task(async () => {
|
|
1411
|
+
const path = await saveCalendar(
|
|
1412
|
+
api,
|
|
1413
|
+
root,
|
|
1414
|
+
form.eventId,
|
|
1415
|
+
form.values.path,
|
|
1416
|
+
);
|
|
1417
|
+
setForm(null);
|
|
1418
|
+
setFocus("content");
|
|
1419
|
+
setNotice("Calendar saved: " + clean(path));
|
|
1420
|
+
});
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1237
1423
|
if (form.kind === "appearance") {
|
|
1238
1424
|
await task(async () => {
|
|
1239
1425
|
const saved = await saveAppearance(form.values);
|
|
@@ -1272,6 +1458,23 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1272
1458
|
navigate({ view: "post", id, tab: "overview", page: 1 });
|
|
1273
1459
|
return;
|
|
1274
1460
|
}
|
|
1461
|
+
if (form.kind === "catalog") {
|
|
1462
|
+
if (form.catalogProgress?.uncertain)
|
|
1463
|
+
throw Error(
|
|
1464
|
+
"Publication outcome is unknown. Check your projects/events before creating another copy. Keep this draft for reference.",
|
|
1465
|
+
);
|
|
1466
|
+
const plan = await prepareCatalog(api, root, form);
|
|
1467
|
+
setPreview({
|
|
1468
|
+
catalog: plan,
|
|
1469
|
+
payload: plan.payload,
|
|
1470
|
+
title: form.title,
|
|
1471
|
+
method: form.entityId ? "PATCH" : "POST",
|
|
1472
|
+
url: `/${form.entity}s`,
|
|
1473
|
+
});
|
|
1474
|
+
setScroll(0);
|
|
1475
|
+
setYes(false);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1275
1478
|
let payload,
|
|
1276
1479
|
method = "POST",
|
|
1277
1480
|
url,
|
|
@@ -1363,10 +1566,22 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1363
1566
|
async function publish() {
|
|
1364
1567
|
if (!preview) return;
|
|
1365
1568
|
await task(async () => {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1569
|
+
let working = form;
|
|
1570
|
+
const response = preview.catalog
|
|
1571
|
+
? await publishCatalog(
|
|
1572
|
+
api,
|
|
1573
|
+
preview.catalog,
|
|
1574
|
+
form.catalogProgress,
|
|
1575
|
+
async (progress) => {
|
|
1576
|
+
working = { ...working, catalogProgress: progress };
|
|
1577
|
+
setForm(working);
|
|
1578
|
+
await queueDraft(() => drafts.save(working));
|
|
1579
|
+
},
|
|
1580
|
+
)
|
|
1581
|
+
: await api(preview.url, {
|
|
1582
|
+
method: preview.method,
|
|
1583
|
+
body: preview.payload,
|
|
1584
|
+
});
|
|
1370
1585
|
cache.current.clear();
|
|
1371
1586
|
let localWarning = "";
|
|
1372
1587
|
try {
|
|
@@ -1378,7 +1593,9 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1378
1593
|
setPreview(null);
|
|
1379
1594
|
setForm(null);
|
|
1380
1595
|
setScroll(0);
|
|
1381
|
-
if (preview.
|
|
1596
|
+
if (preview.catalog)
|
|
1597
|
+
navigate({ view: form.entity, id: response.id, page: 1 });
|
|
1598
|
+
else if (preview.url === "/posts")
|
|
1382
1599
|
navigate({ view: "post", id: response.id, tab: "overview", page: 1 });
|
|
1383
1600
|
else if (preview.url === "/communities")
|
|
1384
1601
|
navigate({ view: "community", id: response.id, page: 1 });
|
|
@@ -1396,6 +1613,11 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1396
1613
|
});
|
|
1397
1614
|
}
|
|
1398
1615
|
async function openSelected() {
|
|
1616
|
+
if (route.view === "projects" || route.view === "events") {
|
|
1617
|
+
if (selected)
|
|
1618
|
+
navigate({ view: route.view.slice(0, -1), id: selected.id, page: 1 });
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1399
1621
|
if (route.view === "community" && !current?.community?.is_member) return;
|
|
1400
1622
|
const item = selected;
|
|
1401
1623
|
if (!item) return;
|
|
@@ -1636,6 +1858,10 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1636
1858
|
}
|
|
1637
1859
|
function runAction(id) {
|
|
1638
1860
|
setActionIndex(null);
|
|
1861
|
+
if (id?.startsWith("catalog-")) {
|
|
1862
|
+
catalogAction(id.slice(8));
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1639
1865
|
if (id === "read") {
|
|
1640
1866
|
if (current?.post) {
|
|
1641
1867
|
setReading(selected);
|
|
@@ -1694,6 +1920,10 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1694
1920
|
);
|
|
1695
1921
|
return;
|
|
1696
1922
|
}
|
|
1923
|
+
if (parsed.command === "project" || parsed.command === "event") {
|
|
1924
|
+
navigate({ view: parsed.command, id: parsed.arg, page: 1 });
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1697
1927
|
if (parsed.command === "appearance") {
|
|
1698
1928
|
openAppearance();
|
|
1699
1929
|
return;
|
|
@@ -1711,14 +1941,16 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1711
1941
|
navigate({
|
|
1712
1942
|
view: parsed.command,
|
|
1713
1943
|
page: 1,
|
|
1714
|
-
...(
|
|
1944
|
+
...(["search", "projects", "events"].includes(parsed.command)
|
|
1945
|
+
? { query: parsed.arg }
|
|
1946
|
+
: {}),
|
|
1715
1947
|
});
|
|
1716
1948
|
} catch (e) {
|
|
1717
1949
|
setError(e.message);
|
|
1718
1950
|
}
|
|
1719
1951
|
}
|
|
1720
1952
|
function askQuit() {
|
|
1721
|
-
if (
|
|
1953
|
+
if (["appearance", "catalogFilter", "calendar"].includes(form?.kind)) {
|
|
1722
1954
|
exit();
|
|
1723
1955
|
return;
|
|
1724
1956
|
}
|
|
@@ -1865,6 +2097,22 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1865
2097
|
return;
|
|
1866
2098
|
}
|
|
1867
2099
|
if (form) {
|
|
2100
|
+
if (keyEvent.ctrl && input === "o" && form.kind === "catalog") {
|
|
2101
|
+
const active = fieldsFor(form)[field];
|
|
2102
|
+
if (["logo", "banner", "screenshots"].includes(active?.key)) {
|
|
2103
|
+
setPicker({ mediaKey: active.key });
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (
|
|
2108
|
+
["catalogFilter", "calendar"].includes(form.kind) &&
|
|
2109
|
+
keyEvent.escape
|
|
2110
|
+
) {
|
|
2111
|
+
setForm(null);
|
|
2112
|
+
setFocus("content");
|
|
2113
|
+
setError("");
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
1868
2116
|
if (form.kind === "appearance" && keyEvent.escape) {
|
|
1869
2117
|
setForm(null);
|
|
1870
2118
|
setFocus("content");
|
|
@@ -1944,6 +2192,14 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
1944
2192
|
return;
|
|
1945
2193
|
}
|
|
1946
2194
|
if (keyEvent.return && !active.multiline) {
|
|
2195
|
+
if (
|
|
2196
|
+
form.kind === "catalog" &&
|
|
2197
|
+
["logo", "banner", "screenshots"].includes(active.key) &&
|
|
2198
|
+
!form.values[active.key]
|
|
2199
|
+
) {
|
|
2200
|
+
setPicker({ mediaKey: active.key });
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
1947
2203
|
if (active.key === "file" && !form.values.file) {
|
|
1948
2204
|
setPicker("files");
|
|
1949
2205
|
return;
|
|
@@ -2029,6 +2285,17 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
2029
2285
|
setReviewFocus((v) => (v === "code" ? "notes" : "code"));
|
|
2030
2286
|
return;
|
|
2031
2287
|
}
|
|
2288
|
+
if (
|
|
2289
|
+
(input === "f" || input === "F") &&
|
|
2290
|
+
["projects", "events"].includes(route.view)
|
|
2291
|
+
) {
|
|
2292
|
+
catalogFilter();
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
if (input === "j" && route.view === "event") {
|
|
2296
|
+
catalogAction("attend");
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2032
2299
|
if (
|
|
2033
2300
|
reviewing &&
|
|
2034
2301
|
(input === "f" || input === "F") &&
|
|
@@ -2094,7 +2361,8 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
2094
2361
|
const prose =
|
|
2095
2362
|
route.view === "help" ||
|
|
2096
2363
|
(route.view === "post" && (!route.tab || route.tab === "overview")) ||
|
|
2097
|
-
(route.view === "community" && !current?.community?.is_member)
|
|
2364
|
+
(route.view === "community" && !current?.community?.is_member) ||
|
|
2365
|
+
["project", "event"].includes(route.view);
|
|
2098
2366
|
if (
|
|
2099
2367
|
keyEvent.upArrow ||
|
|
2100
2368
|
keyEvent.downArrow ||
|
|
@@ -2166,6 +2434,10 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
2166
2434
|
return;
|
|
2167
2435
|
}
|
|
2168
2436
|
if (input === "n") {
|
|
2437
|
+
if (["projects", "events"].includes(route.view)) {
|
|
2438
|
+
composeCatalog(route.view.slice(0, -1));
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2169
2441
|
if (route.view === "communities")
|
|
2170
2442
|
openForm({
|
|
2171
2443
|
kind: "community",
|
|
@@ -2194,6 +2466,10 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
2194
2466
|
return;
|
|
2195
2467
|
}
|
|
2196
2468
|
if (input === "e" || input === "E") {
|
|
2469
|
+
if (["project", "event"].includes(route.view)) {
|
|
2470
|
+
composeCatalog(route.view, true);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2197
2473
|
editSelected();
|
|
2198
2474
|
return;
|
|
2199
2475
|
}
|
|
@@ -2250,9 +2526,33 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
2250
2526
|
root,
|
|
2251
2527
|
width,
|
|
2252
2528
|
height: bodyHeight,
|
|
2253
|
-
initial: picker,
|
|
2529
|
+
initial: picker.mediaKey ? "files" : picker,
|
|
2530
|
+
media: !!picker.mediaKey,
|
|
2254
2531
|
onCancel: () => setPicker(null),
|
|
2255
2532
|
onSelect: (source) => {
|
|
2533
|
+
if (picker.mediaKey) {
|
|
2534
|
+
const key = picker.mediaKey;
|
|
2535
|
+
setForm((old) => ({
|
|
2536
|
+
...old,
|
|
2537
|
+
values: {
|
|
2538
|
+
...old.values,
|
|
2539
|
+
[key]:
|
|
2540
|
+
key === "screenshots"
|
|
2541
|
+
? [
|
|
2542
|
+
old.values[key] === "-" ? "" : old.values[key],
|
|
2543
|
+
source.path,
|
|
2544
|
+
]
|
|
2545
|
+
.filter(Boolean)
|
|
2546
|
+
.join("\n")
|
|
2547
|
+
: source.path,
|
|
2548
|
+
},
|
|
2549
|
+
}));
|
|
2550
|
+
setPicker(null);
|
|
2551
|
+
setNotice(
|
|
2552
|
+
"Image selected locally. It is uploaded only after confirmation.",
|
|
2553
|
+
);
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2256
2556
|
setForm((old) => ({
|
|
2257
2557
|
...old,
|
|
2258
2558
|
source,
|
|
@@ -2416,8 +2716,29 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
2416
2716
|
const raw = [
|
|
2417
2717
|
"AUDIENCE · " + audience,
|
|
2418
2718
|
"",
|
|
2419
|
-
...(payload.title
|
|
2719
|
+
...(payload.title || payload.name
|
|
2720
|
+
? [payload.title || payload.name, ""]
|
|
2721
|
+
: []),
|
|
2420
2722
|
payload.body || payload.reason || payload.description || "",
|
|
2723
|
+
...(preview.catalog
|
|
2724
|
+
? [
|
|
2725
|
+
"",
|
|
2726
|
+
...Object.entries(payload)
|
|
2727
|
+
.filter(([key]) => !["description", "details"].includes(key))
|
|
2728
|
+
.map(([key, value]) => `${key}: ${value}`),
|
|
2729
|
+
"",
|
|
2730
|
+
"DETAILS",
|
|
2731
|
+
JSON.stringify(payload.details, null, 2),
|
|
2732
|
+
"",
|
|
2733
|
+
"IMAGES TO UPLOAD",
|
|
2734
|
+
...(preview.catalog.files.length
|
|
2735
|
+
? preview.catalog.files.map(
|
|
2736
|
+
(file) =>
|
|
2737
|
+
`${file.path} · ${file.mime} · ${file.size} bytes`,
|
|
2738
|
+
)
|
|
2739
|
+
: ["No new images"]),
|
|
2740
|
+
]
|
|
2741
|
+
: []),
|
|
2421
2742
|
...(payload.code ? ["", "CODE TO PUBLISH", payload.code] : []),
|
|
2422
2743
|
...(payload.review_focus
|
|
2423
2744
|
? ["", "REVIEW FOCUS", payload.review_focus]
|
|
@@ -2776,27 +3097,31 @@ function App({ initialConfig, flags, root, initialAppearance }) {
|
|
|
2776
3097
|
);
|
|
2777
3098
|
}
|
|
2778
3099
|
const hint =
|
|
2779
|
-
form
|
|
2780
|
-
? "
|
|
2781
|
-
:
|
|
2782
|
-
? "
|
|
2783
|
-
:
|
|
2784
|
-
? "
|
|
2785
|
-
:
|
|
2786
|
-
? "
|
|
2787
|
-
: reading
|
|
2788
|
-
? "
|
|
2789
|
-
:
|
|
2790
|
-
? "
|
|
2791
|
-
:
|
|
2792
|
-
? "
|
|
2793
|
-
:
|
|
2794
|
-
? "
|
|
2795
|
-
:
|
|
2796
|
-
? "
|
|
2797
|
-
:
|
|
2798
|
-
? "
|
|
2799
|
-
:
|
|
3100
|
+
["projects", "events"].includes(route.view) && !form && !preview && !picker
|
|
3101
|
+
? "↑↓ select · Enter open · F filters · N new · [ ] pages · / search"
|
|
3102
|
+
: ["project", "event"].includes(route.view) && !form && !preview
|
|
3103
|
+
? "↑↓ scroll · X actions · E edit your publication · Esc back"
|
|
3104
|
+
: form?.kind === "appearance"
|
|
3105
|
+
? "← → choose · Tab next · Ctrl+S save · Esc cancel · 0 default preview"
|
|
3106
|
+
: actionIndex !== null
|
|
3107
|
+
? "Actions are scoped to the selected item · Esc closes"
|
|
3108
|
+
: reviewing && !form && !preview && !reading
|
|
3109
|
+
? "W code / discussion · Space select · C comment · X actions · F filter"
|
|
3110
|
+
: picker
|
|
3111
|
+
? "Project files stay local until publication is confirmed"
|
|
3112
|
+
: reading
|
|
3113
|
+
? "↑↓ scroll · R reply · E edit · D delete · Esc close"
|
|
3114
|
+
: preview
|
|
3115
|
+
? "← → choose · Enter confirm · Esc edit"
|
|
3116
|
+
: form
|
|
3117
|
+
? "Tab field · Ctrl+E editor · Ctrl+O files · Ctrl+G diff · Ctrl+S preview"
|
|
3118
|
+
: phase !== "ready"
|
|
3119
|
+
? "Enter connect / open browser · Ctrl+C exit"
|
|
3120
|
+
: route.view === "feedback"
|
|
3121
|
+
? "↑↓ select · R reply · E edit · D delete · S resolve · ! report"
|
|
3122
|
+
: route.view === "post"
|
|
3123
|
+
? "←→ tabs · ↑↓ select · R reply · E edit · D delete · Space read"
|
|
3124
|
+
: "↑↓ select · Enter open · Tab sections · [ ] pages · N new · / search";
|
|
2800
3125
|
return h(
|
|
2801
3126
|
ThemeContext.Provider,
|
|
2802
3127
|
{ value: colors },
|