@harborppm/cli 0.82.0 → 0.84.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/dist/app.mjs +227 -98
- package/package.json +1 -1
package/dist/app.mjs
CHANGED
|
@@ -3085,7 +3085,36 @@ var init_barnacles = __esmMin((() => {
|
|
|
3085
3085
|
plural$7 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
3086
3086
|
}));
|
|
3087
3087
|
|
|
3088
|
+
// src/lib/local-day.ts
|
|
3089
|
+
init_indexes();
|
|
3090
|
+
init_premortem();
|
|
3091
|
+
init_barnacles();
|
|
3092
|
+
init_format();
|
|
3093
|
+
/**
|
|
3094
|
+
* The day a saved moment fell on where this person is: "2026-11-05T01:30:00Z"
|
|
3095
|
+
* is still the 4th in New York. A plain day ("2026-11-05") is itself.
|
|
3096
|
+
*/
|
|
3097
|
+
function localDay(stamp) {
|
|
3098
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(stamp)) return stamp;
|
|
3099
|
+
const d = new Date(stamp);
|
|
3100
|
+
return Number.isNaN(d.getTime()) ? stamp.slice(0, 10) : todayISO(d);
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3088
3103
|
// src/lib/task-keys.ts
|
|
3104
|
+
/**
|
|
3105
|
+
* Short keys for tasks: a project's key and a task's number within it,
|
|
3106
|
+
* "RR-42". They're what someone types in a branch name, a pull request or a
|
|
3107
|
+
* commit, so once given they don't change, except that a task moved to
|
|
3108
|
+
* another project takes that project's next number.
|
|
3109
|
+
*
|
|
3110
|
+
* The server settles keys on every write (a save, an agent's change, an
|
|
3111
|
+
* intake form, the nightly sweep) and on first load: projects without a key
|
|
3112
|
+
* get one, tasks without a number get the next ones in the order they were
|
|
3113
|
+
* made, and when two tabs gave out the same number at once the task made
|
|
3114
|
+
* first keeps it. In the browser, `write()` numbers only the tasks it adds or
|
|
3115
|
+
* moves (`numberNewTasks`), so a tab never invents numbers for old tasks that
|
|
3116
|
+
* the server would number differently.
|
|
3117
|
+
*/
|
|
3089
3118
|
/** "Ridge Runner" → "RR", "Website" → "WEB", "Q4 launch plan" → "QLP". */
|
|
3090
3119
|
function baseKey(name) {
|
|
3091
3120
|
const words = name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9 ]+/g, " ").split(/\s+/).filter((w) => /[A-Za-z]/.test(w));
|
|
@@ -3248,7 +3277,6 @@ function findByKey(data, text) {
|
|
|
3248
3277
|
}
|
|
3249
3278
|
var MAX_KEY, KEY, made, kept, KEY_TEXT;
|
|
3250
3279
|
var init_task_keys = __esmMin((() => {
|
|
3251
|
-
init_indexes();
|
|
3252
3280
|
MAX_KEY = 5;
|
|
3253
3281
|
KEY = /^[A-Z][A-Z0-9]{0,9}$/;
|
|
3254
3282
|
made = (a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
@@ -3365,11 +3393,9 @@ var init_charter_lines = __esmMin((() => {
|
|
|
3365
3393
|
}));
|
|
3366
3394
|
|
|
3367
3395
|
// src/lib/charter.ts
|
|
3368
|
-
init_barnacles();
|
|
3369
3396
|
init_thanks();
|
|
3370
3397
|
init_date_fns();
|
|
3371
3398
|
init_date_text();
|
|
3372
|
-
init_premortem();
|
|
3373
3399
|
init_charter_lines();
|
|
3374
3400
|
const CHARTER_LABEL = {
|
|
3375
3401
|
why: "Why",
|
|
@@ -3487,7 +3513,7 @@ init_date_fns();
|
|
|
3487
3513
|
init_blockers();
|
|
3488
3514
|
init_date_text();
|
|
3489
3515
|
init_premortem();
|
|
3490
|
-
const day$8 = (iso) => parseIso(iso
|
|
3516
|
+
const day$8 = (iso) => parseIso(localDay(iso));
|
|
3491
3517
|
const between = (a, b) => differenceInCalendarDays(day$8(b), day$8(a));
|
|
3492
3518
|
/** When the project was marked Done: its last move to Done in its health history. */
|
|
3493
3519
|
function finishedOn(project, tasks) {
|
|
@@ -3509,7 +3535,7 @@ function voyageLog(data, projectId) {
|
|
|
3509
3535
|
return {
|
|
3510
3536
|
task: t,
|
|
3511
3537
|
daysLate: between(t.dueDate, t.completedAt),
|
|
3512
|
-
waitedOn: blockerIds(t).map((id) => byId.get(id)).filter((b) => Boolean(b?.completedAt && b.completedAt
|
|
3538
|
+
waitedOn: blockerIds(t).map((id) => byId.get(id)).filter((b) => Boolean(b?.completedAt && localDay(b.completedAt) > t.dueDate.slice(0, 10))).sort((a, b) => b.completedAt.localeCompare(a.completedAt))[0] ?? null
|
|
3513
3539
|
};
|
|
3514
3540
|
}).filter((s) => s.daysLate > 0).sort((a, b) => b.daysLate - a.daysLate).slice(0, 3);
|
|
3515
3541
|
const raid = data.raidItems.filter((r) => r.projectId === projectId);
|
|
@@ -5626,6 +5652,7 @@ const ATTENTION = ["at-risk", "off-track"];
|
|
|
5626
5652
|
function matches$1(row, f) {
|
|
5627
5653
|
if (f.health === "attention" && !ATTENTION.includes(row.health)) return false;
|
|
5628
5654
|
if (f.ownerId && row.ownerId !== f.ownerId) return false;
|
|
5655
|
+
if (f.hideDone && row.health === "done") return false;
|
|
5629
5656
|
return true;
|
|
5630
5657
|
}
|
|
5631
5658
|
function milestoneOf(m, today) {
|
|
@@ -5642,7 +5669,8 @@ function milestoneOf(m, today) {
|
|
|
5642
5669
|
/**
|
|
5643
5670
|
* Programs by start date, each followed by its projects by start date unless
|
|
5644
5671
|
* collapsed. A filter keeps a program when it or any of its projects match,
|
|
5645
|
-
* and keeps the projects that match.
|
|
5672
|
+
* and keeps the projects that match. So with done work hidden, a Done
|
|
5673
|
+
* program still heads any of its projects that aren't done.
|
|
5646
5674
|
*/
|
|
5647
5675
|
function roadmapRows(data, portfolioId, filter, collapsed, today) {
|
|
5648
5676
|
const programs = data.programs.filter((p) => p.portfolioId === portfolioId).sort((a, b) => a.startDate.localeCompare(b.startDate) || byName(a.name, b.name));
|
|
@@ -5666,7 +5694,7 @@ function roadmapRows(data, portfolioId, filter, collapsed, today) {
|
|
|
5666
5694
|
baselineEnd: null,
|
|
5667
5695
|
slip: null,
|
|
5668
5696
|
waitsOn: blockerIds(program).filter((id) => programIds.has(id)),
|
|
5669
|
-
milestones: data.milestones.filter((m) => m.programId === program.id).sort((a, b) => a.dueDate.localeCompare(b.dueDate)).map((m) => milestoneOf(m, today)),
|
|
5697
|
+
milestones: data.milestones.filter((m) => m.programId === program.id && !(filter.hideDone && m.done)).sort((a, b) => a.dueDate.localeCompare(b.dueDate)).map((m) => milestoneOf(m, today)),
|
|
5670
5698
|
projectCount: projects.length
|
|
5671
5699
|
});
|
|
5672
5700
|
if (collapsed.has(program.id)) continue;
|
|
@@ -5689,6 +5717,18 @@ function roadmapRows(data, portfolioId, filter, collapsed, today) {
|
|
|
5689
5717
|
}
|
|
5690
5718
|
return out;
|
|
5691
5719
|
}
|
|
5720
|
+
/** How many programs and projects on this roadmap are Done: what hiding done work tucks away. */
|
|
5721
|
+
function roadmapDoneCount(data, portfolioId) {
|
|
5722
|
+
const programIds = /* @__PURE__ */ new Set();
|
|
5723
|
+
let count = 0;
|
|
5724
|
+
for (const p of data.programs) {
|
|
5725
|
+
if (p.portfolioId !== portfolioId) continue;
|
|
5726
|
+
programIds.add(p.id);
|
|
5727
|
+
if (p.health === "done") count++;
|
|
5728
|
+
}
|
|
5729
|
+
for (const p of data.projects) if (programIds.has(p.programId) && p.health === "done") count++;
|
|
5730
|
+
return count;
|
|
5731
|
+
}
|
|
5692
5732
|
|
|
5693
5733
|
// src/lib/search.ts
|
|
5694
5734
|
function tokensOf(query) {
|
|
@@ -8952,7 +8992,7 @@ function TaskLine({ task, projects, people, today, selected, picked = false, wid
|
|
|
8952
8992
|
});
|
|
8953
8993
|
}
|
|
8954
8994
|
/** Up/down within a list of `length`, wrapping neither way. */
|
|
8955
|
-
function step(at, delta, length) {
|
|
8995
|
+
function step$1(at, delta, length) {
|
|
8956
8996
|
if (length === 0) return 0;
|
|
8957
8997
|
return Math.max(0, Math.min(length - 1, at + delta));
|
|
8958
8998
|
}
|
|
@@ -9009,8 +9049,8 @@ function CalendarView({ active, width, height, go }) {
|
|
|
9009
9049
|
useInput((input, key) => {
|
|
9010
9050
|
if (inList) {
|
|
9011
9051
|
if (key.escape) setInList(false);
|
|
9012
|
-
else if (key.upArrow || input === "k") setAt((i) => step(i, -1, agenda.length));
|
|
9013
|
-
else if (key.downArrow || input === "j") setAt((i) => step(i, 1, agenda.length));
|
|
9052
|
+
else if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, agenda.length));
|
|
9053
|
+
else if (key.downArrow || input === "j") setAt((i) => step$1(i, 1, agenda.length));
|
|
9014
9054
|
else if (key.return && selected) go.openTask(selected.id);
|
|
9015
9055
|
else if (input === "x" && selected) moveTask(store, go, selected, "done");
|
|
9016
9056
|
return;
|
|
@@ -9699,12 +9739,12 @@ function HistoryView({ active, width, height, go }) {
|
|
|
9699
9739
|
});
|
|
9700
9740
|
};
|
|
9701
9741
|
useInput((input, key) => {
|
|
9702
|
-
if (key.upArrow || input === "k") setAt((i) => step(i, -1, rows.length));
|
|
9742
|
+
if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, rows.length));
|
|
9703
9743
|
else if (key.downArrow || input === "j") {
|
|
9704
9744
|
if (at >= rows.length - 2) loadMore();
|
|
9705
|
-
setAt((i) => step(i, 1, rows.length));
|
|
9706
|
-
} else if (key.pageDown) setAt((i) => step(i, 10, rows.length));
|
|
9707
|
-
else if (key.pageUp) setAt((i) => step(i, -10, rows.length));
|
|
9745
|
+
setAt((i) => step$1(i, 1, rows.length));
|
|
9746
|
+
} else if (key.pageDown) setAt((i) => step$1(i, 10, rows.length));
|
|
9747
|
+
else if (key.pageUp) setAt((i) => step$1(i, -10, rows.length));
|
|
9708
9748
|
else if (input === "s") go.pick("History of", [
|
|
9709
9749
|
...portfolios.map((p) => ({
|
|
9710
9750
|
label: p.name,
|
|
@@ -9824,6 +9864,36 @@ function HistoryView({ active, width, height, go }) {
|
|
|
9824
9864
|
});
|
|
9825
9865
|
}
|
|
9826
9866
|
|
|
9867
|
+
// src/lib/read-marks.ts
|
|
9868
|
+
/**
|
|
9869
|
+
* Inbox read marks. A harbor keeps one list of them (`readPingIds`), shared
|
|
9870
|
+
* by everyone in it, so each mark names whose it is: reading a mention that
|
|
9871
|
+
* names Sam and Aya clears it from Sam's Inbox only. Marks written before
|
|
9872
|
+
* they were personal (the bare line id) still count as read. Pure; the Inbox
|
|
9873
|
+
* makes the marks it hands the store, so none of this is in the first load.
|
|
9874
|
+
*/
|
|
9875
|
+
/** FNV-1a over the text, as eight hex digits. */
|
|
9876
|
+
function hash8(text) {
|
|
9877
|
+
let h = 2166136261;
|
|
9878
|
+
for (let i = 0; i < text.length; i++) {
|
|
9879
|
+
h ^= text.charCodeAt(i);
|
|
9880
|
+
h = Math.imul(h, 16777619);
|
|
9881
|
+
}
|
|
9882
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
9883
|
+
}
|
|
9884
|
+
/** A saved row id holds 64 characters. */
|
|
9885
|
+
const MAX_MARK = 64;
|
|
9886
|
+
/** The mark that says this person has read this Inbox line. */
|
|
9887
|
+
function readMark(personId, pingId) {
|
|
9888
|
+
const mark = `r${hash8(personId)}:${pingId}`;
|
|
9889
|
+
return mark.length <= MAX_MARK ? mark : `r${hash8(personId)}:#${hash8(pingId)}`;
|
|
9890
|
+
}
|
|
9891
|
+
/** Whether a person has read a line, given the harbor's marks. */
|
|
9892
|
+
function readBy(marks, personId) {
|
|
9893
|
+
const set = marks instanceof Set ? marks : new Set(marks ?? []);
|
|
9894
|
+
return (pingId) => set.has(pingId) || set.has(readMark(personId, pingId));
|
|
9895
|
+
}
|
|
9896
|
+
|
|
9827
9897
|
// src/lib/inbox.ts
|
|
9828
9898
|
init_date_fns();
|
|
9829
9899
|
init_indexes();
|
|
@@ -10091,22 +10161,22 @@ function InboxView({ active, width, height, go }) {
|
|
|
10091
10161
|
me,
|
|
10092
10162
|
store
|
|
10093
10163
|
]);
|
|
10094
|
-
const read =
|
|
10095
|
-
const unread = pings.filter((p) => !read
|
|
10164
|
+
const read = readBy(readIds, me);
|
|
10165
|
+
const unread = pings.filter((p) => !read(p.id)).length;
|
|
10096
10166
|
const [at, setAt] = useState(0);
|
|
10097
10167
|
const ping = pings[Math.min(at, pings.length - 1)];
|
|
10098
10168
|
const open = (p) => {
|
|
10099
|
-
store.getState().readPing(p.id);
|
|
10169
|
+
store.getState().readPing(readMark(me, p.id));
|
|
10100
10170
|
if (p.taskId) go.openTask(p.taskId);
|
|
10101
10171
|
else if (p.raidId) go.openPanel("raid", p.raidId);
|
|
10102
10172
|
else if (p.programId) go.openProgram(p.programId);
|
|
10103
10173
|
};
|
|
10104
10174
|
useInput((input, key) => {
|
|
10105
|
-
if (key.upArrow || input === "k") setAt((i) => step(i, -1, pings.length));
|
|
10106
|
-
else if (key.downArrow || input === "j") setAt((i) => step(i, 1, pings.length));
|
|
10175
|
+
if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, pings.length));
|
|
10176
|
+
else if (key.downArrow || input === "j") setAt((i) => step$1(i, 1, pings.length));
|
|
10107
10177
|
else if (key.return && ping) open(ping);
|
|
10108
|
-
else if (input === " " && ping) store.getState().readPing(ping.id);
|
|
10109
|
-
else if (input === "a")
|
|
10178
|
+
else if (input === " " && ping) store.getState().readPing(readMark(me, ping.id));
|
|
10179
|
+
else if (input === "a") store.getState().catchUp(pings.map((p) => readMark(me, p.id)));
|
|
10110
10180
|
}, { isActive: active });
|
|
10111
10181
|
const view = windowOf(pings, at, Math.max(1, height - 2));
|
|
10112
10182
|
return /* @__PURE__ */ jsxs(Box, {
|
|
@@ -10125,7 +10195,7 @@ function InboxView({ active, width, height, go }) {
|
|
|
10125
10195
|
}) : null,
|
|
10126
10196
|
view.items.map((p, i) => {
|
|
10127
10197
|
const on = active && view.start + i === at;
|
|
10128
|
-
const fresh = !read
|
|
10198
|
+
const fresh = !read(p.id);
|
|
10129
10199
|
return /* @__PURE__ */ jsxs(Text, {
|
|
10130
10200
|
color: on ? theme.selected : void 0,
|
|
10131
10201
|
bold: on || fresh,
|
|
@@ -12108,10 +12178,10 @@ function RowList({ title, right, rows, empty, active, width, height, onOpen, key
|
|
|
12108
12178
|
const [at, setAt] = useState(0);
|
|
12109
12179
|
const row = rows[Math.min(at, rows.length - 1)];
|
|
12110
12180
|
useInput((input, key) => {
|
|
12111
|
-
if (key.upArrow || input === "k") setAt((i) => step(i, -1, rows.length));
|
|
12112
|
-
else if (key.downArrow || input === "j") setAt((i) => step(i, 1, rows.length));
|
|
12113
|
-
else if (key.pageDown) setAt((i) => step(i, 10, rows.length));
|
|
12114
|
-
else if (key.pageUp) setAt((i) => step(i, -10, rows.length));
|
|
12181
|
+
if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, rows.length));
|
|
12182
|
+
else if (key.downArrow || input === "j") setAt((i) => step$1(i, 1, rows.length));
|
|
12183
|
+
else if (key.pageDown) setAt((i) => step$1(i, 10, rows.length));
|
|
12184
|
+
else if (key.pageUp) setAt((i) => step$1(i, -10, rows.length));
|
|
12115
12185
|
else if (key.return && row) onOpen(row);
|
|
12116
12186
|
else keys[input]?.(row);
|
|
12117
12187
|
}, { isActive: active });
|
|
@@ -12741,8 +12811,8 @@ function SearchBox({ width, onOpen, onClose }) {
|
|
|
12741
12811
|
]);
|
|
12742
12812
|
useInput((input, key) => {
|
|
12743
12813
|
if (key.escape) return onClose();
|
|
12744
|
-
if (key.upArrow) return setAt((i) => step(i, -1, hits.length));
|
|
12745
|
-
if (key.downArrow) return setAt((i) => step(i, 1, hits.length));
|
|
12814
|
+
if (key.upArrow) return setAt((i) => step$1(i, -1, hits.length));
|
|
12815
|
+
if (key.downArrow) return setAt((i) => step$1(i, 1, hits.length));
|
|
12746
12816
|
if (key.return) {
|
|
12747
12817
|
const hit = hits[Math.min(at, hits.length - 1)];
|
|
12748
12818
|
if (hit) onOpen(hit.item.id);
|
|
@@ -13000,8 +13070,8 @@ function Palette({ width, base, open, onClose }) {
|
|
|
13000
13070
|
const hits = useMemo(() => matchCommands(all, query, 12), [all, query]);
|
|
13001
13071
|
useInput((input, key) => {
|
|
13002
13072
|
if (key.escape) return onClose();
|
|
13003
|
-
if (key.upArrow) return setAt((i) => step(i, -1, hits.length));
|
|
13004
|
-
if (key.downArrow) return setAt((i) => step(i, 1, hits.length));
|
|
13073
|
+
if (key.upArrow) return setAt((i) => step$1(i, -1, hits.length));
|
|
13074
|
+
if (key.downArrow) return setAt((i) => step$1(i, 1, hits.length));
|
|
13005
13075
|
if (key.return) {
|
|
13006
13076
|
const hit = hits[Math.min(at, hits.length - 1)];
|
|
13007
13077
|
onClose();
|
|
@@ -13108,8 +13178,8 @@ function PremortemPanel({ id, width, height, go, active, onClose }) {
|
|
|
13108
13178
|
useInput((input, key) => {
|
|
13109
13179
|
if (key.escape) return onClose();
|
|
13110
13180
|
if (!project || !pm) return;
|
|
13111
|
-
if (key.upArrow || input === "k") return setAt((i) => step(i, -1, rows.length));
|
|
13112
|
-
if (key.downArrow || input === "j") return setAt((i) => step(i, 1, rows.length));
|
|
13181
|
+
if (key.upArrow || input === "k") return setAt((i) => step$1(i, -1, rows.length));
|
|
13182
|
+
if (key.downArrow || input === "j") return setAt((i) => step$1(i, 1, rows.length));
|
|
13113
13183
|
if (key.return && row?.risk) return go.openPanel("raid", row.risk.id);
|
|
13114
13184
|
if (viewOnly) return;
|
|
13115
13185
|
if (pm.phase === "none") {
|
|
@@ -23485,25 +23555,39 @@ var init_focus = __esmMin((() => {
|
|
|
23485
23555
|
// src/lib/recurrence.ts
|
|
23486
23556
|
/** The due date one interval after `from`. */
|
|
23487
23557
|
function nextDueDate(from, repeat) {
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23558
|
+
return iso(step(/* @__PURE__ */ new Date(`${from}T12:00:00`), repeat, 1));
|
|
23559
|
+
}
|
|
23560
|
+
/**
|
|
23561
|
+
* The next date in a series that started on `due`, at least one interval
|
|
23562
|
+
* on and no earlier than `today`. A weekly Monday meeting ticked off late on
|
|
23563
|
+
* Wednesday comes back the next Monday, so the series keeps its day; a
|
|
23564
|
+
* monthly one on the 31st keeps the 31st where the month has one.
|
|
23565
|
+
*/
|
|
23566
|
+
function nextSeriesDate(due, repeat, today) {
|
|
23567
|
+
const start = /* @__PURE__ */ new Date(`${due}T12:00:00`);
|
|
23568
|
+
let n = 1;
|
|
23569
|
+
let next = iso(step(start, repeat, n));
|
|
23570
|
+
while (next < today && n < 2e3) next = iso(step(start, repeat, ++n));
|
|
23571
|
+
return next;
|
|
23492
23572
|
}
|
|
23493
23573
|
/**
|
|
23494
23574
|
* The next occurrence of a repeating task once this one is done: same
|
|
23495
|
-
* title, owner, priority and checklist (unticked),
|
|
23496
|
-
* the
|
|
23497
|
-
*
|
|
23575
|
+
* title, owner, priority and checklist (unticked), back in To do. It is due
|
|
23576
|
+
* on the series' next date that isn't already past (see `nextSeriesDate`),
|
|
23577
|
+
* or one interval after today when the task had no due date. Null when the
|
|
23578
|
+
* task does not repeat, or when `siblings` (the project's tasks) already
|
|
23579
|
+
* hold an open copy of the series due on or after that date, so finishing,
|
|
23580
|
+
* reopening and finishing again doesn't queue a second one.
|
|
23498
23581
|
*/
|
|
23499
|
-
function nextOccurrence(task, id, today, createdAt) {
|
|
23582
|
+
function nextOccurrence(task, id, today, createdAt, siblings = []) {
|
|
23500
23583
|
if (!task.repeat) return null;
|
|
23501
|
-
const
|
|
23584
|
+
const dueDate = task.dueDate ? nextSeriesDate(task.dueDate, task.repeat, today) : nextDueDate(today, task.repeat);
|
|
23585
|
+
if (siblings.some((t) => t.status !== "done" && t.repeat === task.repeat && t.title === task.title && (t.dueDate ?? "") >= dueDate)) return null;
|
|
23502
23586
|
return {
|
|
23503
23587
|
...task,
|
|
23504
23588
|
id,
|
|
23505
23589
|
status: "todo",
|
|
23506
|
-
dueDate
|
|
23590
|
+
dueDate,
|
|
23507
23591
|
createdAt,
|
|
23508
23592
|
completedAt: null,
|
|
23509
23593
|
blockedById: null,
|
|
@@ -23514,7 +23598,7 @@ function nextOccurrence(task, id, today, createdAt) {
|
|
|
23514
23598
|
}))
|
|
23515
23599
|
};
|
|
23516
23600
|
}
|
|
23517
|
-
var REPEAT_LABEL, iso;
|
|
23601
|
+
var REPEAT_LABEL, iso, step;
|
|
23518
23602
|
var init_recurrence = __esmMin((() => {
|
|
23519
23603
|
init_date_fns();
|
|
23520
23604
|
REPEAT_LABEL = {
|
|
@@ -23525,6 +23609,7 @@ var init_recurrence = __esmMin((() => {
|
|
|
23525
23609
|
iso = (d) => {
|
|
23526
23610
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
23527
23611
|
};
|
|
23612
|
+
step = (d, repeat, n) => repeat === "weekly" ? addDays(d, 7 * n) : repeat === "biweekly" ? addDays(d, 14 * n) : addMonths$1(d, n);
|
|
23528
23613
|
}));
|
|
23529
23614
|
|
|
23530
23615
|
// src/lib/assignment.ts
|
|
@@ -23700,7 +23785,7 @@ function tasksSlice({ set, get, write }) {
|
|
|
23700
23785
|
return stampAssignment(row, finalTask, get().currentUserId, stamp());
|
|
23701
23786
|
});
|
|
23702
23787
|
const finished = task && patch.status === "done" && task.status !== "done" ? tasks.find((t) => t.id === id) : void 0;
|
|
23703
|
-
const follow = finished ? nextOccurrence(finished, uid("t"), todayISO(), stamp()) : null;
|
|
23788
|
+
const follow = finished ? nextOccurrence(finished, uid("t"), todayISO(), stamp(), tasksOf(tasks, finished.projectId)) : null;
|
|
23704
23789
|
write({
|
|
23705
23790
|
tasks: follow ? [...tasks, follow] : tasks,
|
|
23706
23791
|
comments: newComments.length ? [...get().comments, ...newComments] : get().comments,
|
|
@@ -23732,7 +23817,7 @@ function tasksSlice({ set, get, write }) {
|
|
|
23732
23817
|
created: false,
|
|
23733
23818
|
currentUserId: get().currentUserId
|
|
23734
23819
|
});
|
|
23735
|
-
const follow = status === "done" ? nextOccurrence(finalRow, uid("t"), todayISO(), stamp()) : null;
|
|
23820
|
+
const follow = status === "done" ? nextOccurrence(finalRow, uid("t"), todayISO(), stamp(), tasksOf(get().tasks, task.projectId)) : null;
|
|
23736
23821
|
const moved = get().tasks.map((row) => row.id === id ? finalRow : row);
|
|
23737
23822
|
write({
|
|
23738
23823
|
tasks: follow ? [...moved, follow] : moved,
|
|
@@ -23851,6 +23936,19 @@ function tasksSlice({ set, get, write }) {
|
|
|
23851
23936
|
} : i)
|
|
23852
23937
|
} : t) });
|
|
23853
23938
|
},
|
|
23939
|
+
renameChecklistItem: (taskId, itemId, title) => {
|
|
23940
|
+
const name = title.trim();
|
|
23941
|
+
const item = get().tasks.find((t) => t.id === taskId)?.checklist?.find((i) => i.id === itemId);
|
|
23942
|
+
if (!name || !item || item.title === name) return;
|
|
23943
|
+
set({ tasks: get().tasks.map((t) => t.id === taskId ? {
|
|
23944
|
+
...t,
|
|
23945
|
+
touchedAt: stamp(),
|
|
23946
|
+
checklist: (t.checklist ?? []).map((i) => i.id === itemId ? {
|
|
23947
|
+
...i,
|
|
23948
|
+
title: name
|
|
23949
|
+
} : i)
|
|
23950
|
+
} : t) });
|
|
23951
|
+
},
|
|
23854
23952
|
removeChecklistItem: (taskId, itemId) => {
|
|
23855
23953
|
set({ tasks: get().tasks.map((t) => t.id === taskId ? {
|
|
23856
23954
|
...t,
|
|
@@ -23924,15 +24022,15 @@ function tasksSlice({ set, get, write }) {
|
|
|
23924
24022
|
github: withRef(t.github, ref)
|
|
23925
24023
|
} : t) });
|
|
23926
24024
|
},
|
|
23927
|
-
readPing: (
|
|
24025
|
+
readPing: (mark) => {
|
|
23928
24026
|
const ids = get().readPingIds ?? [];
|
|
23929
|
-
if (ids.includes(
|
|
23930
|
-
set({ readPingIds: [...ids,
|
|
24027
|
+
if (ids.includes(mark)) return;
|
|
24028
|
+
set({ readPingIds: [...ids, mark] });
|
|
23931
24029
|
},
|
|
23932
|
-
catchUp: (
|
|
23933
|
-
if (
|
|
24030
|
+
catchUp: (marks) => {
|
|
24031
|
+
if (marks.length === 0) return;
|
|
23934
24032
|
const have = new Set(get().readPingIds ?? []);
|
|
23935
|
-
for (const
|
|
24033
|
+
for (const mark of marks) have.add(mark);
|
|
23936
24034
|
set({ readPingIds: [...have] });
|
|
23937
24035
|
},
|
|
23938
24036
|
tickVoyage: (ids) => {
|
|
@@ -24001,7 +24099,7 @@ function tasksSlice({ set, get, write }) {
|
|
|
24001
24099
|
})));
|
|
24002
24100
|
const final = stampAssignment(row, automated, me, at);
|
|
24003
24101
|
if (final.status === "done" && row.status !== "done") {
|
|
24004
|
-
const follow = nextOccurrence(final, uid("t"), todayISO(), at);
|
|
24102
|
+
const follow = nextOccurrence(final, uid("t"), todayISO(), at, [...tasksOf(s.tasks, row.projectId), ...follows]);
|
|
24005
24103
|
if (follow) follows.push(follow);
|
|
24006
24104
|
}
|
|
24007
24105
|
return final;
|
|
@@ -24020,7 +24118,8 @@ function tasksSlice({ set, get, write }) {
|
|
|
24020
24118
|
toggleFocus: (taskId) => {
|
|
24021
24119
|
const personId = get().currentUserId;
|
|
24022
24120
|
const date = todayISO();
|
|
24023
|
-
const
|
|
24121
|
+
const live = byId(get().tasks);
|
|
24122
|
+
const ids = ((get().ropes ?? []).find((r) => r.personId === personId && r.date === date)?.taskIds ?? []).filter((id) => live.has(id));
|
|
24024
24123
|
if (ids.includes(taskId)) {
|
|
24025
24124
|
set({ ropes: upsertFocus(get().ropes, personId, date, ids.filter((id) => id !== taskId)) });
|
|
24026
24125
|
return "removed";
|
|
@@ -24120,6 +24219,7 @@ var init_tasks = __esmMin((() => {
|
|
|
24120
24219
|
init_work();
|
|
24121
24220
|
init_shared();
|
|
24122
24221
|
init_recurrence();
|
|
24222
|
+
init_indexes();
|
|
24123
24223
|
init_assignment();
|
|
24124
24224
|
init_bulk();
|
|
24125
24225
|
init_task_people();
|
|
@@ -27317,8 +27417,8 @@ function DockPanel({ id, width, height, go, active, onClose }) {
|
|
|
27317
27417
|
useInput((input, key) => {
|
|
27318
27418
|
if (key.escape) return onClose();
|
|
27319
27419
|
if (!project) return;
|
|
27320
|
-
if (key.upArrow || input === "k") return setAt((i) => step(i, -1, changes.length));
|
|
27321
|
-
if (key.downArrow || input === "j") return setAt((i) => step(i, 1, changes.length));
|
|
27420
|
+
if (key.upArrow || input === "k") return setAt((i) => step$1(i, -1, changes.length));
|
|
27421
|
+
if (key.downArrow || input === "j") return setAt((i) => step$1(i, 1, changes.length));
|
|
27322
27422
|
if (input === "a" || input === "n") return newChange();
|
|
27323
27423
|
if (input === "d" && changes.length) {
|
|
27324
27424
|
const i = Math.min(at, changes.length - 1);
|
|
@@ -27496,8 +27596,8 @@ function LifeboatsPanel({ id, width, height, go, active, onClose }) {
|
|
|
27496
27596
|
};
|
|
27497
27597
|
useInput((input, key) => {
|
|
27498
27598
|
if (key.escape) return onClose();
|
|
27499
|
-
if (key.upArrow || input === "k") return setAt((i) => step(i, -1, lines.length));
|
|
27500
|
-
if (key.downArrow || input === "j") return setAt((i) => step(i, 1, lines.length));
|
|
27599
|
+
if (key.upArrow || input === "k") return setAt((i) => step$1(i, -1, lines.length));
|
|
27600
|
+
if (key.downArrow || input === "j") return setAt((i) => step$1(i, 1, lines.length));
|
|
27501
27601
|
if (key.return && line) return choose(line);
|
|
27502
27602
|
}, { isActive: active !== false });
|
|
27503
27603
|
const rows = Math.max(1, Math.floor((height - 6 - notes.length) / 3));
|
|
@@ -27629,8 +27729,8 @@ function ShorePanel({ id, width, height, go, active, onClose }) {
|
|
|
27629
27729
|
useInput((input, key) => {
|
|
27630
27730
|
if (key.escape) return onClose();
|
|
27631
27731
|
if (!answer) return;
|
|
27632
|
-
if (key.upArrow || input === "k") return setAt((i) => step(i, -1, SHORE_KINDS.length));
|
|
27633
|
-
if (key.downArrow || input === "j") return setAt((i) => step(i, 1, SHORE_KINDS.length));
|
|
27732
|
+
if (key.upArrow || input === "k") return setAt((i) => step$1(i, -1, SHORE_KINDS.length));
|
|
27733
|
+
if (key.downArrow || input === "j") return setAt((i) => step$1(i, 1, SHORE_KINDS.length));
|
|
27634
27734
|
if (viewOnly) return;
|
|
27635
27735
|
if (input === "p") return go.ask("The channel's incoming-webhook address", (text) => {
|
|
27636
27736
|
const url = text.trim();
|
|
@@ -27789,8 +27889,8 @@ function WelcomeView({ when, active, width, height, go, onDone }) {
|
|
|
27789
27889
|
const look = w.lookFirst;
|
|
27790
27890
|
useInput((input, key) => {
|
|
27791
27891
|
if (key.escape || input === "g") return onDone();
|
|
27792
|
-
if (key.upArrow || input === "k") setAt((i) => step(i, -1, look.length));
|
|
27793
|
-
else if (key.downArrow || input === "j") setAt((i) => step(i, 1, look.length));
|
|
27892
|
+
if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, look.length));
|
|
27893
|
+
else if (key.downArrow || input === "j") setAt((i) => step$1(i, 1, look.length));
|
|
27794
27894
|
else if (key.return) {
|
|
27795
27895
|
const l = look[at];
|
|
27796
27896
|
if (!l) return onDone();
|
|
@@ -28199,11 +28299,11 @@ function ProgramView({ programId, active, width, height, go, onBack }) {
|
|
|
28199
28299
|
return setShowDone((v) => !v);
|
|
28200
28300
|
}
|
|
28201
28301
|
if (side === "projects") {
|
|
28202
|
-
if (key.upArrow || input === "k") setPAt((i) => step(i, -1, mine.length));
|
|
28203
|
-
else if (key.downArrow || input === "j") setPAt((i) => step(i, 1, mine.length));
|
|
28302
|
+
if (key.upArrow || input === "k") setPAt((i) => step$1(i, -1, mine.length));
|
|
28303
|
+
else if (key.downArrow || input === "j") setPAt((i) => step$1(i, 1, mine.length));
|
|
28204
28304
|
else if (key.return && project) go.openProject(project.id);
|
|
28205
|
-
} else if (key.upArrow || input === "k") setMAt((i) => step(i, -1, marks.length));
|
|
28206
|
-
else if (key.downArrow || input === "j") setMAt((i) => step(i, 1, marks.length));
|
|
28305
|
+
} else if (key.upArrow || input === "k") setMAt((i) => step$1(i, -1, marks.length));
|
|
28306
|
+
else if (key.downArrow || input === "j") setMAt((i) => step$1(i, 1, marks.length));
|
|
28207
28307
|
else if (key.return && mark) go.openPanel("milestone", mark.id);
|
|
28208
28308
|
else if (input === " " && mark) store.getState().toggleMilestone(mark.id);
|
|
28209
28309
|
else if (input === "d" && mark) go.confirm(`Delete the milestone “${mark.name}”? u undoes it.`, () => store.getState().deleteMilestone(mark.id), "Delete");
|
|
@@ -28427,8 +28527,8 @@ function PortfolioView({ portfolioId, active, width, height, go, onBack }) {
|
|
|
28427
28527
|
if (input === "n") return go.create("program", { portfolioId });
|
|
28428
28528
|
if (input === "v") return setLayout((l) => l === "board" ? "bands" : "board");
|
|
28429
28529
|
if (input === "D" && split.done.length > 0) return setShowDone((v) => !v);
|
|
28430
|
-
if (key.upArrow || input === "k") setAt((i) => step(i, -1, list.length));
|
|
28431
|
-
else if (key.downArrow || input === "j") setAt((i) => step(i, 1, list.length));
|
|
28530
|
+
if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, list.length));
|
|
28531
|
+
else if (key.downArrow || input === "j") setAt((i) => step$1(i, 1, list.length));
|
|
28432
28532
|
else if (key.return && program) go.openProgram(program.id);
|
|
28433
28533
|
}, { isActive: active });
|
|
28434
28534
|
if (!portfolio) return /* @__PURE__ */ jsx(Text, {
|
|
@@ -28817,8 +28917,8 @@ function ProjectView({ projectId, active, width, height, go, onBack }) {
|
|
|
28817
28917
|
if (input === "v") return setMode((m) => m === "board" ? "list" : m === "list" ? "budget" : m === "budget" ? "files" : "board");
|
|
28818
28918
|
if (mode === "files") {
|
|
28819
28919
|
const f = files[Math.min(fileAt, files.length - 1)];
|
|
28820
|
-
if (key.upArrow || input === "k") setFileAt((i) => step(i, -1, files.length));
|
|
28821
|
-
else if (key.downArrow || input === "j") setFileAt((i) => step(i, 1, files.length));
|
|
28920
|
+
if (key.upArrow || input === "k") setFileAt((i) => step$1(i, -1, files.length));
|
|
28921
|
+
else if (key.downArrow || input === "j") setFileAt((i) => step$1(i, 1, files.length));
|
|
28822
28922
|
else if (key.return && f) go.openTask(f.task.id);
|
|
28823
28923
|
return;
|
|
28824
28924
|
}
|
|
@@ -28862,8 +28962,8 @@ function ProjectView({ projectId, active, width, height, go, onBack }) {
|
|
|
28862
28962
|
if (mode === "budget") {
|
|
28863
28963
|
const entries = project ? budgetOf({ spendEntries }, project, /* @__PURE__ */ new Date()).entries : [];
|
|
28864
28964
|
const entry = entries[Math.min(spendAt, entries.length - 1)];
|
|
28865
|
-
if (key.upArrow || input === "k") setSpendAt((i) => step(i, -1, entries.length));
|
|
28866
|
-
else if (key.downArrow || input === "j") setSpendAt((i) => step(i, 1, entries.length));
|
|
28965
|
+
if (key.upArrow || input === "k") setSpendAt((i) => step$1(i, -1, entries.length));
|
|
28966
|
+
else if (key.downArrow || input === "j") setSpendAt((i) => step$1(i, 1, entries.length));
|
|
28867
28967
|
else if (input === "a" || input === "n") go.create("spend", { projectId });
|
|
28868
28968
|
else if (input === "d" && entry) go.confirm(`Take out ${money(entry.amount)}${entry.note ? ` for “${entry.note}”` : ""}? u undoes it.`, () => store.getState().removeSpend(entry.id), "Take it out");
|
|
28869
28969
|
return;
|
|
@@ -28879,20 +28979,20 @@ function ProjectView({ projectId, active, width, height, go, onBack }) {
|
|
|
28879
28979
|
if (input === "x" && selected) return moveTask(store, go, selected, "done");
|
|
28880
28980
|
if (input === "d" && selected) return go.confirm(`Delete ${keyOf$1({ projects }, selected)} “${selected.title}”? u undoes it.`, () => store.getState().deleteTask(selected.id), "Delete");
|
|
28881
28981
|
if (mode === "board") {
|
|
28882
|
-
if (key.leftArrow || input === "h") setCol((c) => step(c, -1, columns.length));
|
|
28883
|
-
else if (key.rightArrow || input === "l") setCol((c) => step(c, 1, columns.length));
|
|
28982
|
+
if (key.leftArrow || input === "h") setCol((c) => step$1(c, -1, columns.length));
|
|
28983
|
+
else if (key.rightArrow || input === "l") setCol((c) => step$1(c, 1, columns.length));
|
|
28884
28984
|
else if (key.upArrow || input === "k") setRowByCol((r) => ({
|
|
28885
28985
|
...r,
|
|
28886
|
-
[col]: step(row, -1, column?.tasks.length ?? 0)
|
|
28986
|
+
[col]: step$1(row, -1, column?.tasks.length ?? 0)
|
|
28887
28987
|
}));
|
|
28888
28988
|
else if (key.downArrow || input === "j") setRowByCol((r) => ({
|
|
28889
28989
|
...r,
|
|
28890
|
-
[col]: step(row, 1, column?.tasks.length ?? 0)
|
|
28990
|
+
[col]: step$1(row, 1, column?.tasks.length ?? 0)
|
|
28891
28991
|
}));
|
|
28892
|
-
} else if (key.upArrow || input === "k") setListAt((i) => step(i, -1, list.length));
|
|
28893
|
-
else if (key.downArrow || input === "j") setListAt((i) => step(i, 1, list.length));
|
|
28894
|
-
else if (key.pageDown) setListAt((i) => step(i, 10, list.length));
|
|
28895
|
-
else if (key.pageUp) setListAt((i) => step(i, -10, list.length));
|
|
28992
|
+
} else if (key.upArrow || input === "k") setListAt((i) => step$1(i, -1, list.length));
|
|
28993
|
+
else if (key.downArrow || input === "j") setListAt((i) => step$1(i, 1, list.length));
|
|
28994
|
+
else if (key.pageDown) setListAt((i) => step$1(i, 10, list.length));
|
|
28995
|
+
else if (key.pageUp) setListAt((i) => step$1(i, -10, list.length));
|
|
28896
28996
|
}, { isActive: active });
|
|
28897
28997
|
if (!project) return /* @__PURE__ */ jsx(Box, {
|
|
28898
28998
|
paddingX: 1,
|
|
@@ -29363,7 +29463,8 @@ const NAME = 26;
|
|
|
29363
29463
|
/**
|
|
29364
29464
|
* A portfolio's programs and projects across the months, as on the
|
|
29365
29465
|
* website's roadmap: bars in each one's health, ◆ for milestones. [ ] for
|
|
29366
|
-
* another portfolio, z to zoom, ←→ to page through time
|
|
29466
|
+
* another portfolio, z to zoom, ←→ to page through time, D to hide or show
|
|
29467
|
+
* Done work (shown at first, as on the website).
|
|
29367
29468
|
*/
|
|
29368
29469
|
function RoadmapView({ active, width, height, go }) {
|
|
29369
29470
|
const { today } = useDeps();
|
|
@@ -29375,6 +29476,7 @@ function RoadmapView({ active, width, height, go }) {
|
|
|
29375
29476
|
const [zoom, setZoom] = useState("half");
|
|
29376
29477
|
const [shift, setShift] = useState(0);
|
|
29377
29478
|
const [at, setAt] = useState(0);
|
|
29479
|
+
const [hideDone, setHideDone] = useState(false);
|
|
29378
29480
|
const portfolio = portfolios[Math.min(pf, portfolios.length - 1)];
|
|
29379
29481
|
const day = today();
|
|
29380
29482
|
const w = useMemo(() => roadmapWindow(zoom, day, shift), [
|
|
@@ -29388,14 +29490,20 @@ function RoadmapView({ active, width, height, go }) {
|
|
|
29388
29490
|
milestones
|
|
29389
29491
|
}, portfolio.id, {
|
|
29390
29492
|
health: "all",
|
|
29391
|
-
ownerId: null
|
|
29493
|
+
ownerId: null,
|
|
29494
|
+
hideDone
|
|
29392
29495
|
}, /* @__PURE__ */ new Set(), day) : [], [
|
|
29393
29496
|
portfolio,
|
|
29394
29497
|
programs,
|
|
29395
29498
|
projects,
|
|
29396
29499
|
milestones,
|
|
29397
|
-
day
|
|
29500
|
+
day,
|
|
29501
|
+
hideDone
|
|
29398
29502
|
]);
|
|
29503
|
+
const doneCount = portfolio ? roadmapDoneCount({
|
|
29504
|
+
programs,
|
|
29505
|
+
projects
|
|
29506
|
+
}, portfolio.id) : 0;
|
|
29399
29507
|
const row = rows[Math.min(at, rows.length - 1)];
|
|
29400
29508
|
useInput((input, key) => {
|
|
29401
29509
|
if (input === "[") setPf((i) => (i - 1 + portfolios.length) % Math.max(1, portfolios.length));
|
|
@@ -29404,8 +29512,11 @@ function RoadmapView({ active, width, height, go }) {
|
|
|
29404
29512
|
else if (key.leftArrow || input === "h") setShift((s) => s - 1);
|
|
29405
29513
|
else if (key.rightArrow || input === "l") setShift((s) => s + 1);
|
|
29406
29514
|
else if (input === "t") setShift(0);
|
|
29407
|
-
else if (
|
|
29408
|
-
|
|
29515
|
+
else if (input === "D" && (doneCount > 0 || hideDone)) {
|
|
29516
|
+
setHideDone((v) => !v);
|
|
29517
|
+
setAt(0);
|
|
29518
|
+
} else if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, rows.length));
|
|
29519
|
+
else if (key.downArrow || input === "j") setAt((i) => step$1(i, 1, rows.length));
|
|
29409
29520
|
else if (key.return && row) {
|
|
29410
29521
|
if (row.kind === "project") go.openProject(row.id);
|
|
29411
29522
|
else go.openProgram(row.id);
|
|
@@ -29421,7 +29532,7 @@ function RoadmapView({ active, width, height, go }) {
|
|
|
29421
29532
|
paddingX: 1,
|
|
29422
29533
|
children: [
|
|
29423
29534
|
/* @__PURE__ */ jsxs(Heading, {
|
|
29424
|
-
right: `${label} · [ ] portfolio · z zoom · ←→ time · t today`,
|
|
29535
|
+
right: `${label}${doneCount ? ` · ${doneCount} done ${hideDone ? "hidden" : "shown"} (D)` : ""} · [ ] portfolio · z zoom · ←→ time · t today`,
|
|
29425
29536
|
children: ["Roadmap: ", portfolio?.name ?? "no portfolios yet"]
|
|
29426
29537
|
}),
|
|
29427
29538
|
/* @__PURE__ */ jsxs(Text, {
|
|
@@ -29430,7 +29541,7 @@ function RoadmapView({ active, width, height, go }) {
|
|
|
29430
29541
|
}),
|
|
29431
29542
|
rows.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
29432
29543
|
color: theme.muted,
|
|
29433
|
-
children: "Nothing scheduled in this portfolio."
|
|
29544
|
+
children: hideDone && doneCount ? "Everything in this portfolio is done. D shows it." : "Nothing scheduled in this portfolio."
|
|
29434
29545
|
}) : null,
|
|
29435
29546
|
view.items.map((r, i) => {
|
|
29436
29547
|
const on = active && view.start + i === at;
|
|
@@ -30148,8 +30259,8 @@ function TaskPanel({ taskId, width, height, go, active = true, onClose }) {
|
|
|
30148
30259
|
useInput((input, key) => {
|
|
30149
30260
|
if (!task) return onClose();
|
|
30150
30261
|
if (key.escape) return onClose();
|
|
30151
|
-
if (key.upArrow || input === "k") return setAt((i) => step(i, -1, rows));
|
|
30152
|
-
if (key.downArrow || input === "j") return setAt((i) => step(i, 1, rows));
|
|
30262
|
+
if (key.upArrow || input === "k") return setAt((i) => step$1(i, -1, rows));
|
|
30263
|
+
if (key.downArrow || input === "j") return setAt((i) => step$1(i, 1, rows));
|
|
30153
30264
|
if (input === "c") return setEditing({ kind: "comment" });
|
|
30154
30265
|
if (input === "a") return setEditing({ kind: "step" });
|
|
30155
30266
|
if (input === "C") {
|
|
@@ -30171,6 +30282,13 @@ function TaskPanel({ taskId, width, height, go, active = true, onClose }) {
|
|
|
30171
30282
|
const item = task.checklist[at - stepsFrom];
|
|
30172
30283
|
if (item && (input === " " || key.return)) return store.getState().toggleChecklistItem(task.id, item.id);
|
|
30173
30284
|
if (item && input === "-") return store.getState().removeChecklistItem(task.id, item.id);
|
|
30285
|
+
if (item && input === "e") {
|
|
30286
|
+
if (viewOnly) return go.notify("You can only view this harbor.");
|
|
30287
|
+
return setEditing({
|
|
30288
|
+
kind: "restep",
|
|
30289
|
+
id: item.id
|
|
30290
|
+
});
|
|
30291
|
+
}
|
|
30174
30292
|
if (input === "o") {
|
|
30175
30293
|
if (viewOnly) return go.notify("You can only view this harbor.");
|
|
30176
30294
|
if (alsoOnIds(task).length === 0 && alsoOnChoices(task, people).length === 0) return go.notify("Nobody else to put on it yet. Invite your crew first.");
|
|
@@ -30239,7 +30357,18 @@ function TaskPanel({ taskId, width, height, go, active = true, onClose }) {
|
|
|
30239
30357
|
done();
|
|
30240
30358
|
}
|
|
30241
30359
|
});
|
|
30242
|
-
else if (editing?.kind === "
|
|
30360
|
+
else if (editing?.kind === "restep") {
|
|
30361
|
+
const item = task.checklist.find((c) => c.id === editing.id);
|
|
30362
|
+
editor = /* @__PURE__ */ jsx(TextField, {
|
|
30363
|
+
label: "Checklist step",
|
|
30364
|
+
initial: item?.title ?? "",
|
|
30365
|
+
onCancel: done,
|
|
30366
|
+
onSubmit: (text) => {
|
|
30367
|
+
if (item) store.getState().renameChecklistItem(task.id, item.id, text);
|
|
30368
|
+
done();
|
|
30369
|
+
}
|
|
30370
|
+
});
|
|
30371
|
+
} else if (editing?.kind === "mine") editor = /* @__PURE__ */ jsx(Picker, {
|
|
30243
30372
|
title: "One of your comments: Enter to change it or take it out",
|
|
30244
30373
|
choices: mine.map((c) => ({
|
|
30245
30374
|
label: c.body.replace(/\s+/g, " "),
|
|
@@ -30562,7 +30691,7 @@ function TaskPanel({ taskId, width, height, go, active = true, onClose }) {
|
|
|
30562
30691
|
editor ?? /* @__PURE__ */ jsx(Text, {
|
|
30563
30692
|
color: theme.muted,
|
|
30564
30693
|
wrap: "truncate-end",
|
|
30565
|
-
children: "↑↓ field · Enter change · o who's on it · c comment · C yours · a add step · space tick · x done · S sounding · d delete · Esc close"
|
|
30694
|
+
children: "↑↓ field · Enter change · o who's on it · c comment · C yours · a add step · e edit step · space tick · x done · S sounding · d delete · Esc close"
|
|
30566
30695
|
})
|
|
30567
30696
|
]
|
|
30568
30697
|
});
|
|
@@ -30628,8 +30757,8 @@ function WaitsEditor({ task, onDone }) {
|
|
|
30628
30757
|
const set = (ids) => store.getState().updateTask(task.id, blockersPatch(ids));
|
|
30629
30758
|
useInput((input, key) => {
|
|
30630
30759
|
if (key.escape) return onDone();
|
|
30631
|
-
if (key.upArrow) return setAt((i) => step(i, -1, list.length));
|
|
30632
|
-
if (key.downArrow) return setAt((i) => step(i, 1, list.length));
|
|
30760
|
+
if (key.upArrow) return setAt((i) => step$1(i, -1, list.length));
|
|
30761
|
+
if (key.downArrow) return setAt((i) => step$1(i, 1, list.length));
|
|
30633
30762
|
if (key.return) {
|
|
30634
30763
|
const pick = list[Math.min(at, list.length - 1)];
|
|
30635
30764
|
if (!pick) return onDone();
|
|
@@ -30738,10 +30867,10 @@ function TodayView({ active, width, height, go }) {
|
|
|
30738
30867
|
const selected = mine[Math.min(at, mine.length - 1)];
|
|
30739
30868
|
const late = mine.filter((t) => isLate(t, day)).length;
|
|
30740
30869
|
useInput((input, key) => {
|
|
30741
|
-
if (key.upArrow || input === "k") setAt((i) => step(i, -1, mine.length));
|
|
30742
|
-
else if (key.downArrow || input === "j") setAt((i) => step(i, 1, mine.length));
|
|
30743
|
-
else if (key.pageDown) setAt((i) => step(i, 10, mine.length));
|
|
30744
|
-
else if (key.pageUp) setAt((i) => step(i, -10, mine.length));
|
|
30870
|
+
if (key.upArrow || input === "k") setAt((i) => step$1(i, -1, mine.length));
|
|
30871
|
+
else if (key.downArrow || input === "j") setAt((i) => step$1(i, 1, mine.length));
|
|
30872
|
+
else if (key.pageDown) setAt((i) => step$1(i, 10, mine.length));
|
|
30873
|
+
else if (key.pageUp) setAt((i) => step$1(i, -10, mine.length));
|
|
30745
30874
|
else if (key.return && selected) go.openTask(selected.id);
|
|
30746
30875
|
else if (input === "x" && selected) moveTask(store, go, selected, "done");
|
|
30747
30876
|
else if (input === "n" && projects.length === 0) go.create("project");
|
|
@@ -30936,8 +31065,8 @@ function WeekView({ active, width, height, go }) {
|
|
|
30936
31065
|
if (input === "t") return goWeek(0);
|
|
30937
31066
|
if (input === "v") return setReview((v) => !v);
|
|
30938
31067
|
if (review) return;
|
|
30939
|
-
if (key.upArrow || input === "k") setAt(step(sel, -1, picks.length));
|
|
30940
|
-
else if (key.downArrow || input === "j") setAt(step(sel, 1, picks.length));
|
|
31068
|
+
if (key.upArrow || input === "k") setAt(step$1(sel, -1, picks.length));
|
|
31069
|
+
else if (key.downArrow || input === "j") setAt(step$1(sel, 1, picks.length));
|
|
30941
31070
|
else if (key.return && task) go.openTask(task.id);
|
|
30942
31071
|
else if (input === "p" && line?.kind === "open") go.pick(`Plan “${line.task.title}” on`, dayChoices(), (d) => {
|
|
30943
31072
|
if (store.getState().planTask(line.task.id, d)) go.notify(`On ${dayName(d)}. h changes its hours.`);
|