@youtyan/code-viewer 0.6.2 → 0.6.4
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 +28 -9
- package/dist/code-viewer.js +3317 -490
- package/package.json +1 -1
- package/skills/code-viewer-journal/SKILL.md +131 -0
- package/web/app.js +2552 -263
- package/web/index.html +1 -0
- package/web/style.css +1654 -169
package/web/app.js
CHANGED
|
@@ -227,7 +227,7 @@ ${lines.join(`
|
|
|
227
227
|
function shouldAutoLoadForRoute(route, options = {}) {
|
|
228
228
|
if (route.screen === "history")
|
|
229
229
|
return options.historyWorktreeSelected === true;
|
|
230
|
-
if (route.screen === "database" || route.screen === "help" || route.screen === "unknown")
|
|
230
|
+
if (route.screen === "database" || route.screen === "journal" || route.screen === "help" || route.screen === "unknown")
|
|
231
231
|
return false;
|
|
232
232
|
return true;
|
|
233
233
|
}
|
|
@@ -517,6 +517,7 @@ ${lines.join(`
|
|
|
517
517
|
}
|
|
518
518
|
var PENCIL_16_PATH = "M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.609Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z";
|
|
519
519
|
var COMMENT_DISCUSSION_16_PATH = "M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z";
|
|
520
|
+
var GRABBER_16_PATH = "M10 13h4v-2h-4v2Zm-8 0h4v-2H2v2Zm8-6h4V5h-4v2ZM2 7h4V5H2v2Zm8-6v2h4V1h-4ZM2 3h4V1H2v2Z";
|
|
520
521
|
var PULSE_16_PATH = "M6 2c.306 0 .582.187.696.471L10 10.731l1.304-3.26A.751.751 0 0 1 12 7h3.25a.75.75 0 0 1 0 1.5h-2.742l-1.812 4.528a.751.751 0 0 1-1.392 0L6 4.77 4.696 8.03A.75.75 0 0 1 4 8.5H.75a.75.75 0 0 1 0-1.5h2.742l1.812-4.529A.751.751 0 0 1 6 2Z";
|
|
521
522
|
var MOON_16_PATH = "M9.598 1.591a.749.749 0 0 1 .785-.175 7.001 7.001 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786Zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.499 5.499 0 1 0 7.678-7.678Z";
|
|
522
523
|
var SEARCH_16_PATH = "M10.68 11.74a6 6 0 0 1-7.917-8.984 6 6 0 0 1 8.997 7.905l3.05 3.05a.75.75 0 1 1-1.06 1.06l-3.07-3.03ZM11.5 7a4.499 4.499 0 1 1-8.997 0A4.499 4.499 0 0 1 11.5 7Z";
|
|
@@ -913,6 +914,21 @@ ${lines.join(`
|
|
|
913
914
|
range
|
|
914
915
|
};
|
|
915
916
|
}
|
|
917
|
+
case "/journal": {
|
|
918
|
+
const tabRaw = params.get("tab");
|
|
919
|
+
const tab = tabRaw === "tasks" || tabRaw === "journal" ? tabRaw : undefined;
|
|
920
|
+
const date = params.get("date") || undefined;
|
|
921
|
+
const label = params.get("label") || undefined;
|
|
922
|
+
const task = params.get("task") || undefined;
|
|
923
|
+
return {
|
|
924
|
+
screen: "journal",
|
|
925
|
+
...tab ? { tab } : {},
|
|
926
|
+
...date ? { date } : {},
|
|
927
|
+
...label ? { label } : {},
|
|
928
|
+
...task ? { task } : {},
|
|
929
|
+
range
|
|
930
|
+
};
|
|
931
|
+
}
|
|
916
932
|
case "/database": {
|
|
917
933
|
const db = params.get("db") || undefined;
|
|
918
934
|
const schema = params.get("schema") || undefined;
|
|
@@ -985,6 +1001,19 @@ ${lines.join(`
|
|
|
985
1001
|
const qs = params.toString();
|
|
986
1002
|
return `/history${qs ? `?${qs}` : ""}`;
|
|
987
1003
|
}
|
|
1004
|
+
case "journal": {
|
|
1005
|
+
const params = new URLSearchParams;
|
|
1006
|
+
if (route.tab && route.tab !== "journal")
|
|
1007
|
+
params.set("tab", route.tab);
|
|
1008
|
+
if (route.date)
|
|
1009
|
+
params.set("date", route.date);
|
|
1010
|
+
if (route.label)
|
|
1011
|
+
params.set("label", route.label);
|
|
1012
|
+
if (route.task)
|
|
1013
|
+
params.set("task", route.task);
|
|
1014
|
+
const qs = params.toString();
|
|
1015
|
+
return `/journal${qs ? `?${qs}` : ""}`;
|
|
1016
|
+
}
|
|
988
1017
|
case "database": {
|
|
989
1018
|
const params = new URLSearchParams;
|
|
990
1019
|
if (route.db)
|
|
@@ -8056,6 +8085,132 @@ ${frontmatter.yaml}
|
|
|
8056
8085
|
return { width: rect.width || 800, height: rect.height || 600 };
|
|
8057
8086
|
}
|
|
8058
8087
|
|
|
8088
|
+
// web-src/views/diff-line-select.ts
|
|
8089
|
+
var SELECTED_CLASS = "gdp-diff-line-selected";
|
|
8090
|
+
function cardPath(el) {
|
|
8091
|
+
return el.closest(".gdp-file-shell[data-path]")?.dataset.path || "";
|
|
8092
|
+
}
|
|
8093
|
+
function afterLineFromCell(cell) {
|
|
8094
|
+
const sideCell = cell.closest("td.d2h-code-side-linenumber");
|
|
8095
|
+
if (sideCell) {
|
|
8096
|
+
const side = sideCell.closest(".d2h-file-side-diff");
|
|
8097
|
+
const wrapper = sideCell.closest(".d2h-file-wrapper");
|
|
8098
|
+
if (!side || !wrapper)
|
|
8099
|
+
return null;
|
|
8100
|
+
const sides = wrapper.querySelectorAll(".d2h-file-side-diff");
|
|
8101
|
+
if (sides.length < 2 || side !== sides[1])
|
|
8102
|
+
return null;
|
|
8103
|
+
const line2 = Number((sideCell.textContent || "").trim());
|
|
8104
|
+
return Number.isInteger(line2) && line2 > 0 ? line2 : null;
|
|
8105
|
+
}
|
|
8106
|
+
const numCell = cell.closest("td.d2h-code-linenumber");
|
|
8107
|
+
if (!numCell)
|
|
8108
|
+
return null;
|
|
8109
|
+
const raw = (numCell.querySelector(".line-num2")?.textContent || "").trim();
|
|
8110
|
+
const line = Number(raw);
|
|
8111
|
+
return Number.isInteger(line) && line > 0 ? line : null;
|
|
8112
|
+
}
|
|
8113
|
+
function afterLineCellFromRow(row) {
|
|
8114
|
+
const cells = row.querySelectorAll("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
8115
|
+
for (const cell of cells) {
|
|
8116
|
+
if (afterLineFromCell(cell) !== null)
|
|
8117
|
+
return cell;
|
|
8118
|
+
}
|
|
8119
|
+
return null;
|
|
8120
|
+
}
|
|
8121
|
+
function diffRowAfterLineNumber(row) {
|
|
8122
|
+
const cell = afterLineCellFromRow(row);
|
|
8123
|
+
return cell ? afterLineFromCell(cell) : null;
|
|
8124
|
+
}
|
|
8125
|
+
function diffRowHasAfterChange(row) {
|
|
8126
|
+
const cell = afterLineCellFromRow(row);
|
|
8127
|
+
return !!cell && cell.classList.contains("d2h-ins");
|
|
8128
|
+
}
|
|
8129
|
+
function rowsWithAfterLines(card) {
|
|
8130
|
+
const out = [];
|
|
8131
|
+
card.querySelectorAll("table.d2h-diff-table tr").forEach((row) => {
|
|
8132
|
+
const line = diffRowAfterLineNumber(row);
|
|
8133
|
+
if (line !== null)
|
|
8134
|
+
out.push({ row, line });
|
|
8135
|
+
});
|
|
8136
|
+
return out;
|
|
8137
|
+
}
|
|
8138
|
+
function createDiffLineSelect(deps) {
|
|
8139
|
+
let drag = null;
|
|
8140
|
+
let selection = null;
|
|
8141
|
+
function clearHighlights() {
|
|
8142
|
+
document.querySelectorAll(`.${SELECTED_CLASS}`).forEach((row) => {
|
|
8143
|
+
row.classList.remove(SELECTED_CLASS);
|
|
8144
|
+
});
|
|
8145
|
+
}
|
|
8146
|
+
function applySelection(next) {
|
|
8147
|
+
selection = next;
|
|
8148
|
+
clearHighlights();
|
|
8149
|
+
if (!next) {
|
|
8150
|
+
deps.pill.hide();
|
|
8151
|
+
return;
|
|
8152
|
+
}
|
|
8153
|
+
const start = Math.min(next.start, next.end);
|
|
8154
|
+
const end = Math.max(next.start, next.end);
|
|
8155
|
+
const card = document.querySelector(`.gdp-file-shell[data-path="${CSS.escape(next.path)}"]`);
|
|
8156
|
+
if (card) {
|
|
8157
|
+
for (const item of rowsWithAfterLines(card)) {
|
|
8158
|
+
if (item.line >= start && item.line <= end)
|
|
8159
|
+
item.row.classList.add(SELECTED_CLASS);
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8162
|
+
deps.pill.show(next.path, start, end);
|
|
8163
|
+
}
|
|
8164
|
+
function clear() {
|
|
8165
|
+
const hadSelection = !!selection || !!drag;
|
|
8166
|
+
drag = null;
|
|
8167
|
+
applySelection(null);
|
|
8168
|
+
return hadSelection;
|
|
8169
|
+
}
|
|
8170
|
+
const diff = document.querySelector("#diff");
|
|
8171
|
+
if (!diff)
|
|
8172
|
+
return { clear };
|
|
8173
|
+
diff.addEventListener("mousedown", (e2) => {
|
|
8174
|
+
const target = e2.target;
|
|
8175
|
+
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
8176
|
+
if (!cell)
|
|
8177
|
+
return;
|
|
8178
|
+
const line = afterLineFromCell(cell);
|
|
8179
|
+
const path = cardPath(cell);
|
|
8180
|
+
if (line === null || !path) {
|
|
8181
|
+
if (selection)
|
|
8182
|
+
clear();
|
|
8183
|
+
return;
|
|
8184
|
+
}
|
|
8185
|
+
e2.preventDefault();
|
|
8186
|
+
drag = { path, start: line };
|
|
8187
|
+
applySelection({ path, start: line, end: line });
|
|
8188
|
+
});
|
|
8189
|
+
diff.addEventListener("mouseover", (e2) => {
|
|
8190
|
+
if (!drag)
|
|
8191
|
+
return;
|
|
8192
|
+
const target = e2.target;
|
|
8193
|
+
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
8194
|
+
if (!cell || cardPath(cell) !== drag.path)
|
|
8195
|
+
return;
|
|
8196
|
+
const line = afterLineFromCell(cell);
|
|
8197
|
+
if (line === null)
|
|
8198
|
+
return;
|
|
8199
|
+
applySelection({ path: drag.path, start: drag.start, end: line });
|
|
8200
|
+
});
|
|
8201
|
+
document.addEventListener("mouseup", () => {
|
|
8202
|
+
drag = null;
|
|
8203
|
+
});
|
|
8204
|
+
document.addEventListener("keydown", (e2) => {
|
|
8205
|
+
if (isImeComposing(e2))
|
|
8206
|
+
return;
|
|
8207
|
+
if (e2.key === "Escape" && selection && !drag && !isEditableKeyTarget(e2.target)) {
|
|
8208
|
+
clear();
|
|
8209
|
+
}
|
|
8210
|
+
});
|
|
8211
|
+
return { clear };
|
|
8212
|
+
}
|
|
8213
|
+
|
|
8059
8214
|
// web-src/views/ui-dialog.ts
|
|
8060
8215
|
var BACKDROP_CLASS = "gdp-dialog-backdrop";
|
|
8061
8216
|
var DIALOG_CLASS = "gdp-dialog";
|
|
@@ -8478,15 +8633,34 @@ ${frontmatter.yaml}
|
|
|
8478
8633
|
function inlineAnnotationTargetRow(entry) {
|
|
8479
8634
|
if (!entry.line)
|
|
8480
8635
|
return null;
|
|
8481
|
-
const line = entry.line.end;
|
|
8482
8636
|
const card = document.querySelector(deps.diffCardSelector(entry.path));
|
|
8483
8637
|
if (!card)
|
|
8484
8638
|
return null;
|
|
8639
|
+
const line = entry.line.end;
|
|
8485
8640
|
const sourceRow = card.querySelector(`.gdp-source-table tr[data-line="${String(line)}"]`);
|
|
8486
8641
|
if (sourceRow)
|
|
8487
8642
|
return sourceRow;
|
|
8643
|
+
const start = entry.line.start;
|
|
8644
|
+
const end = entry.line.end;
|
|
8488
8645
|
const rows = Array.from(card.querySelectorAll("table.d2h-diff-table tr"));
|
|
8489
|
-
|
|
8646
|
+
const byLine = new Map;
|
|
8647
|
+
let hasChangedAfterRow = false;
|
|
8648
|
+
for (const row of rows) {
|
|
8649
|
+
const rowLine = deps.diffRowLineNumber(row);
|
|
8650
|
+
if (rowLine === null || rowLine < start || rowLine > end)
|
|
8651
|
+
continue;
|
|
8652
|
+
if (!byLine.has(rowLine))
|
|
8653
|
+
byLine.set(rowLine, row);
|
|
8654
|
+
if (diffRowHasAfterChange(row))
|
|
8655
|
+
hasChangedAfterRow = true;
|
|
8656
|
+
}
|
|
8657
|
+
if (!hasChangedAfterRow)
|
|
8658
|
+
return null;
|
|
8659
|
+
for (let rowLine = start;rowLine <= end; rowLine++) {
|
|
8660
|
+
if (!byLine.has(rowLine))
|
|
8661
|
+
return null;
|
|
8662
|
+
}
|
|
8663
|
+
return byLine.get(end) || null;
|
|
8490
8664
|
}
|
|
8491
8665
|
function siblingSideRow(row) {
|
|
8492
8666
|
const side = row.closest(".d2h-file-side-diff");
|
|
@@ -8517,10 +8691,6 @@ ${frontmatter.yaml}
|
|
|
8517
8691
|
row.remove();
|
|
8518
8692
|
});
|
|
8519
8693
|
const session = ANNOTATIONS.sessions.find((s2) => s2.id === activeSessionId);
|
|
8520
|
-
if (!annotationPanel.hidden) {
|
|
8521
|
-
applyDatabaseAnnotations(undefined);
|
|
8522
|
-
return;
|
|
8523
|
-
}
|
|
8524
8694
|
applyDatabaseAnnotations(session);
|
|
8525
8695
|
if (!session)
|
|
8526
8696
|
return;
|
|
@@ -8589,21 +8759,18 @@ ${frontmatter.yaml}
|
|
|
8589
8759
|
renderAnnotationPanel();
|
|
8590
8760
|
applyInlineAnnotations();
|
|
8591
8761
|
}
|
|
8592
|
-
async function
|
|
8593
|
-
if (!entry.line
|
|
8594
|
-
return;
|
|
8762
|
+
async function waitForAnnotationDiffTarget(entry) {
|
|
8763
|
+
if (!entry.line)
|
|
8764
|
+
return null;
|
|
8765
|
+
const ready = inlineAnnotationTargetRow(entry);
|
|
8766
|
+
if (ready)
|
|
8767
|
+
return ready;
|
|
8595
8768
|
const card = document.querySelector(deps.diffCardSelector(entry.path));
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
if (inlineAnnotationTargetRow(entry))
|
|
8602
|
-
return;
|
|
8603
|
-
if (card.classList.contains("gdp-context-expanded"))
|
|
8604
|
-
return;
|
|
8605
|
-
await deps.expandAllFileContext(card, file);
|
|
8606
|
-
deps.focusDiffLine(card, annotationLineTarget(entry));
|
|
8769
|
+
if (!card || card.classList.contains("gdp-standalone-source"))
|
|
8770
|
+
return null;
|
|
8771
|
+
if (!card.classList.contains("loaded") && !await deps.loadDiffFile(entry.path))
|
|
8772
|
+
return null;
|
|
8773
|
+
return inlineAnnotationTargetRow(entry);
|
|
8607
8774
|
}
|
|
8608
8775
|
function syncInlineAnnotationActive() {
|
|
8609
8776
|
document.querySelectorAll(".gdp-annotation-row").forEach((row) => {
|
|
@@ -9097,11 +9264,13 @@ ${frontmatter.yaml}
|
|
|
9097
9264
|
return;
|
|
9098
9265
|
emptyDiffRangeKey = deps.getFiles().length ? null : rangeKey;
|
|
9099
9266
|
}
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9267
|
+
let showSourceView = !deps.getFiles().some((f2) => f2.path === entry.path);
|
|
9268
|
+
let replaceSourceRoute = needDiffLoad;
|
|
9269
|
+
if (!showSourceView) {
|
|
9103
9270
|
const hasDiffCards = !!document.querySelector(".gdp-file-shell:not(.gdp-standalone-source)");
|
|
9104
9271
|
if (!hasDiffCards) {
|
|
9272
|
+
deps.setRoute({ screen: "diff", range, path: entry.path, line }, needDiffLoad);
|
|
9273
|
+
deps.setPageMode();
|
|
9105
9274
|
deps.removeStandaloneSource();
|
|
9106
9275
|
await deps.load();
|
|
9107
9276
|
if (stale())
|
|
@@ -9109,14 +9278,22 @@ ${frontmatter.yaml}
|
|
|
9109
9278
|
}
|
|
9110
9279
|
deps.removeStandaloneSource();
|
|
9111
9280
|
deps.scrollToFile(entry.path, line);
|
|
9112
|
-
await
|
|
9281
|
+
const target = await waitForAnnotationDiffTarget(entry);
|
|
9113
9282
|
if (stale())
|
|
9114
9283
|
return;
|
|
9115
|
-
|
|
9284
|
+
if (!target) {
|
|
9285
|
+
showSourceView = true;
|
|
9286
|
+
replaceSourceRoute = true;
|
|
9287
|
+
} else {
|
|
9288
|
+
deps.setRoute({ screen: "diff", range, path: entry.path, line }, needDiffLoad);
|
|
9289
|
+
deps.setPageMode();
|
|
9290
|
+
}
|
|
9291
|
+
}
|
|
9292
|
+
if (showSourceView) {
|
|
9116
9293
|
const ref = annotationRefForEntry(entry);
|
|
9117
9294
|
const card = document.querySelector(".gdp-standalone-source");
|
|
9118
9295
|
const reusable = prevRoute.screen === "file" && prevRoute.path === entry.path && prevRoute.ref === ref && card?.dataset.path === entry.path && !!card.querySelector(".gdp-source-table");
|
|
9119
|
-
deps.setRoute({ screen: "file", path: entry.path, ref, view: "blob", line, range },
|
|
9296
|
+
deps.setRoute({ screen: "file", path: entry.path, ref, view: "blob", line, range }, replaceSourceRoute);
|
|
9120
9297
|
deps.setPageMode();
|
|
9121
9298
|
if (reusable && card) {
|
|
9122
9299
|
focusStandaloneSourceLines(card, entry);
|
|
@@ -14544,7 +14721,7 @@ ${frontmatter.yaml}
|
|
|
14544
14721
|
refreshBtn.setAttribute("aria-busy", refreshBusy ? "true" : "false");
|
|
14545
14722
|
refreshBtn.disabled = refreshBusy;
|
|
14546
14723
|
refreshBtn.classList.toggle("spinning", refreshBusy);
|
|
14547
|
-
refreshLabel.textContent =
|
|
14724
|
+
refreshLabel.textContent = "";
|
|
14548
14725
|
}
|
|
14549
14726
|
function setRefreshBusy(busy) {
|
|
14550
14727
|
refreshBusy = busy;
|
|
@@ -16200,7 +16377,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16200
16377
|
refreshBtn.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
16201
16378
|
const refreshLabel = document.createElement("span");
|
|
16202
16379
|
refreshLabel.className = "db-grid-refresh-label";
|
|
16203
|
-
refreshLabel.textContent =
|
|
16380
|
+
refreshLabel.textContent = "";
|
|
16204
16381
|
refreshBtn.appendChild(refreshLabel);
|
|
16205
16382
|
const refreshResult = document.createElement("span");
|
|
16206
16383
|
refreshResult.className = "db-refresh-result db-grid-refresh-result";
|
|
@@ -16308,7 +16485,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16308
16485
|
filteredEmptyActions.hidden = true;
|
|
16309
16486
|
const filteredEmptyReloadAction = document.createElement("button");
|
|
16310
16487
|
filteredEmptyReloadAction.type = "button";
|
|
16311
|
-
filteredEmptyReloadAction.className = "db-btn db-btn-
|
|
16488
|
+
filteredEmptyReloadAction.className = "db-btn db-btn-icon db-grid-refresh";
|
|
16489
|
+
filteredEmptyReloadAction.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
16312
16490
|
filteredEmptyReloadAction.hidden = true;
|
|
16313
16491
|
filteredEmptyReloadAction.disabled = true;
|
|
16314
16492
|
const filteredEmptyAction = document.createElement("button");
|
|
@@ -16404,7 +16582,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16404
16582
|
let rafId = 0;
|
|
16405
16583
|
let statusEl = null;
|
|
16406
16584
|
let isRefreshing = false;
|
|
16407
|
-
let refreshResultState = null;
|
|
16408
16585
|
let filterTimer = null;
|
|
16409
16586
|
let selectedRowIndex = -1;
|
|
16410
16587
|
let activeCellRowIndex = -1;
|
|
@@ -16524,7 +16701,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16524
16701
|
function syncRefreshButton() {
|
|
16525
16702
|
const count = activeFilterCount();
|
|
16526
16703
|
const t2 = text2().grid;
|
|
16527
|
-
refreshLabel.textContent =
|
|
16704
|
+
refreshLabel.textContent = "";
|
|
16528
16705
|
const action = count > 0 ? t2.refreshActionWithFilters(count) : t2.refreshAction;
|
|
16529
16706
|
refreshBtn.classList.toggle("has-filters", count > 0);
|
|
16530
16707
|
refreshBtn.title = action;
|
|
@@ -16532,26 +16709,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16532
16709
|
refreshBtn.setAttribute("aria-busy", isRefreshing ? "true" : "false");
|
|
16533
16710
|
}
|
|
16534
16711
|
function syncRefreshResult() {
|
|
16535
|
-
|
|
16536
|
-
refreshResult.
|
|
16537
|
-
refreshResult.classList.toggle("changed",
|
|
16538
|
-
if (!result) {
|
|
16539
|
-
refreshResult.textContent = "";
|
|
16540
|
-
return;
|
|
16541
|
-
}
|
|
16542
|
-
const t2 = text2().grid;
|
|
16543
|
-
const total = result.totalRows.toLocaleString();
|
|
16544
|
-
refreshResult.textContent = result.delta === 0 ? t2.refreshResultUnchanged(total) : t2.refreshResultChanged(result.delta, total);
|
|
16712
|
+
refreshResult.hidden = true;
|
|
16713
|
+
refreshResult.textContent = "";
|
|
16714
|
+
refreshResult.classList.toggle("changed", false);
|
|
16545
16715
|
}
|
|
16546
16716
|
function clearRefreshResult() {
|
|
16547
|
-
refreshResultState = null;
|
|
16548
|
-
syncRefreshResult();
|
|
16549
|
-
}
|
|
16550
|
-
function setRefreshResult(previousTotalRows, nextTotalRows) {
|
|
16551
|
-
refreshResultState = {
|
|
16552
|
-
delta: nextTotalRows - previousTotalRows,
|
|
16553
|
-
totalRows: nextTotalRows
|
|
16554
|
-
};
|
|
16555
16717
|
syncRefreshResult();
|
|
16556
16718
|
}
|
|
16557
16719
|
function syncFilterClearButton() {
|
|
@@ -16578,7 +16740,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16578
16740
|
const reloadAction = t2.refreshActionWithFilters(count);
|
|
16579
16741
|
filteredEmptyTitle.textContent = t2.filteredEmptyTitle(count);
|
|
16580
16742
|
filteredEmptyHint.textContent = t2.filteredEmptyHint;
|
|
16581
|
-
filteredEmptyReloadAction.
|
|
16743
|
+
filteredEmptyReloadAction.classList.toggle("spinning", isRefreshing);
|
|
16744
|
+
filteredEmptyReloadAction.textContent = "";
|
|
16745
|
+
filteredEmptyReloadAction.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
16582
16746
|
filteredEmptyReloadAction.title = reloadAction;
|
|
16583
16747
|
filteredEmptyReloadAction.setAttribute("aria-label", reloadAction);
|
|
16584
16748
|
filteredEmptyReloadAction.setAttribute("aria-busy", isRefreshing ? "true" : "false");
|
|
@@ -16628,7 +16792,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16628
16792
|
if (!currentTable || refreshBtn.disabled)
|
|
16629
16793
|
return;
|
|
16630
16794
|
const refreshTable = currentTable;
|
|
16631
|
-
const previousTotalRows = totalRows;
|
|
16632
16795
|
const refreshFilterKey = JSON.stringify(collectFilters());
|
|
16633
16796
|
const scrollTop = viewport.scrollTop;
|
|
16634
16797
|
const pageStart = Math.floor(scrollTop / ROW_HEIGHT / PAGE_SIZE) * PAGE_SIZE;
|
|
@@ -16661,7 +16824,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16661
16824
|
syncFilteredEmptyState();
|
|
16662
16825
|
if (!endedInError) {
|
|
16663
16826
|
if (loadGeneration === refreshGeneration && JSON.stringify(collectFilters()) === refreshFilterKey) {
|
|
16664
|
-
|
|
16827
|
+
syncRefreshResult();
|
|
16665
16828
|
callbacks.onRefreshComplete?.({
|
|
16666
16829
|
table: refreshTable,
|
|
16667
16830
|
totalRows,
|
|
@@ -18563,7 +18726,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18563
18726
|
const tableSelectGuard = createAbortGuard();
|
|
18564
18727
|
let historyRefreshPending = null;
|
|
18565
18728
|
let isRefreshingDatastores = false;
|
|
18566
|
-
let datastoreRefreshResult = null;
|
|
18567
18729
|
const dbSelect = document.createElement("select");
|
|
18568
18730
|
dbSelect.className = "db-file-select";
|
|
18569
18731
|
reloc(() => {
|
|
@@ -19682,46 +19844,24 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19682
19844
|
handleSchemaSelectChange();
|
|
19683
19845
|
});
|
|
19684
19846
|
function syncDbRefreshButton() {
|
|
19685
|
-
|
|
19686
|
-
dbRefreshLabel.textContent = isRefreshingDatastores ? text2.refreshDatastoresBusy : text2.refreshDatastoresShort;
|
|
19847
|
+
dbRefreshLabel.textContent = "";
|
|
19687
19848
|
dbRefreshBtn.setAttribute("aria-busy", isRefreshingDatastores ? "true" : "false");
|
|
19688
19849
|
syncDatastoreRefreshResult();
|
|
19689
19850
|
}
|
|
19690
19851
|
function syncDatastoreRefreshResult() {
|
|
19691
|
-
|
|
19692
|
-
dbRefreshResult.
|
|
19693
|
-
dbRefreshResult.classList.toggle("changed",
|
|
19694
|
-
if (!result)
|
|
19695
|
-
return;
|
|
19696
|
-
const text2 = paneText().nav;
|
|
19697
|
-
dbRefreshResult.textContent = result.added > 0 || result.removed > 0 ? text2.refreshDatastoresChanged(result.added, result.removed) : text2.refreshDatastoresUnchanged;
|
|
19698
|
-
}
|
|
19699
|
-
function diffDatastoreFiles(before, after) {
|
|
19700
|
-
const beforeIds = new Set(before.map((file) => file.id));
|
|
19701
|
-
const afterIds = new Set(after.map((file) => file.id));
|
|
19702
|
-
let added = 0;
|
|
19703
|
-
let removed = 0;
|
|
19704
|
-
for (const id of afterIds) {
|
|
19705
|
-
if (!beforeIds.has(id))
|
|
19706
|
-
added++;
|
|
19707
|
-
}
|
|
19708
|
-
for (const id of beforeIds) {
|
|
19709
|
-
if (!afterIds.has(id))
|
|
19710
|
-
removed++;
|
|
19711
|
-
}
|
|
19712
|
-
return { added, removed };
|
|
19852
|
+
dbRefreshResult.hidden = true;
|
|
19853
|
+
dbRefreshResult.textContent = "";
|
|
19854
|
+
dbRefreshResult.classList.toggle("changed", false);
|
|
19713
19855
|
}
|
|
19714
19856
|
async function refreshDatastoreList() {
|
|
19715
19857
|
if (dbRefreshBtn.disabled)
|
|
19716
19858
|
return;
|
|
19717
|
-
const beforeFiles = [...lastFiles];
|
|
19718
19859
|
isRefreshingDatastores = true;
|
|
19719
19860
|
dbRefreshBtn.disabled = true;
|
|
19720
19861
|
dbRefreshBtn.classList.add("spinning");
|
|
19721
19862
|
syncDbRefreshButton();
|
|
19722
19863
|
try {
|
|
19723
19864
|
await outerDeps.refreshDatastores();
|
|
19724
|
-
datastoreRefreshResult = cb.isActive() ? diffDatastoreFiles(beforeFiles, lastFiles) : null;
|
|
19725
19865
|
} finally {
|
|
19726
19866
|
isRefreshingDatastores = false;
|
|
19727
19867
|
dbRefreshBtn.classList.remove("spinning");
|
|
@@ -19757,7 +19897,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19757
19897
|
const dbId = dbSelect.value;
|
|
19758
19898
|
if (!dbId)
|
|
19759
19899
|
return;
|
|
19760
|
-
datastoreRefreshResult = null;
|
|
19761
19900
|
syncDatastoreRefreshResult();
|
|
19762
19901
|
const generation = ++loadGeneration;
|
|
19763
19902
|
const file = lastFiles.find((f2) => f2.id === dbId);
|
|
@@ -21146,119 +21285,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21146
21285
|
return `${bytes} B`;
|
|
21147
21286
|
}
|
|
21148
21287
|
|
|
21149
|
-
// web-src/views/diff-line-select.ts
|
|
21150
|
-
var SELECTED_CLASS = "gdp-diff-line-selected";
|
|
21151
|
-
function cardPath(el) {
|
|
21152
|
-
return el.closest(".gdp-file-shell[data-path]")?.dataset.path || "";
|
|
21153
|
-
}
|
|
21154
|
-
function afterLineFromCell(cell) {
|
|
21155
|
-
const sideCell = cell.closest("td.d2h-code-side-linenumber");
|
|
21156
|
-
if (sideCell) {
|
|
21157
|
-
const side = sideCell.closest(".d2h-file-side-diff");
|
|
21158
|
-
const wrapper = sideCell.closest(".d2h-file-wrapper");
|
|
21159
|
-
if (!side || !wrapper)
|
|
21160
|
-
return null;
|
|
21161
|
-
const sides = wrapper.querySelectorAll(".d2h-file-side-diff");
|
|
21162
|
-
if (sides.length < 2 || side !== sides[1])
|
|
21163
|
-
return null;
|
|
21164
|
-
const line2 = Number((sideCell.textContent || "").trim());
|
|
21165
|
-
return Number.isInteger(line2) && line2 > 0 ? line2 : null;
|
|
21166
|
-
}
|
|
21167
|
-
const numCell = cell.closest("td.d2h-code-linenumber");
|
|
21168
|
-
if (!numCell)
|
|
21169
|
-
return null;
|
|
21170
|
-
const raw = (numCell.querySelector(".line-num2")?.textContent || "").trim();
|
|
21171
|
-
const line = Number(raw);
|
|
21172
|
-
return Number.isInteger(line) && line > 0 ? line : null;
|
|
21173
|
-
}
|
|
21174
|
-
function rowsWithAfterLines(card) {
|
|
21175
|
-
const out = [];
|
|
21176
|
-
card.querySelectorAll("table.d2h-diff-table tr").forEach((row) => {
|
|
21177
|
-
const cell = row.querySelector("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
21178
|
-
if (!cell)
|
|
21179
|
-
return;
|
|
21180
|
-
const line = afterLineFromCell(cell);
|
|
21181
|
-
if (line !== null)
|
|
21182
|
-
out.push({ row, line });
|
|
21183
|
-
});
|
|
21184
|
-
return out;
|
|
21185
|
-
}
|
|
21186
|
-
function createDiffLineSelect(deps) {
|
|
21187
|
-
let drag = null;
|
|
21188
|
-
let selection = null;
|
|
21189
|
-
function clearHighlights() {
|
|
21190
|
-
document.querySelectorAll(`.${SELECTED_CLASS}`).forEach((row) => {
|
|
21191
|
-
row.classList.remove(SELECTED_CLASS);
|
|
21192
|
-
});
|
|
21193
|
-
}
|
|
21194
|
-
function applySelection(next) {
|
|
21195
|
-
selection = next;
|
|
21196
|
-
clearHighlights();
|
|
21197
|
-
if (!next) {
|
|
21198
|
-
deps.pill.hide();
|
|
21199
|
-
return;
|
|
21200
|
-
}
|
|
21201
|
-
const start = Math.min(next.start, next.end);
|
|
21202
|
-
const end = Math.max(next.start, next.end);
|
|
21203
|
-
const card = document.querySelector(`.gdp-file-shell[data-path="${CSS.escape(next.path)}"]`);
|
|
21204
|
-
if (card) {
|
|
21205
|
-
for (const item of rowsWithAfterLines(card)) {
|
|
21206
|
-
if (item.line >= start && item.line <= end)
|
|
21207
|
-
item.row.classList.add(SELECTED_CLASS);
|
|
21208
|
-
}
|
|
21209
|
-
}
|
|
21210
|
-
deps.pill.show(next.path, start, end);
|
|
21211
|
-
}
|
|
21212
|
-
function clear() {
|
|
21213
|
-
const hadSelection = !!selection || !!drag;
|
|
21214
|
-
drag = null;
|
|
21215
|
-
applySelection(null);
|
|
21216
|
-
return hadSelection;
|
|
21217
|
-
}
|
|
21218
|
-
const diff = document.querySelector("#diff");
|
|
21219
|
-
if (!diff)
|
|
21220
|
-
return { clear };
|
|
21221
|
-
diff.addEventListener("mousedown", (e2) => {
|
|
21222
|
-
const target = e2.target;
|
|
21223
|
-
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
21224
|
-
if (!cell)
|
|
21225
|
-
return;
|
|
21226
|
-
const line = afterLineFromCell(cell);
|
|
21227
|
-
const path = cardPath(cell);
|
|
21228
|
-
if (line === null || !path) {
|
|
21229
|
-
if (selection)
|
|
21230
|
-
clear();
|
|
21231
|
-
return;
|
|
21232
|
-
}
|
|
21233
|
-
e2.preventDefault();
|
|
21234
|
-
drag = { path, start: line };
|
|
21235
|
-
applySelection({ path, start: line, end: line });
|
|
21236
|
-
});
|
|
21237
|
-
diff.addEventListener("mouseover", (e2) => {
|
|
21238
|
-
if (!drag)
|
|
21239
|
-
return;
|
|
21240
|
-
const target = e2.target;
|
|
21241
|
-
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
21242
|
-
if (!cell || cardPath(cell) !== drag.path)
|
|
21243
|
-
return;
|
|
21244
|
-
const line = afterLineFromCell(cell);
|
|
21245
|
-
if (line === null)
|
|
21246
|
-
return;
|
|
21247
|
-
applySelection({ path: drag.path, start: drag.start, end: line });
|
|
21248
|
-
});
|
|
21249
|
-
document.addEventListener("mouseup", () => {
|
|
21250
|
-
drag = null;
|
|
21251
|
-
});
|
|
21252
|
-
document.addEventListener("keydown", (e2) => {
|
|
21253
|
-
if (isImeComposing(e2))
|
|
21254
|
-
return;
|
|
21255
|
-
if (e2.key === "Escape" && selection && !drag && !isEditableKeyTarget(e2.target)) {
|
|
21256
|
-
clear();
|
|
21257
|
-
}
|
|
21258
|
-
});
|
|
21259
|
-
return { clear };
|
|
21260
|
-
}
|
|
21261
|
-
|
|
21262
21288
|
// web-src/core/ws-highlight.ts
|
|
21263
21289
|
function isWhitespaceOnlyInlineHighlight(text2) {
|
|
21264
21290
|
return !!text2 && !/\S/.test(text2);
|
|
@@ -21479,6 +21505,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21479
21505
|
metaFilesForViewedProgress = meta.files || [];
|
|
21480
21506
|
el.innerHTML = "";
|
|
21481
21507
|
const text2 = diffText();
|
|
21508
|
+
if (meta.error) {
|
|
21509
|
+
const error2 = document.createElement("span");
|
|
21510
|
+
error2.className = "chip chip-error";
|
|
21511
|
+
error2.textContent = meta.error;
|
|
21512
|
+
el.appendChild(error2);
|
|
21513
|
+
}
|
|
21482
21514
|
if (meta.totals) {
|
|
21483
21515
|
const files = document.createElement("span");
|
|
21484
21516
|
files.className = "chip chip-files";
|
|
@@ -21546,22 +21578,31 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21546
21578
|
});
|
|
21547
21579
|
}
|
|
21548
21580
|
function diffRowLineNumber(row) {
|
|
21549
|
-
|
|
21550
|
-
const raw = (newLine?.textContent || "").trim();
|
|
21551
|
-
const line = Number(raw);
|
|
21552
|
-
return Number.isInteger(line) && line > 0 ? line : null;
|
|
21581
|
+
return diffRowAfterLineNumber(row);
|
|
21553
21582
|
}
|
|
21554
21583
|
function focusDiffLine(card, line) {
|
|
21555
21584
|
const start = lineTargetStart(line);
|
|
21556
21585
|
if (!start)
|
|
21557
21586
|
return false;
|
|
21587
|
+
const end = typeof line === "object" ? line.end : start;
|
|
21558
21588
|
const rows = Array.from(card.querySelectorAll("table.d2h-diff-table tr"));
|
|
21559
|
-
const
|
|
21560
|
-
|
|
21589
|
+
const targetRows = rows.filter((candidate) => {
|
|
21590
|
+
const rowLine = diffRowLineNumber(candidate);
|
|
21591
|
+
return rowLine !== null && rowLine >= start && rowLine <= end;
|
|
21592
|
+
});
|
|
21593
|
+
const targetLines = new Set(targetRows.map((candidate) => diffRowLineNumber(candidate)).filter((value) => value !== null));
|
|
21594
|
+
for (let lineNo = start;lineNo <= end; lineNo++) {
|
|
21595
|
+
if (!targetLines.has(lineNo))
|
|
21596
|
+
return false;
|
|
21597
|
+
}
|
|
21598
|
+
const firstRow = targetRows[0];
|
|
21599
|
+
if (!firstRow)
|
|
21561
21600
|
return false;
|
|
21562
21601
|
clearDiffLineFocus();
|
|
21563
|
-
|
|
21564
|
-
|
|
21602
|
+
targetRows.forEach((row) => {
|
|
21603
|
+
row.classList.add("gdp-diff-line-target");
|
|
21604
|
+
});
|
|
21605
|
+
scrollDiffElementIntoView(firstRow, "center");
|
|
21565
21606
|
return true;
|
|
21566
21607
|
}
|
|
21567
21608
|
function scrollDiffElementIntoView(element, block2) {
|
|
@@ -22069,7 +22110,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22069
22110
|
}
|
|
22070
22111
|
});
|
|
22071
22112
|
}
|
|
22072
|
-
function loadFile(file, card, urlOverride) {
|
|
22113
|
+
function loadFile(file, card, urlOverride, options) {
|
|
22073
22114
|
card.classList.remove("pending");
|
|
22074
22115
|
card.classList.add("loading");
|
|
22075
22116
|
if (lazyObserver)
|
|
@@ -22103,7 +22144,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22103
22144
|
retryStale();
|
|
22104
22145
|
return;
|
|
22105
22146
|
}
|
|
22106
|
-
|
|
22147
|
+
if (!options?.immediate)
|
|
22148
|
+
await nextIdle();
|
|
22107
22149
|
if (String(myReq) !== card.dataset.reqId)
|
|
22108
22150
|
return;
|
|
22109
22151
|
renderFile(file, data, card);
|
|
@@ -22126,6 +22168,18 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22126
22168
|
});
|
|
22127
22169
|
});
|
|
22128
22170
|
}
|
|
22171
|
+
async function loadDiffFile(path) {
|
|
22172
|
+
const card = document.querySelector(diffCardSelector(path));
|
|
22173
|
+
if (!card || card.classList.contains("gdp-standalone-source"))
|
|
22174
|
+
return false;
|
|
22175
|
+
if (card.classList.contains("loaded"))
|
|
22176
|
+
return true;
|
|
22177
|
+
const file = STATE.files.find((x) => x.path === path);
|
|
22178
|
+
if (!file)
|
|
22179
|
+
return false;
|
|
22180
|
+
await loadFile(file, card, undefined, { immediate: true });
|
|
22181
|
+
return card.classList.contains("loaded");
|
|
22182
|
+
}
|
|
22129
22183
|
function mountDiff(card, file, data) {
|
|
22130
22184
|
const head = card.querySelector(".gdp-shell-header");
|
|
22131
22185
|
if (head)
|
|
@@ -22650,6 +22704,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22650
22704
|
enqueueInitialLoads,
|
|
22651
22705
|
enqueueLoad,
|
|
22652
22706
|
loadFile,
|
|
22707
|
+
loadDiffFile,
|
|
22653
22708
|
createFileBreadcrumb,
|
|
22654
22709
|
highlightInsertedSpans,
|
|
22655
22710
|
setFileCollapsed,
|
|
@@ -22931,13 +22986,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22931
22986
|
bodyExpandClose: "Collapse",
|
|
22932
22987
|
bodyExpandMore: (n2) => `Show more (${n2} lines)`,
|
|
22933
22988
|
refreshLabel: "Refresh",
|
|
22934
|
-
refreshLabelPending: "Update",
|
|
22935
22989
|
refreshTitle: "Refresh commit history",
|
|
22936
22990
|
refreshTitlePending: "History may have changed. Refresh",
|
|
22937
|
-
refreshPendingStatus: "History may have changed",
|
|
22938
|
-
refreshResultUpdated: (sha) => `Updated: ${sha} is now latest`,
|
|
22939
|
-
refreshResultUnchanged: "No new commits",
|
|
22940
|
-
refreshResultUnchangedInView: "No new commits in this view",
|
|
22941
22991
|
filterClearLabel: "Clear",
|
|
22942
22992
|
filterClearTitle: "Clear commit filter"
|
|
22943
22993
|
},
|
|
@@ -22946,13 +22996,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22946
22996
|
bodyExpandClose: "閉じる",
|
|
22947
22997
|
bodyExpandMore: (n2) => `もっと見る (${n2} 行)`,
|
|
22948
22998
|
refreshLabel: "更新",
|
|
22949
|
-
refreshLabelPending: "更新あり",
|
|
22950
22999
|
refreshTitle: "コミット履歴を更新",
|
|
22951
23000
|
refreshTitlePending: "新しい履歴がある可能性があります。更新",
|
|
22952
|
-
refreshPendingStatus: "新しい履歴がある可能性があります",
|
|
22953
|
-
refreshResultUpdated: (sha) => `更新: ${sha} が最新です`,
|
|
22954
|
-
refreshResultUnchanged: "新しいコミットはありません",
|
|
22955
|
-
refreshResultUnchangedInView: "この表示では新しいコミットはありません",
|
|
22956
23001
|
filterClearLabel: "解除",
|
|
22957
23002
|
filterClearTitle: "コミットフィルタを解除"
|
|
22958
23003
|
}
|
|
@@ -22988,7 +23033,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22988
23033
|
refreshButton.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
22989
23034
|
const refreshLabel = document.createElement("span");
|
|
22990
23035
|
refreshLabel.className = "history-refresh-label";
|
|
22991
|
-
refreshLabel.textContent =
|
|
23036
|
+
refreshLabel.textContent = "";
|
|
22992
23037
|
refreshButton.appendChild(refreshLabel);
|
|
22993
23038
|
const refreshResult = document.createElement("span");
|
|
22994
23039
|
refreshResult.className = "db-refresh-result history-refresh-result";
|
|
@@ -23239,28 +23284,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
23239
23284
|
}
|
|
23240
23285
|
function setRefreshResult(previousTopSha, nextTopSha) {
|
|
23241
23286
|
freshSha = previousTopSha && nextTopSha && previousTopSha !== nextTopSha ? nextTopSha : "";
|
|
23242
|
-
refreshStatus =
|
|
23287
|
+
refreshStatus = { type: "none" };
|
|
23243
23288
|
renderList();
|
|
23244
23289
|
}
|
|
23245
|
-
function refreshStatusMessage() {
|
|
23246
|
-
const text2 = historyText(deps.getLanguage());
|
|
23247
|
-
switch (refreshStatus.type) {
|
|
23248
|
-
case "pending":
|
|
23249
|
-
return text2.refreshPendingStatus;
|
|
23250
|
-
case "updated":
|
|
23251
|
-
return text2.refreshResultUpdated(refreshStatus.sha);
|
|
23252
|
-
case "unchanged":
|
|
23253
|
-
return refreshStatus.scoped ? text2.refreshResultUnchangedInView : text2.refreshResultUnchanged;
|
|
23254
|
-
case "none":
|
|
23255
|
-
return "";
|
|
23256
|
-
default: {
|
|
23257
|
-
const exhaustive = refreshStatus;
|
|
23258
|
-
return exhaustive;
|
|
23259
|
-
}
|
|
23260
|
-
}
|
|
23261
|
-
}
|
|
23262
23290
|
function syncRefreshStatusText() {
|
|
23263
|
-
setStatusText(loading ? "loading..." :
|
|
23291
|
+
setStatusText(loading ? "loading..." : refreshStatus.type === "pending" ? "" : commits.length ? "" : "no commits");
|
|
23264
23292
|
}
|
|
23265
23293
|
function commitInfoElement() {
|
|
23266
23294
|
return activeMount.commitInfo || document.querySelector("#history-commit-info");
|
|
@@ -23371,19 +23399,17 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
23371
23399
|
button.setAttribute("aria-label", title);
|
|
23372
23400
|
button.classList.toggle("has-update", hasPendingUpdate);
|
|
23373
23401
|
const label = button.querySelector(".history-refresh-label");
|
|
23374
|
-
if (label)
|
|
23375
|
-
label.textContent =
|
|
23376
|
-
}
|
|
23402
|
+
if (label)
|
|
23403
|
+
label.textContent = "";
|
|
23377
23404
|
}
|
|
23378
23405
|
function syncRefreshResult(result) {
|
|
23379
23406
|
const el = result ?? activeMount.refreshResult;
|
|
23380
23407
|
if (!el)
|
|
23381
23408
|
return;
|
|
23382
|
-
|
|
23383
|
-
el.
|
|
23384
|
-
el.
|
|
23385
|
-
el.classList.toggle("
|
|
23386
|
-
el.classList.toggle("pending", refreshStatus.type === "pending");
|
|
23409
|
+
el.textContent = "";
|
|
23410
|
+
el.hidden = true;
|
|
23411
|
+
el.classList.toggle("changed", false);
|
|
23412
|
+
el.classList.toggle("pending", false);
|
|
23387
23413
|
}
|
|
23388
23414
|
function syncFilterClearButton(button) {
|
|
23389
23415
|
const clearButton = button ?? activeMount.filterClearButton;
|
|
@@ -24398,11 +24424,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
24398
24424
|
},
|
|
24399
24425
|
{
|
|
24400
24426
|
kind: "paragraph",
|
|
24401
|
-
text: "Each row reports OK / WARN / ERROR with a remediation hint when relevant. The check groups are Runtime (Node / Bun / NODE_MODULE_VERSION), Package (version + execution origin including npx cache), SQLite driver, Snapshot store, Git, Discovery summary, Docker / Compose (CLI / v2 plugin / daemon / compose config dry-parse / compose ps health per discovered service), and Server. The most common hint is the npx cache fix for better-sqlite3 NODE_MODULE_VERSION mismatch (rm -rf ~/.npm/_npx then re-run with npx -y @youtyan/code-viewer@latest)."
|
|
24427
|
+
text: "Each row reports OK / WARN / ERROR with a remediation hint when relevant. The check groups are Runtime (Node / Bun / NODE_MODULE_VERSION), Package (version + execution origin including npx cache), SQLite driver, Snapshot store, Git, GitHub CLI, Discovery summary, Docker / Compose (CLI / v2 plugin / daemon / compose config dry-parse / compose ps health per discovered service), and Server. The most common hint is the npx cache fix for better-sqlite3 NODE_MODULE_VERSION mismatch (rm -rf ~/.npm/_npx then re-run with npx -y @youtyan/code-viewer@latest)."
|
|
24402
24428
|
},
|
|
24403
24429
|
{
|
|
24404
24430
|
kind: "paragraph",
|
|
24405
|
-
text: 'From the terminal — and for AI agents or CI — the same report is available without a browser. `code-viewer doctor` prints a status summary; add `--json` for the full DoctorReport (matches the /_doctor endpoint). Exit code is 1 when worstStatus is "error", so it doubles as a CI gate.'
|
|
24431
|
+
text: 'From the terminal — and for AI agents or CI — the same report is available without a browser. `code-viewer doctor` prints a status summary; add `--json` for the full DoctorReport (matches the /_doctor endpoint). Use `--bin git=/absolute/path`, `--bin docker=/absolute/path`, or `--bin gh=/absolute/path` when PATH resolves the wrong tool. Exit code is 1 when worstStatus is "error", so it doubles as a CI gate.'
|
|
24406
24432
|
},
|
|
24407
24433
|
{
|
|
24408
24434
|
kind: "command",
|
|
@@ -24413,7 +24439,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
24413
24439
|
kind: "command",
|
|
24414
24440
|
title: "Doctor JSON for agents and CI",
|
|
24415
24441
|
command: `code-viewer doctor --json
|
|
24416
|
-
code-viewer doctor --cwd /path/to/repo --port 64160 --json
|
|
24442
|
+
code-viewer doctor --cwd /path/to/repo --port 64160 --json
|
|
24443
|
+
code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=/opt/bin/gh --json`
|
|
24417
24444
|
}
|
|
24418
24445
|
]
|
|
24419
24446
|
}
|
|
@@ -24769,7 +24796,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
24769
24796
|
skills: {
|
|
24770
24797
|
nav: "Agent Skill",
|
|
24771
24798
|
title: "Agent Skill Setup",
|
|
24772
|
-
intro: "The package bundles
|
|
24799
|
+
intro: "The package bundles four skills — code-viewer-annotate, code-viewer-journal, code-viewer-query, and code-viewer-snapshot — so AI agents know when and how to create browser walkthroughs, manage Work Log task queues, run read-only queries, and capture snapshot / diff sessions. A single skill install command copies all four into the selected agent directories.",
|
|
24773
24800
|
groups: [
|
|
24774
24801
|
{
|
|
24775
24802
|
title: "Install the skills",
|
|
@@ -24814,12 +24841,16 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
24814
24841
|
"Code reviews, onboarding walkthroughs, and explanations of changes."
|
|
24815
24842
|
],
|
|
24816
24843
|
[
|
|
24817
|
-
"How to
|
|
24818
|
-
"Use
|
|
24844
|
+
"How to manage Work Log tasks",
|
|
24845
|
+
"Use the journal CLI for task lists, task creation, claim/done flows, and GitHub Issue linking."
|
|
24846
|
+
],
|
|
24847
|
+
[
|
|
24848
|
+
"How to inspect data",
|
|
24849
|
+
"Use read-only query commands for schema, SQL, saved query history, and table-wide search."
|
|
24819
24850
|
],
|
|
24820
24851
|
[
|
|
24821
|
-
"How to
|
|
24822
|
-
"Use
|
|
24852
|
+
"How to verify data changes",
|
|
24853
|
+
"Use snapshot create/list/diff workflows to compare before and after states."
|
|
24823
24854
|
]
|
|
24824
24855
|
]
|
|
24825
24856
|
}
|
|
@@ -24834,8 +24865,10 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
24834
24865
|
},
|
|
24835
24866
|
{
|
|
24836
24867
|
kind: "command",
|
|
24837
|
-
title: "Show the agent
|
|
24838
|
-
command:
|
|
24868
|
+
title: "Show the agent guides",
|
|
24869
|
+
command: `code-viewer annotate agent-help
|
|
24870
|
+
code-viewer journal agent-help
|
|
24871
|
+
code-viewer query agent-help`
|
|
24839
24872
|
}
|
|
24840
24873
|
]
|
|
24841
24874
|
}
|
|
@@ -25050,11 +25083,11 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25050
25083
|
},
|
|
25051
25084
|
{
|
|
25052
25085
|
kind: "paragraph",
|
|
25053
|
-
text: "各項目は OK / WARN / ERROR で表示され、必要に応じて対処手順のヒントが付きます。診断グループは Runtime (Node / Bun / NODE_MODULE_VERSION)、Package (バージョン + 実行元: npx cache / global / local / bunx)、SQLite driver、Snapshot store、Git、Discovery summary、Docker / Compose (CLI / v2 plugin / daemon / compose config dry parse / compose ps による各サービスのヘルス)、Server (待ち受けポート)
|
|
25086
|
+
text: "各項目は OK / WARN / ERROR で表示され、必要に応じて対処手順のヒントが付きます。診断グループは Runtime (Node / Bun / NODE_MODULE_VERSION)、Package (バージョン + 実行元: npx cache / global / local / bunx)、SQLite driver、Snapshot store、Git、GitHub CLI、Discovery summary、Docker / Compose (CLI / v2 plugin / daemon / compose config dry parse / compose ps による各サービスのヘルス)、Server (待ち受けポート) です。よくあるヒントは npx キャッシュ起因の better-sqlite3 NODE_MODULE_VERSION 不一致で、rm -rf ~/.npm/_npx の後に npx -y @youtyan/code-viewer@latest を再実行する手順を表示します。"
|
|
25054
25087
|
},
|
|
25055
25088
|
{
|
|
25056
25089
|
kind: "paragraph",
|
|
25057
|
-
text: 'ターミナルからは `code-viewer doctor` で同じレポートを取得できます。AI エージェントや CI からは `--json` で /_doctor と同じ DoctorReport を受け取れます。worstStatus が "error" なら exit code 1 を返すので CI ガードに直接使えます。'
|
|
25090
|
+
text: 'ターミナルからは `code-viewer doctor` で同じレポートを取得できます。AI エージェントや CI からは `--json` で /_doctor と同じ DoctorReport を受け取れます。PATH と別の実行ファイルを使いたい場合は `--bin git=/absolute/path`、`--bin docker=/absolute/path`、`--bin gh=/absolute/path` を指定できます。worstStatus が "error" なら exit code 1 を返すので CI ガードに直接使えます。'
|
|
25058
25091
|
},
|
|
25059
25092
|
{
|
|
25060
25093
|
kind: "command",
|
|
@@ -25065,7 +25098,8 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25065
25098
|
kind: "command",
|
|
25066
25099
|
title: "AI / CI 用 doctor JSON",
|
|
25067
25100
|
command: `code-viewer doctor --json
|
|
25068
|
-
code-viewer doctor --cwd /path/to/repo --port 64160 --json
|
|
25101
|
+
code-viewer doctor --cwd /path/to/repo --port 64160 --json
|
|
25102
|
+
code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=/opt/bin/gh --json`
|
|
25069
25103
|
}
|
|
25070
25104
|
]
|
|
25071
25105
|
}
|
|
@@ -25421,7 +25455,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25421
25455
|
skills: {
|
|
25422
25456
|
nav: "スキル登録",
|
|
25423
25457
|
title: "Agent Skill の登録",
|
|
25424
|
-
intro: "このパッケージには
|
|
25458
|
+
intro: "このパッケージには 4 つのスキル (code-viewer-annotate / code-viewer-journal / code-viewer-query / code-viewer-snapshot) が同梱されており、AI エージェントに annotate でのウォークスルー、Work Log タスク管理、read-only な query、スナップショット / 差分の使い分けを教えます。skill install を 1 回叩くと、選んだエージェントすべてに 4 スキルがコピーされます。",
|
|
25425
25459
|
groups: [
|
|
25426
25460
|
{
|
|
25427
25461
|
title: "スキルをインストールする",
|
|
@@ -25466,12 +25500,16 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25466
25500
|
"コードレビュー、オンボーディング、変更内容の解説で使うこと。"
|
|
25467
25501
|
],
|
|
25468
25502
|
[
|
|
25469
|
-
"
|
|
25470
|
-
"
|
|
25503
|
+
"Work Log タスクをどう扱うか",
|
|
25504
|
+
"journal CLI でタスク一覧取得、作成、claim/done、GitHub Issue 紐づけを行うこと。"
|
|
25505
|
+
],
|
|
25506
|
+
[
|
|
25507
|
+
"データをどう調査するか",
|
|
25508
|
+
"read-only な query コマンドでスキーマ、SQL、履歴、横断検索を扱うこと。"
|
|
25471
25509
|
],
|
|
25472
25510
|
[
|
|
25473
|
-
"
|
|
25474
|
-
"
|
|
25511
|
+
"データ変更をどう検証するか",
|
|
25512
|
+
"snapshot create/list/diff で before/after を比較すること。"
|
|
25475
25513
|
]
|
|
25476
25514
|
]
|
|
25477
25515
|
}
|
|
@@ -25487,7 +25525,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25487
25525
|
{
|
|
25488
25526
|
kind: "command",
|
|
25489
25527
|
title: "AI 向けガイドを表示",
|
|
25490
|
-
command:
|
|
25528
|
+
command: `code-viewer annotate agent-help
|
|
25529
|
+
code-viewer journal agent-help
|
|
25530
|
+
code-viewer query agent-help`
|
|
25491
25531
|
}
|
|
25492
25532
|
]
|
|
25493
25533
|
}
|
|
@@ -26169,6 +26209,2004 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
26169
26209
|
return { setupHunkExpand };
|
|
26170
26210
|
}
|
|
26171
26211
|
|
|
26212
|
+
// web-src/core/journal.ts
|
|
26213
|
+
var JOURNAL_TASK_STATUSES = [
|
|
26214
|
+
"draft",
|
|
26215
|
+
"todo",
|
|
26216
|
+
"doing",
|
|
26217
|
+
"blocked",
|
|
26218
|
+
"done"
|
|
26219
|
+
];
|
|
26220
|
+
var JOURNAL_TASK_PRIORITIES = ["p0", "p1", "p2", "p3"];
|
|
26221
|
+
var JOURNAL_LABEL_MAX_CHARS = 48;
|
|
26222
|
+
var JOURNAL_MAX_LABELS = 24;
|
|
26223
|
+
var PRIORITY_SCORE = {
|
|
26224
|
+
p0: 0,
|
|
26225
|
+
p1: 1,
|
|
26226
|
+
p2: 2,
|
|
26227
|
+
p3: 3
|
|
26228
|
+
};
|
|
26229
|
+
function isJournalTaskStatus(value) {
|
|
26230
|
+
return JOURNAL_TASK_STATUSES.includes(value);
|
|
26231
|
+
}
|
|
26232
|
+
function todayIsoDate(now = new Date) {
|
|
26233
|
+
const year = now.getFullYear();
|
|
26234
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
26235
|
+
const date = String(now.getDate()).padStart(2, "0");
|
|
26236
|
+
return `${year}-${month}-${date}`;
|
|
26237
|
+
}
|
|
26238
|
+
function taskClaimActive(task, now = Date.now()) {
|
|
26239
|
+
if (!task.claim)
|
|
26240
|
+
return false;
|
|
26241
|
+
const expires = Date.parse(task.claim.lease_expires_at);
|
|
26242
|
+
return Number.isFinite(expires) && expires > now;
|
|
26243
|
+
}
|
|
26244
|
+
function normalizeJournalLabel(value) {
|
|
26245
|
+
if (typeof value !== "string")
|
|
26246
|
+
return null;
|
|
26247
|
+
const label = value.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^\p{L}\p{N}._:-]+/gu, "").slice(0, JOURNAL_LABEL_MAX_CHARS).replace(/^[-_.:]+|[-_.:]+$/g, "");
|
|
26248
|
+
return label || null;
|
|
26249
|
+
}
|
|
26250
|
+
function normalizeJournalLabels(raw) {
|
|
26251
|
+
if (!Array.isArray(raw))
|
|
26252
|
+
return [];
|
|
26253
|
+
const labels = [];
|
|
26254
|
+
const seen = new Set;
|
|
26255
|
+
for (const item of raw) {
|
|
26256
|
+
const label = normalizeJournalLabel(item);
|
|
26257
|
+
if (!label || seen.has(label))
|
|
26258
|
+
continue;
|
|
26259
|
+
seen.add(label);
|
|
26260
|
+
labels.push(label);
|
|
26261
|
+
if (labels.length >= JOURNAL_MAX_LABELS)
|
|
26262
|
+
break;
|
|
26263
|
+
}
|
|
26264
|
+
return labels;
|
|
26265
|
+
}
|
|
26266
|
+
function journalIssueLabel(issueNumber) {
|
|
26267
|
+
return `issue-${issueNumber}`;
|
|
26268
|
+
}
|
|
26269
|
+
function journalIssueRepoLabel(repo) {
|
|
26270
|
+
if (typeof repo !== "string")
|
|
26271
|
+
return null;
|
|
26272
|
+
return normalizeJournalLabel(`repo-${repo.replace(/[\\/]+/g, "-")}`);
|
|
26273
|
+
}
|
|
26274
|
+
function filterJournalTasks(state, filter = {}) {
|
|
26275
|
+
const statuses = Array.isArray(filter.status) ? filter.status : filter.status ? [filter.status] : undefined;
|
|
26276
|
+
const labels = filter.labels?.filter(Boolean) || [];
|
|
26277
|
+
return state.tasks.filter((task) => {
|
|
26278
|
+
if (statuses && !statuses.includes(task.status))
|
|
26279
|
+
return false;
|
|
26280
|
+
if (labels.length && !labels.every((label) => task.labels.includes(label))) {
|
|
26281
|
+
return false;
|
|
26282
|
+
}
|
|
26283
|
+
if (!filter.includeClaimed && taskClaimActive(task, filter.now))
|
|
26284
|
+
return false;
|
|
26285
|
+
return true;
|
|
26286
|
+
});
|
|
26287
|
+
}
|
|
26288
|
+
function selectNextJournalTasks(state, filter = {}, limit = 1) {
|
|
26289
|
+
const status = filter.status || "todo";
|
|
26290
|
+
const indexed = filterJournalTasks(state, { ...filter, status }).map((task, index) => ({ task, index }));
|
|
26291
|
+
indexed.sort((a2, b2) => {
|
|
26292
|
+
const priority = PRIORITY_SCORE[a2.task.priority] - PRIORITY_SCORE[b2.task.priority];
|
|
26293
|
+
if (priority !== 0)
|
|
26294
|
+
return priority;
|
|
26295
|
+
if (a2.index !== b2.index)
|
|
26296
|
+
return a2.index - b2.index;
|
|
26297
|
+
return a2.task.created_at.localeCompare(b2.task.created_at);
|
|
26298
|
+
});
|
|
26299
|
+
return indexed.slice(0, Math.max(0, limit)).map((item) => item.task);
|
|
26300
|
+
}
|
|
26301
|
+
|
|
26302
|
+
// web-src/views/journal-view.ts
|
|
26303
|
+
var GITHUB_ISSUE_INITIAL_LIMIT = 30;
|
|
26304
|
+
var GITHUB_ISSUE_LIMIT_STEP = 30;
|
|
26305
|
+
var GITHUB_ISSUE_LIMIT_MAX = 100;
|
|
26306
|
+
var GITHUB_ISSUE_RATE_LIMIT_RETRY_MS = 15000;
|
|
26307
|
+
var TASK_DRAG_AUTOSCROLL_EDGE_PX = 72;
|
|
26308
|
+
var TASK_DRAG_AUTOSCROLL_MAX_STEP_PX = 22;
|
|
26309
|
+
var TASK_EDITOR_WIDTH_STORAGE_KEY = "code-viewer:journal-task-editor-width";
|
|
26310
|
+
var TASK_EDITOR_DEFAULT_WIDTH = 380;
|
|
26311
|
+
var TASK_EDITOR_MIN_WIDTH = 320;
|
|
26312
|
+
var TASK_EDITOR_MAX_WIDTH = 760;
|
|
26313
|
+
function isJournalRoute(route) {
|
|
26314
|
+
return route.screen === "journal";
|
|
26315
|
+
}
|
|
26316
|
+
function parseGithubLabels(raw) {
|
|
26317
|
+
const labels = [];
|
|
26318
|
+
const seen = new Set;
|
|
26319
|
+
for (const item of raw.split(",")) {
|
|
26320
|
+
const label = item.trim().slice(0, 200);
|
|
26321
|
+
if (!label || seen.has(label))
|
|
26322
|
+
continue;
|
|
26323
|
+
seen.add(label);
|
|
26324
|
+
labels.push(label);
|
|
26325
|
+
}
|
|
26326
|
+
return labels;
|
|
26327
|
+
}
|
|
26328
|
+
function taskDateRange(task) {
|
|
26329
|
+
const first = task.source_date || task.due_date;
|
|
26330
|
+
const last = task.due_date || task.source_date;
|
|
26331
|
+
if (!first || !last)
|
|
26332
|
+
return null;
|
|
26333
|
+
return first <= last ? { start: first, end: last } : { start: last, end: first };
|
|
26334
|
+
}
|
|
26335
|
+
function taskCoversDate(task, date) {
|
|
26336
|
+
const range = taskDateRange(task);
|
|
26337
|
+
return !!range && range.start <= date && date <= range.end;
|
|
26338
|
+
}
|
|
26339
|
+
function monthKey(date) {
|
|
26340
|
+
return date.slice(0, 7);
|
|
26341
|
+
}
|
|
26342
|
+
function monthTitle(date, locale) {
|
|
26343
|
+
const parsed = new Date(`${monthKey(date)}-01T00:00:00`);
|
|
26344
|
+
return parsed.toLocaleString(locale, { month: "long", year: "numeric" });
|
|
26345
|
+
}
|
|
26346
|
+
function offsetMonth(date, delta) {
|
|
26347
|
+
const parsed = new Date(`${monthKey(date)}-01T00:00:00`);
|
|
26348
|
+
parsed.setMonth(parsed.getMonth() + delta);
|
|
26349
|
+
return `${todayIsoDate(parsed).slice(0, 7)}-01`;
|
|
26350
|
+
}
|
|
26351
|
+
function labelChip(label) {
|
|
26352
|
+
const chip = document.createElement("span");
|
|
26353
|
+
chip.className = "journal-label-chip";
|
|
26354
|
+
chip.textContent = label;
|
|
26355
|
+
return chip;
|
|
26356
|
+
}
|
|
26357
|
+
function taskField(labelText, control) {
|
|
26358
|
+
const field = document.createElement("label");
|
|
26359
|
+
field.className = "journal-field";
|
|
26360
|
+
const caption = document.createElement("span");
|
|
26361
|
+
caption.className = "journal-field-label";
|
|
26362
|
+
caption.textContent = labelText;
|
|
26363
|
+
field.append(caption, control);
|
|
26364
|
+
return field;
|
|
26365
|
+
}
|
|
26366
|
+
function createLabelEditor(initialLabels, placeholder, suggestionLabels = [], removeLabelText = (label) => `Remove ${label}`) {
|
|
26367
|
+
const root = document.createElement("div");
|
|
26368
|
+
root.className = "journal-label-editor";
|
|
26369
|
+
const input = document.createElement("input");
|
|
26370
|
+
input.className = "journal-label-editor-input";
|
|
26371
|
+
input.type = "text";
|
|
26372
|
+
input.autocomplete = "off";
|
|
26373
|
+
const labels = normalizeJournalLabels(initialLabels);
|
|
26374
|
+
const suggestions = normalizeJournalLabels(suggestionLabels);
|
|
26375
|
+
const menu = document.createElement("div");
|
|
26376
|
+
menu.className = "journal-label-editor-suggestions";
|
|
26377
|
+
menu.hidden = true;
|
|
26378
|
+
const quick = document.createElement("div");
|
|
26379
|
+
quick.className = "journal-label-editor-quick";
|
|
26380
|
+
quick.hidden = true;
|
|
26381
|
+
let highlightedSuggestion = -1;
|
|
26382
|
+
const suggestionItems = () => {
|
|
26383
|
+
const raw = input.value.trim().toLowerCase();
|
|
26384
|
+
const normalized = normalizeJournalLabel(raw) || raw;
|
|
26385
|
+
return suggestions.filter((label) => !labels.includes(label) && (!normalized || label.includes(normalized))).slice(0, 8);
|
|
26386
|
+
};
|
|
26387
|
+
const renderSuggestions = () => {
|
|
26388
|
+
const items = suggestionItems();
|
|
26389
|
+
const hasQuery = !!(normalizeJournalLabel(input.value) || input.value.trim());
|
|
26390
|
+
menu.replaceChildren();
|
|
26391
|
+
if (!items.length || !hasQuery || document.activeElement !== input) {
|
|
26392
|
+
menu.hidden = true;
|
|
26393
|
+
highlightedSuggestion = -1;
|
|
26394
|
+
return;
|
|
26395
|
+
}
|
|
26396
|
+
menu.hidden = false;
|
|
26397
|
+
if (highlightedSuggestion >= items.length)
|
|
26398
|
+
highlightedSuggestion = 0;
|
|
26399
|
+
items.forEach((label, index) => {
|
|
26400
|
+
const button = document.createElement("button");
|
|
26401
|
+
button.type = "button";
|
|
26402
|
+
button.className = "journal-label-editor-suggestion";
|
|
26403
|
+
button.classList.toggle("active", index === highlightedSuggestion);
|
|
26404
|
+
button.textContent = label;
|
|
26405
|
+
button.addEventListener("mousedown", (event) => {
|
|
26406
|
+
event.preventDefault();
|
|
26407
|
+
commit(label);
|
|
26408
|
+
});
|
|
26409
|
+
menu.appendChild(button);
|
|
26410
|
+
});
|
|
26411
|
+
};
|
|
26412
|
+
const renderQuickSuggestions = () => {
|
|
26413
|
+
const items = suggestionItems().slice(0, 6);
|
|
26414
|
+
quick.replaceChildren();
|
|
26415
|
+
quick.hidden = !items.length;
|
|
26416
|
+
for (const label of items) {
|
|
26417
|
+
const button = document.createElement("button");
|
|
26418
|
+
button.type = "button";
|
|
26419
|
+
button.className = "journal-label-editor-quick-chip";
|
|
26420
|
+
button.textContent = label;
|
|
26421
|
+
button.addEventListener("click", (event) => {
|
|
26422
|
+
event.preventDefault();
|
|
26423
|
+
event.stopPropagation();
|
|
26424
|
+
commit(label);
|
|
26425
|
+
});
|
|
26426
|
+
quick.appendChild(button);
|
|
26427
|
+
}
|
|
26428
|
+
};
|
|
26429
|
+
const render = () => {
|
|
26430
|
+
root.replaceChildren();
|
|
26431
|
+
for (const label of labels) {
|
|
26432
|
+
const chip = document.createElement("span");
|
|
26433
|
+
chip.className = "journal-label-editor-chip";
|
|
26434
|
+
const name = document.createElement("span");
|
|
26435
|
+
name.textContent = label;
|
|
26436
|
+
const remove = document.createElement("button");
|
|
26437
|
+
remove.type = "button";
|
|
26438
|
+
remove.className = "journal-label-editor-remove";
|
|
26439
|
+
remove.setAttribute("aria-label", removeLabelText(label));
|
|
26440
|
+
remove.innerHTML = iconSvg("octicon-x", X_16_PATH);
|
|
26441
|
+
remove.addEventListener("click", (event) => {
|
|
26442
|
+
event.stopPropagation();
|
|
26443
|
+
const index = labels.indexOf(label);
|
|
26444
|
+
if (index >= 0)
|
|
26445
|
+
labels.splice(index, 1);
|
|
26446
|
+
render();
|
|
26447
|
+
input.focus();
|
|
26448
|
+
});
|
|
26449
|
+
chip.append(name, remove);
|
|
26450
|
+
root.appendChild(chip);
|
|
26451
|
+
}
|
|
26452
|
+
input.placeholder = labels.length ? "" : placeholder;
|
|
26453
|
+
root.append(input, quick, menu);
|
|
26454
|
+
renderQuickSuggestions();
|
|
26455
|
+
renderSuggestions();
|
|
26456
|
+
};
|
|
26457
|
+
const commit = (raw) => {
|
|
26458
|
+
const label = normalizeJournalLabel(raw);
|
|
26459
|
+
input.value = "";
|
|
26460
|
+
if (!label || labels.includes(label))
|
|
26461
|
+
return false;
|
|
26462
|
+
labels.push(label);
|
|
26463
|
+
highlightedSuggestion = -1;
|
|
26464
|
+
render();
|
|
26465
|
+
input.focus();
|
|
26466
|
+
return true;
|
|
26467
|
+
};
|
|
26468
|
+
input.addEventListener("keydown", (event) => {
|
|
26469
|
+
const items = suggestionItems();
|
|
26470
|
+
if (event.key === "ArrowDown" && items.length) {
|
|
26471
|
+
event.preventDefault();
|
|
26472
|
+
highlightedSuggestion = (highlightedSuggestion + 1) % items.length;
|
|
26473
|
+
renderSuggestions();
|
|
26474
|
+
return;
|
|
26475
|
+
}
|
|
26476
|
+
if (event.key === "ArrowUp" && items.length) {
|
|
26477
|
+
event.preventDefault();
|
|
26478
|
+
highlightedSuggestion = (highlightedSuggestion - 1 + items.length) % items.length;
|
|
26479
|
+
renderSuggestions();
|
|
26480
|
+
return;
|
|
26481
|
+
}
|
|
26482
|
+
const value = input.value.trim();
|
|
26483
|
+
if (event.key === "Enter" || event.key === ",") {
|
|
26484
|
+
event.preventDefault();
|
|
26485
|
+
commit(event.key === "Enter" && highlightedSuggestion >= 0 && items[highlightedSuggestion] ? items[highlightedSuggestion] : value);
|
|
26486
|
+
return;
|
|
26487
|
+
}
|
|
26488
|
+
if (event.key === "Tab" && value) {
|
|
26489
|
+
event.preventDefault();
|
|
26490
|
+
commit(value);
|
|
26491
|
+
return;
|
|
26492
|
+
}
|
|
26493
|
+
if (event.key === "Backspace" && !value && labels.length) {
|
|
26494
|
+
labels.pop();
|
|
26495
|
+
render();
|
|
26496
|
+
input.focus();
|
|
26497
|
+
}
|
|
26498
|
+
});
|
|
26499
|
+
input.addEventListener("input", () => {
|
|
26500
|
+
highlightedSuggestion = -1;
|
|
26501
|
+
renderQuickSuggestions();
|
|
26502
|
+
renderSuggestions();
|
|
26503
|
+
});
|
|
26504
|
+
input.addEventListener("focus", renderSuggestions);
|
|
26505
|
+
input.addEventListener("paste", (event) => {
|
|
26506
|
+
const pasted = event.clipboardData?.getData("text") || "";
|
|
26507
|
+
if (!/[,\n]/.test(pasted))
|
|
26508
|
+
return;
|
|
26509
|
+
event.preventDefault();
|
|
26510
|
+
for (const item of pasted.split(/[,\n]+/))
|
|
26511
|
+
commit(item);
|
|
26512
|
+
});
|
|
26513
|
+
input.addEventListener("blur", () => {
|
|
26514
|
+
if (input.value.trim())
|
|
26515
|
+
commit(input.value);
|
|
26516
|
+
window.setTimeout(() => {
|
|
26517
|
+
menu.hidden = true;
|
|
26518
|
+
highlightedSuggestion = -1;
|
|
26519
|
+
}, 120);
|
|
26520
|
+
});
|
|
26521
|
+
root.addEventListener("click", () => input.focus());
|
|
26522
|
+
render();
|
|
26523
|
+
return {
|
|
26524
|
+
element: root,
|
|
26525
|
+
getLabels: () => [...labels]
|
|
26526
|
+
};
|
|
26527
|
+
}
|
|
26528
|
+
function setButtonBusy(button, busy) {
|
|
26529
|
+
button.disabled = busy;
|
|
26530
|
+
button.setAttribute("aria-busy", busy ? "true" : "false");
|
|
26531
|
+
}
|
|
26532
|
+
function createJournalView(deps) {
|
|
26533
|
+
const root = document.createElement("section");
|
|
26534
|
+
root.className = "journal-page";
|
|
26535
|
+
root.hidden = true;
|
|
26536
|
+
root.setAttribute("aria-label", deps.getText().ariaLabel);
|
|
26537
|
+
let mounted = false;
|
|
26538
|
+
let lifecycle = 0;
|
|
26539
|
+
let data = null;
|
|
26540
|
+
let activeTab = "journal";
|
|
26541
|
+
let selectedDate = todayIsoDate();
|
|
26542
|
+
let selectedEntryId = "";
|
|
26543
|
+
let creatingEntry = false;
|
|
26544
|
+
let selectedTaskId = "";
|
|
26545
|
+
let labelFilter = "";
|
|
26546
|
+
let editorMode = "split";
|
|
26547
|
+
let message = "";
|
|
26548
|
+
let draggingTaskId = "";
|
|
26549
|
+
let draggingGithubIssueNumber = null;
|
|
26550
|
+
let githubIssues = [];
|
|
26551
|
+
let githubIssuesLoading = false;
|
|
26552
|
+
let githubIssuesLoaded = false;
|
|
26553
|
+
let githubIssuesError = "";
|
|
26554
|
+
let githubRepoFilter = "";
|
|
26555
|
+
let githubLabelFilter = "";
|
|
26556
|
+
let githubSearchFilter = "";
|
|
26557
|
+
let githubStateFilter = "open";
|
|
26558
|
+
let githubIssueLimit = GITHUB_ISSUE_INITIAL_LIMIT;
|
|
26559
|
+
let githubIssuesRetryAt = 0;
|
|
26560
|
+
let githubIssueGeneration = 0;
|
|
26561
|
+
let githubIssuesAutoRequested = false;
|
|
26562
|
+
let githubInboxOpen = false;
|
|
26563
|
+
let suppressTaskClick = false;
|
|
26564
|
+
let pendingTaskEditorFocus = false;
|
|
26565
|
+
let pendingTaskEditorReveal = false;
|
|
26566
|
+
let lastTaskDropTarget = null;
|
|
26567
|
+
let nativeTaskDragActive = false;
|
|
26568
|
+
let taskEditorResizePointerId = null;
|
|
26569
|
+
let taskEditorResizeStartX = 0;
|
|
26570
|
+
let taskEditorResizeStartWidth = TASK_EDITOR_DEFAULT_WIDTH;
|
|
26571
|
+
let taskEditorFocusRequest = 0;
|
|
26572
|
+
let taskDragAutoScrollFrame = 0;
|
|
26573
|
+
let taskDragAutoScrollX = 0;
|
|
26574
|
+
let taskDragAutoScrollY = 0;
|
|
26575
|
+
let pointerDrag = null;
|
|
26576
|
+
function text2() {
|
|
26577
|
+
return deps.getText();
|
|
26578
|
+
}
|
|
26579
|
+
function selectedEntry() {
|
|
26580
|
+
if (!data)
|
|
26581
|
+
return null;
|
|
26582
|
+
return data.journal.entries.find((entry) => entry.id === selectedEntryId) || null;
|
|
26583
|
+
}
|
|
26584
|
+
function selectedTask() {
|
|
26585
|
+
if (!data)
|
|
26586
|
+
return null;
|
|
26587
|
+
return data.tasks.tasks.find((task) => task.id === selectedTaskId) || null;
|
|
26588
|
+
}
|
|
26589
|
+
function journalEditorHasFocus() {
|
|
26590
|
+
const active = document.activeElement;
|
|
26591
|
+
return !!(active && root.contains(active) && active.closest(".journal-editor, .journal-task-editor"));
|
|
26592
|
+
}
|
|
26593
|
+
function labelSuggestions() {
|
|
26594
|
+
return (data?.labels || []).filter((label) => !/^issue-\d+$/.test(label) && !label.startsWith("repo-"));
|
|
26595
|
+
}
|
|
26596
|
+
function taskEditorMaxWidth() {
|
|
26597
|
+
const layoutWidth = root.querySelector(".journal-tasks-layout")?.clientWidth || window.innerWidth;
|
|
26598
|
+
if (layoutWidth <= 1100)
|
|
26599
|
+
return TASK_EDITOR_MAX_WIDTH;
|
|
26600
|
+
return Math.max(TASK_EDITOR_MIN_WIDTH, Math.min(TASK_EDITOR_MAX_WIDTH, layoutWidth - 560));
|
|
26601
|
+
}
|
|
26602
|
+
function clampTaskEditorWidth(width) {
|
|
26603
|
+
return Math.max(TASK_EDITOR_MIN_WIDTH, Math.min(taskEditorMaxWidth(), Math.round(width)));
|
|
26604
|
+
}
|
|
26605
|
+
function applyTaskEditorWidth(width, persist = true) {
|
|
26606
|
+
const clamped = clampTaskEditorWidth(width);
|
|
26607
|
+
document.documentElement.style.setProperty("--journal-task-editor-w", `${clamped}px`);
|
|
26608
|
+
if (!persist)
|
|
26609
|
+
return;
|
|
26610
|
+
try {
|
|
26611
|
+
window.localStorage.setItem(TASK_EDITOR_WIDTH_STORAGE_KEY, String(clamped));
|
|
26612
|
+
} catch {}
|
|
26613
|
+
}
|
|
26614
|
+
function restoreTaskEditorWidth() {
|
|
26615
|
+
let width = TASK_EDITOR_DEFAULT_WIDTH;
|
|
26616
|
+
try {
|
|
26617
|
+
const raw = window.localStorage.getItem(TASK_EDITOR_WIDTH_STORAGE_KEY);
|
|
26618
|
+
const parsed = raw ? Number(raw) : NaN;
|
|
26619
|
+
if (Number.isFinite(parsed))
|
|
26620
|
+
width = parsed;
|
|
26621
|
+
} catch {
|
|
26622
|
+
width = TASK_EDITOR_DEFAULT_WIDTH;
|
|
26623
|
+
}
|
|
26624
|
+
applyTaskEditorWidth(width, false);
|
|
26625
|
+
}
|
|
26626
|
+
function startTaskEditorResize(event, panel) {
|
|
26627
|
+
if (event.button !== 0)
|
|
26628
|
+
return;
|
|
26629
|
+
taskEditorResizePointerId = event.pointerId;
|
|
26630
|
+
taskEditorResizeStartX = event.clientX;
|
|
26631
|
+
taskEditorResizeStartWidth = panel.getBoundingClientRect().width;
|
|
26632
|
+
panel.setPointerCapture?.(event.pointerId);
|
|
26633
|
+
document.body.classList.add("journal-task-editor-resizing");
|
|
26634
|
+
event.preventDefault();
|
|
26635
|
+
}
|
|
26636
|
+
function handleTaskEditorResizeMove(event) {
|
|
26637
|
+
if (taskEditorResizePointerId !== event.pointerId)
|
|
26638
|
+
return;
|
|
26639
|
+
applyTaskEditorWidth(taskEditorResizeStartWidth - (event.clientX - taskEditorResizeStartX), false);
|
|
26640
|
+
}
|
|
26641
|
+
function finishTaskEditorResize(event) {
|
|
26642
|
+
if (taskEditorResizePointerId !== event.pointerId)
|
|
26643
|
+
return;
|
|
26644
|
+
taskEditorResizePointerId = null;
|
|
26645
|
+
document.body.classList.remove("journal-task-editor-resizing");
|
|
26646
|
+
const panel = root.querySelector(".journal-task-editor");
|
|
26647
|
+
if (panel)
|
|
26648
|
+
applyTaskEditorWidth(panel.getBoundingClientRect().width);
|
|
26649
|
+
}
|
|
26650
|
+
function revealTaskEditorIfNeeded(editor) {
|
|
26651
|
+
const layout = root.querySelector(".journal-tasks-layout");
|
|
26652
|
+
if (!layout || layout.scrollHeight <= layout.clientHeight + 1)
|
|
26653
|
+
return;
|
|
26654
|
+
const layoutRect = layout.getBoundingClientRect();
|
|
26655
|
+
const editorRect = editor.getBoundingClientRect();
|
|
26656
|
+
const topPadding = 8;
|
|
26657
|
+
const bottomPadding = 8;
|
|
26658
|
+
const visibleTop = layoutRect.top + topPadding;
|
|
26659
|
+
const visibleBottom = layoutRect.bottom - bottomPadding;
|
|
26660
|
+
const minVisibleTopGap = Math.min(160, editorRect.height);
|
|
26661
|
+
if (editorRect.top >= visibleTop && editorRect.top <= visibleBottom - minVisibleTopGap)
|
|
26662
|
+
return;
|
|
26663
|
+
layout.scrollTop += editorRect.top - visibleTop;
|
|
26664
|
+
}
|
|
26665
|
+
function scheduleTaskEditorInteraction(options = {}) {
|
|
26666
|
+
if (options.focusEditor)
|
|
26667
|
+
pendingTaskEditorFocus = true;
|
|
26668
|
+
if (options.revealEditor)
|
|
26669
|
+
pendingTaskEditorReveal = true;
|
|
26670
|
+
const request = ++taskEditorFocusRequest;
|
|
26671
|
+
const interact = (final = false) => {
|
|
26672
|
+
if (request !== taskEditorFocusRequest)
|
|
26673
|
+
return;
|
|
26674
|
+
const editor = root.querySelector(".journal-task-editor");
|
|
26675
|
+
const input = editor?.querySelector(".journal-title-input") || null;
|
|
26676
|
+
if (pendingTaskEditorReveal && editor) {
|
|
26677
|
+
revealTaskEditorIfNeeded(editor);
|
|
26678
|
+
pendingTaskEditorReveal = false;
|
|
26679
|
+
}
|
|
26680
|
+
if (pendingTaskEditorFocus && input)
|
|
26681
|
+
input.focus({ preventScroll: true });
|
|
26682
|
+
if (final && request === taskEditorFocusRequest) {
|
|
26683
|
+
pendingTaskEditorFocus = false;
|
|
26684
|
+
pendingTaskEditorReveal = false;
|
|
26685
|
+
}
|
|
26686
|
+
};
|
|
26687
|
+
window.requestAnimationFrame(() => interact());
|
|
26688
|
+
window.setTimeout(() => interact(), 50);
|
|
26689
|
+
window.setTimeout(() => interact(true), 250);
|
|
26690
|
+
}
|
|
26691
|
+
function selectTask(taskId, options = {}) {
|
|
26692
|
+
activeTab = "tasks";
|
|
26693
|
+
selectedTaskId = taskId;
|
|
26694
|
+
setRoute({ tab: "tasks", task: taskId });
|
|
26695
|
+
render();
|
|
26696
|
+
if (options.focusEditor || options.revealEditor)
|
|
26697
|
+
scheduleTaskEditorInteraction(options);
|
|
26698
|
+
}
|
|
26699
|
+
function routeFor(next) {
|
|
26700
|
+
return {
|
|
26701
|
+
screen: "journal",
|
|
26702
|
+
tab: activeTab,
|
|
26703
|
+
date: selectedDate,
|
|
26704
|
+
...labelFilter ? { label: labelFilter } : {},
|
|
26705
|
+
...selectedTaskId ? { task: selectedTaskId } : {},
|
|
26706
|
+
...next,
|
|
26707
|
+
range: deps.currentRange()
|
|
26708
|
+
};
|
|
26709
|
+
}
|
|
26710
|
+
function applyRoute(route = deps.getRoute()) {
|
|
26711
|
+
if (!isJournalRoute(route))
|
|
26712
|
+
return;
|
|
26713
|
+
activeTab = route.tab || activeTab || "journal";
|
|
26714
|
+
selectedDate = route.date || selectedDate || todayIsoDate();
|
|
26715
|
+
labelFilter = route.label || "";
|
|
26716
|
+
selectedTaskId = route.task || "";
|
|
26717
|
+
}
|
|
26718
|
+
function mount() {
|
|
26719
|
+
if (mounted)
|
|
26720
|
+
return;
|
|
26721
|
+
const content = document.getElementById("content");
|
|
26722
|
+
if (!content)
|
|
26723
|
+
return;
|
|
26724
|
+
document.getElementById("diff")?.setAttribute("hidden", "true");
|
|
26725
|
+
document.getElementById("empty")?.classList.add("hidden");
|
|
26726
|
+
document.getElementById("history-commit-info")?.setAttribute("hidden", "true");
|
|
26727
|
+
root.hidden = false;
|
|
26728
|
+
content.appendChild(root);
|
|
26729
|
+
mounted = true;
|
|
26730
|
+
document.body.classList.add("gdp-journal-page");
|
|
26731
|
+
deps.setPageMode();
|
|
26732
|
+
deps.syncHeaderMenu();
|
|
26733
|
+
}
|
|
26734
|
+
function suspend() {
|
|
26735
|
+
lifecycle++;
|
|
26736
|
+
if (!mounted) {
|
|
26737
|
+
root.remove();
|
|
26738
|
+
root.hidden = false;
|
|
26739
|
+
document.body.classList.remove("gdp-journal-page");
|
|
26740
|
+
document.getElementById("diff")?.removeAttribute("hidden");
|
|
26741
|
+
return;
|
|
26742
|
+
}
|
|
26743
|
+
root.remove();
|
|
26744
|
+
root.hidden = false;
|
|
26745
|
+
document.body.classList.remove("gdp-journal-page");
|
|
26746
|
+
document.getElementById("diff")?.removeAttribute("hidden");
|
|
26747
|
+
mounted = false;
|
|
26748
|
+
}
|
|
26749
|
+
async function requestJournal(action, body, afterResponse) {
|
|
26750
|
+
deps.setStatus("refreshing");
|
|
26751
|
+
const res = await deps.trackLoad(fetch("/_journal", {
|
|
26752
|
+
method: "POST",
|
|
26753
|
+
headers: {
|
|
26754
|
+
"Content-Type": "application/json",
|
|
26755
|
+
"X-Code-Viewer-Action": "1"
|
|
26756
|
+
},
|
|
26757
|
+
body: JSON.stringify({ action, ...body })
|
|
26758
|
+
}));
|
|
26759
|
+
if (!res.ok)
|
|
26760
|
+
throw new Error(await res.text());
|
|
26761
|
+
const response = await res.json();
|
|
26762
|
+
afterResponse?.(response);
|
|
26763
|
+
await refresh();
|
|
26764
|
+
return response;
|
|
26765
|
+
}
|
|
26766
|
+
async function refresh() {
|
|
26767
|
+
const seq = ++lifecycle;
|
|
26768
|
+
message = data ? "" : text2().loading;
|
|
26769
|
+
render();
|
|
26770
|
+
deps.setStatus("refreshing");
|
|
26771
|
+
try {
|
|
26772
|
+
const next = await deps.trackLoad(fetch("/_journal").then(async (res) => {
|
|
26773
|
+
if (!res.ok)
|
|
26774
|
+
throw new Error(await res.text());
|
|
26775
|
+
return await res.json();
|
|
26776
|
+
}));
|
|
26777
|
+
if (seq !== lifecycle)
|
|
26778
|
+
return;
|
|
26779
|
+
data = next;
|
|
26780
|
+
if (selectedEntryId && !next.journal.entries.some((entry) => entry.id === selectedEntryId)) {
|
|
26781
|
+
selectedEntryId = "";
|
|
26782
|
+
}
|
|
26783
|
+
if (!creatingEntry && !selectedEntryId) {
|
|
26784
|
+
selectedEntryId = next.journal.entries.find((entry) => entry.date === selectedDate)?.id || "";
|
|
26785
|
+
}
|
|
26786
|
+
if (selectedTaskId && !next.tasks.tasks.some((task) => task.id === selectedTaskId)) {
|
|
26787
|
+
selectedTaskId = "";
|
|
26788
|
+
}
|
|
26789
|
+
message = "";
|
|
26790
|
+
deps.setStatus("live");
|
|
26791
|
+
render();
|
|
26792
|
+
} catch (error2) {
|
|
26793
|
+
if (seq !== lifecycle)
|
|
26794
|
+
return;
|
|
26795
|
+
message = error2 instanceof Error ? error2.message : text2().loadFailed;
|
|
26796
|
+
deps.setStatus("error");
|
|
26797
|
+
render();
|
|
26798
|
+
}
|
|
26799
|
+
}
|
|
26800
|
+
function setRoute(next, replace2 = true) {
|
|
26801
|
+
deps.setRoute(routeFor(next), replace2);
|
|
26802
|
+
}
|
|
26803
|
+
function renderHeader() {
|
|
26804
|
+
const header = document.createElement("div");
|
|
26805
|
+
header.className = "journal-header";
|
|
26806
|
+
const title = document.createElement("div");
|
|
26807
|
+
title.className = "journal-title";
|
|
26808
|
+
title.textContent = text2().title;
|
|
26809
|
+
const tabs = document.createElement("div");
|
|
26810
|
+
tabs.className = "seg journal-tabs";
|
|
26811
|
+
tabs.setAttribute("role", "tablist");
|
|
26812
|
+
for (const tab of ["journal", "tasks"]) {
|
|
26813
|
+
const button = document.createElement("button");
|
|
26814
|
+
button.type = "button";
|
|
26815
|
+
button.classList.toggle("active", activeTab === tab);
|
|
26816
|
+
button.textContent = text2().tabs[tab];
|
|
26817
|
+
button.addEventListener("click", () => {
|
|
26818
|
+
activeTab = tab;
|
|
26819
|
+
setRoute({ tab });
|
|
26820
|
+
});
|
|
26821
|
+
tabs.appendChild(button);
|
|
26822
|
+
}
|
|
26823
|
+
const refreshButton = document.createElement("button");
|
|
26824
|
+
refreshButton.type = "button";
|
|
26825
|
+
refreshButton.className = "global-icon-action journal-icon-action";
|
|
26826
|
+
refreshButton.title = text2().refresh;
|
|
26827
|
+
refreshButton.setAttribute("aria-label", text2().refresh);
|
|
26828
|
+
refreshButton.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
26829
|
+
refreshButton.addEventListener("click", () => void refresh());
|
|
26830
|
+
const status = document.createElement("span");
|
|
26831
|
+
status.className = "journal-status";
|
|
26832
|
+
status.textContent = message;
|
|
26833
|
+
status.hidden = !message;
|
|
26834
|
+
status.setAttribute("role", "status");
|
|
26835
|
+
header.append(title, tabs, refreshButton, status);
|
|
26836
|
+
return header;
|
|
26837
|
+
}
|
|
26838
|
+
function renderCalendar() {
|
|
26839
|
+
const panel = document.createElement("aside");
|
|
26840
|
+
panel.className = "journal-calendar";
|
|
26841
|
+
const head = document.createElement("div");
|
|
26842
|
+
head.className = "journal-calendar-head";
|
|
26843
|
+
const prev = document.createElement("button");
|
|
26844
|
+
prev.type = "button";
|
|
26845
|
+
prev.textContent = "<";
|
|
26846
|
+
prev.setAttribute("aria-label", text2().previousMonth);
|
|
26847
|
+
const next = document.createElement("button");
|
|
26848
|
+
next.type = "button";
|
|
26849
|
+
next.textContent = ">";
|
|
26850
|
+
next.setAttribute("aria-label", text2().nextMonth);
|
|
26851
|
+
const title = document.createElement("strong");
|
|
26852
|
+
title.textContent = monthTitle(selectedDate, text2().locale);
|
|
26853
|
+
prev.addEventListener("click", () => {
|
|
26854
|
+
selectedDate = offsetMonth(selectedDate, -1);
|
|
26855
|
+
selectedEntryId = "";
|
|
26856
|
+
creatingEntry = false;
|
|
26857
|
+
setRoute({ date: selectedDate });
|
|
26858
|
+
});
|
|
26859
|
+
next.addEventListener("click", () => {
|
|
26860
|
+
selectedDate = offsetMonth(selectedDate, 1);
|
|
26861
|
+
selectedEntryId = "";
|
|
26862
|
+
creatingEntry = false;
|
|
26863
|
+
setRoute({ date: selectedDate });
|
|
26864
|
+
});
|
|
26865
|
+
head.append(prev, title, next);
|
|
26866
|
+
const days = document.createElement("div");
|
|
26867
|
+
days.className = "journal-calendar-grid";
|
|
26868
|
+
for (const label of text2().weekDays) {
|
|
26869
|
+
const day = document.createElement("span");
|
|
26870
|
+
day.className = "journal-weekday";
|
|
26871
|
+
day.textContent = label;
|
|
26872
|
+
days.appendChild(day);
|
|
26873
|
+
}
|
|
26874
|
+
const monthStart = new Date(`${monthKey(selectedDate)}-01T00:00:00`);
|
|
26875
|
+
const first = new Date(monthStart);
|
|
26876
|
+
first.setDate(first.getDate() - first.getDay());
|
|
26877
|
+
const entryDates = new Set((data?.journal.entries || []).map((entry) => entry.date));
|
|
26878
|
+
const monthTasks = (data?.tasks.tasks || []).filter((task) => {
|
|
26879
|
+
const range = taskDateRange(task);
|
|
26880
|
+
return !!range && range.end >= todayIsoDate(first) && range.start <= todayIsoDate(new Date(first.getTime() + 41 * 24 * 60 * 60 * 1000));
|
|
26881
|
+
}).slice(0, 60);
|
|
26882
|
+
const today = todayIsoDate();
|
|
26883
|
+
for (let i2 = 0;i2 < 42; i2++) {
|
|
26884
|
+
const current = new Date(first);
|
|
26885
|
+
current.setDate(first.getDate() + i2);
|
|
26886
|
+
const value = todayIsoDate(current);
|
|
26887
|
+
const button = document.createElement("button");
|
|
26888
|
+
button.type = "button";
|
|
26889
|
+
button.className = "journal-day-button";
|
|
26890
|
+
button.classList.toggle("muted", monthKey(value) !== monthKey(selectedDate));
|
|
26891
|
+
button.classList.toggle("active", value === selectedDate);
|
|
26892
|
+
button.classList.toggle("today", value === today);
|
|
26893
|
+
button.classList.toggle("has-entry", entryDates.has(value));
|
|
26894
|
+
const number = document.createElement("span");
|
|
26895
|
+
number.className = "journal-day-number";
|
|
26896
|
+
number.textContent = String(current.getDate());
|
|
26897
|
+
const bars = document.createElement("span");
|
|
26898
|
+
bars.className = "journal-day-ranges";
|
|
26899
|
+
const tasksForDay = monthTasks.filter((item) => taskCoversDate(item, value));
|
|
26900
|
+
for (const task of tasksForDay.slice(0, 3)) {
|
|
26901
|
+
const range = taskDateRange(task);
|
|
26902
|
+
if (!range)
|
|
26903
|
+
continue;
|
|
26904
|
+
const bar = document.createElement("span");
|
|
26905
|
+
bar.className = `journal-day-range journal-range-${task.priority}`;
|
|
26906
|
+
bar.classList.toggle("start", value === range.start);
|
|
26907
|
+
bar.classList.toggle("end", value === range.end);
|
|
26908
|
+
bar.classList.toggle("single", value === range.start && value === range.end);
|
|
26909
|
+
if (value === range.start)
|
|
26910
|
+
bar.textContent = task.title;
|
|
26911
|
+
bar.title = `${task.title} (${range.start} - ${range.end})`;
|
|
26912
|
+
bars.appendChild(bar);
|
|
26913
|
+
}
|
|
26914
|
+
if (tasksForDay.length > 3) {
|
|
26915
|
+
const more = document.createElement("span");
|
|
26916
|
+
more.className = "journal-day-range-more";
|
|
26917
|
+
more.textContent = text2().moreTasks(tasksForDay.length - 3);
|
|
26918
|
+
more.title = tasksForDay.slice(3).map((task) => task.title).join(`
|
|
26919
|
+
`);
|
|
26920
|
+
bars.appendChild(more);
|
|
26921
|
+
}
|
|
26922
|
+
button.append(number, bars);
|
|
26923
|
+
button.addEventListener("click", () => {
|
|
26924
|
+
selectedDate = value;
|
|
26925
|
+
creatingEntry = false;
|
|
26926
|
+
selectedEntryId = data?.journal.entries.find((entry) => entry.date === value)?.id || "";
|
|
26927
|
+
setRoute({ date: value, tab: "journal" });
|
|
26928
|
+
});
|
|
26929
|
+
days.appendChild(button);
|
|
26930
|
+
}
|
|
26931
|
+
panel.append(head, days);
|
|
26932
|
+
return panel;
|
|
26933
|
+
}
|
|
26934
|
+
function renderJournalEntryList(entries) {
|
|
26935
|
+
const list2 = document.createElement("div");
|
|
26936
|
+
list2.className = "journal-entry-list";
|
|
26937
|
+
if (!entries.length) {
|
|
26938
|
+
const empty = document.createElement("div");
|
|
26939
|
+
empty.className = "journal-empty";
|
|
26940
|
+
empty.textContent = text2().noEntries;
|
|
26941
|
+
list2.appendChild(empty);
|
|
26942
|
+
return list2;
|
|
26943
|
+
}
|
|
26944
|
+
for (const entry of entries) {
|
|
26945
|
+
const row = document.createElement("button");
|
|
26946
|
+
row.type = "button";
|
|
26947
|
+
row.className = "journal-entry-row";
|
|
26948
|
+
row.classList.toggle("active", entry.id === selectedEntryId);
|
|
26949
|
+
row.textContent = entry.title || entry.body.split(`
|
|
26950
|
+
`)[0] || entry.date;
|
|
26951
|
+
row.addEventListener("click", () => {
|
|
26952
|
+
selectedEntryId = entry.id;
|
|
26953
|
+
creatingEntry = false;
|
|
26954
|
+
render();
|
|
26955
|
+
});
|
|
26956
|
+
list2.appendChild(row);
|
|
26957
|
+
}
|
|
26958
|
+
return list2;
|
|
26959
|
+
}
|
|
26960
|
+
function renderJournalEditor() {
|
|
26961
|
+
const selected = selectedEntry();
|
|
26962
|
+
const wrap = document.createElement("section");
|
|
26963
|
+
wrap.className = "journal-editor";
|
|
26964
|
+
const head = document.createElement("div");
|
|
26965
|
+
head.className = "journal-section-head";
|
|
26966
|
+
const dateInput = document.createElement("input");
|
|
26967
|
+
dateInput.type = "date";
|
|
26968
|
+
dateInput.value = selectedDate;
|
|
26969
|
+
dateInput.addEventListener("change", () => {
|
|
26970
|
+
selectedDate = dateInput.value || todayIsoDate();
|
|
26971
|
+
selectedEntryId = "";
|
|
26972
|
+
creatingEntry = false;
|
|
26973
|
+
setRoute({ date: selectedDate });
|
|
26974
|
+
});
|
|
26975
|
+
const newButton = document.createElement("button");
|
|
26976
|
+
newButton.type = "button";
|
|
26977
|
+
newButton.innerHTML = `${iconSvg("octicon-plus", PLUS_16_PATH)}<span>${text2().new}</span>`;
|
|
26978
|
+
newButton.addEventListener("click", () => {
|
|
26979
|
+
selectedEntryId = "";
|
|
26980
|
+
creatingEntry = true;
|
|
26981
|
+
render();
|
|
26982
|
+
});
|
|
26983
|
+
head.append(dateInput, newButton);
|
|
26984
|
+
const entriesForDate = (data?.journal.entries || []).filter((entry) => entry.date === selectedDate);
|
|
26985
|
+
const title = document.createElement("input");
|
|
26986
|
+
title.className = "journal-title-input";
|
|
26987
|
+
title.placeholder = text2().titlePlaceholder;
|
|
26988
|
+
title.value = selected?.title || "";
|
|
26989
|
+
const labels = createLabelEditor(selected ? selected.labels : [], text2().labelPlaceholder, labelSuggestions(), text2().removeLabel);
|
|
26990
|
+
const body = document.createElement("textarea");
|
|
26991
|
+
body.className = "journal-body-input";
|
|
26992
|
+
body.placeholder = text2().entryBodyPlaceholder;
|
|
26993
|
+
body.value = selected?.body || "";
|
|
26994
|
+
const actions = document.createElement("div");
|
|
26995
|
+
actions.className = "journal-editor-actions";
|
|
26996
|
+
const save = document.createElement("button");
|
|
26997
|
+
save.type = "button";
|
|
26998
|
+
save.className = "journal-primary-action";
|
|
26999
|
+
save.textContent = selected ? text2().saveEntry : text2().addEntry;
|
|
27000
|
+
save.addEventListener("click", async () => {
|
|
27001
|
+
setButtonBusy(save, true);
|
|
27002
|
+
try {
|
|
27003
|
+
const payload = {
|
|
27004
|
+
date: selectedDate,
|
|
27005
|
+
title: title.value,
|
|
27006
|
+
labels: labels.getLabels(),
|
|
27007
|
+
body: body.value
|
|
27008
|
+
};
|
|
27009
|
+
if (selected)
|
|
27010
|
+
await requestJournal("update-entry", { id: selected.id, ...payload });
|
|
27011
|
+
else
|
|
27012
|
+
await requestJournal("add-entry", payload, (next) => {
|
|
27013
|
+
if (next.entry) {
|
|
27014
|
+
selectedEntryId = next.entry.id;
|
|
27015
|
+
creatingEntry = false;
|
|
27016
|
+
}
|
|
27017
|
+
});
|
|
27018
|
+
} catch (error2) {
|
|
27019
|
+
message = error2 instanceof Error ? error2.message : text2().saveEntryFailed;
|
|
27020
|
+
deps.setStatus("error");
|
|
27021
|
+
render();
|
|
27022
|
+
} finally {
|
|
27023
|
+
setButtonBusy(save, false);
|
|
27024
|
+
}
|
|
27025
|
+
});
|
|
27026
|
+
actions.appendChild(save);
|
|
27027
|
+
if (selected) {
|
|
27028
|
+
const del = document.createElement("button");
|
|
27029
|
+
del.type = "button";
|
|
27030
|
+
del.className = "journal-danger-action";
|
|
27031
|
+
del.innerHTML = `${iconSvg("octicon-trash", TRASH_16_PATH)}<span>${text2().delete}</span>`;
|
|
27032
|
+
del.addEventListener("click", async () => {
|
|
27033
|
+
const ok = await showConfirmDialog({
|
|
27034
|
+
title: `${text2().delete}?`,
|
|
27035
|
+
body: selected.title || selected.date,
|
|
27036
|
+
confirmLabel: text2().delete,
|
|
27037
|
+
danger: true,
|
|
27038
|
+
focusReturnTarget: del
|
|
27039
|
+
});
|
|
27040
|
+
if (!ok)
|
|
27041
|
+
return;
|
|
27042
|
+
setButtonBusy(del, true);
|
|
27043
|
+
try {
|
|
27044
|
+
await requestJournal("delete-entry", { id: selected.id });
|
|
27045
|
+
selectedEntryId = "";
|
|
27046
|
+
creatingEntry = false;
|
|
27047
|
+
} catch (error2) {
|
|
27048
|
+
message = error2 instanceof Error ? error2.message : text2().deleteEntryFailed;
|
|
27049
|
+
deps.setStatus("error");
|
|
27050
|
+
render();
|
|
27051
|
+
} finally {
|
|
27052
|
+
setButtonBusy(del, false);
|
|
27053
|
+
}
|
|
27054
|
+
});
|
|
27055
|
+
actions.appendChild(del);
|
|
27056
|
+
}
|
|
27057
|
+
wrap.append(head, renderJournalEntryList(entriesForDate), title, labels.element, body, actions);
|
|
27058
|
+
return wrap;
|
|
27059
|
+
}
|
|
27060
|
+
function renderRelatedTasks() {
|
|
27061
|
+
const panel = document.createElement("aside");
|
|
27062
|
+
panel.className = "journal-related";
|
|
27063
|
+
const heading2 = document.createElement("h2");
|
|
27064
|
+
heading2.textContent = text2().relatedTasks;
|
|
27065
|
+
panel.appendChild(heading2);
|
|
27066
|
+
const tasks = (data?.tasks.tasks || []).filter((task) => taskCoversDate(task, selectedDate));
|
|
27067
|
+
if (!tasks.length) {
|
|
27068
|
+
const empty = document.createElement("div");
|
|
27069
|
+
empty.className = "journal-empty";
|
|
27070
|
+
empty.textContent = text2().noRelatedTasks;
|
|
27071
|
+
panel.appendChild(empty);
|
|
27072
|
+
return panel;
|
|
27073
|
+
}
|
|
27074
|
+
for (const task of tasks)
|
|
27075
|
+
panel.appendChild(renderTaskCard(task, { compact: true }));
|
|
27076
|
+
return panel;
|
|
27077
|
+
}
|
|
27078
|
+
function renderJournalTab() {
|
|
27079
|
+
const body = document.createElement("div");
|
|
27080
|
+
body.className = "journal-daily-layout";
|
|
27081
|
+
body.append(renderCalendar(), renderJournalEditor(), renderRelatedTasks());
|
|
27082
|
+
return body;
|
|
27083
|
+
}
|
|
27084
|
+
function taskVisible(task) {
|
|
27085
|
+
return !labelFilter || task.labels.includes(labelFilter);
|
|
27086
|
+
}
|
|
27087
|
+
function renderTaskCard(task, options = {}) {
|
|
27088
|
+
const card = document.createElement("button");
|
|
27089
|
+
card.type = "button";
|
|
27090
|
+
card.className = "journal-task-card";
|
|
27091
|
+
card.dataset.taskId = task.id;
|
|
27092
|
+
card.classList.toggle("selected", task.id === selectedTaskId);
|
|
27093
|
+
card.classList.toggle("compact", !!options.compact);
|
|
27094
|
+
card.addEventListener("click", (event) => {
|
|
27095
|
+
if (draggingTaskId || suppressTaskClick || taskDragHandleFromTarget(card, event.target))
|
|
27096
|
+
return;
|
|
27097
|
+
selectTask(task.id, { revealEditor: true });
|
|
27098
|
+
});
|
|
27099
|
+
card.addEventListener("dragstart", (event) => {
|
|
27100
|
+
if (!taskDragHandleFromTarget(card, event.target)) {
|
|
27101
|
+
event.preventDefault();
|
|
27102
|
+
return;
|
|
27103
|
+
}
|
|
27104
|
+
draggingTaskId = task.id;
|
|
27105
|
+
nativeTaskDragActive = true;
|
|
27106
|
+
lastTaskDropTarget = null;
|
|
27107
|
+
card.classList.add("dragging");
|
|
27108
|
+
if (event.dataTransfer) {
|
|
27109
|
+
event.dataTransfer.effectAllowed = "move";
|
|
27110
|
+
event.dataTransfer.setData("text/plain", task.id);
|
|
27111
|
+
const rect = card.getBoundingClientRect();
|
|
27112
|
+
event.dataTransfer.setDragImage?.(card, Math.max(0, Math.min(rect.width, event.clientX - rect.left)), Math.max(0, Math.min(rect.height, event.clientY - rect.top)));
|
|
27113
|
+
}
|
|
27114
|
+
});
|
|
27115
|
+
card.addEventListener("dragenter", (event) => {
|
|
27116
|
+
if (!draggingTaskId && !draggingGithubIssue() || draggingTaskId === task.id)
|
|
27117
|
+
return;
|
|
27118
|
+
event.preventDefault();
|
|
27119
|
+
const after = isTaskDropAfter(card, event);
|
|
27120
|
+
lastTaskDropTarget = after ? { after_id: task.id } : { before_id: task.id };
|
|
27121
|
+
markTaskDropTarget(card, after);
|
|
27122
|
+
});
|
|
27123
|
+
card.addEventListener("dragover", (event) => {
|
|
27124
|
+
const issue = draggingGithubIssue();
|
|
27125
|
+
if (!draggingTaskId && !issue || draggingTaskId === task.id)
|
|
27126
|
+
return;
|
|
27127
|
+
event.preventDefault();
|
|
27128
|
+
if (event.dataTransfer)
|
|
27129
|
+
event.dataTransfer.dropEffect = issue ? "copy" : "move";
|
|
27130
|
+
scheduleTaskDragAutoScroll(event);
|
|
27131
|
+
const after = isTaskDropAfter(card, event);
|
|
27132
|
+
lastTaskDropTarget = after ? { after_id: task.id } : { before_id: task.id };
|
|
27133
|
+
markTaskDropTarget(card, after);
|
|
27134
|
+
});
|
|
27135
|
+
card.addEventListener("dragleave", (event) => {
|
|
27136
|
+
const related = event.relatedTarget;
|
|
27137
|
+
if (related && card.contains(related))
|
|
27138
|
+
return;
|
|
27139
|
+
lastTaskDropTarget = null;
|
|
27140
|
+
card.classList.remove("drop-target", "drop-after");
|
|
27141
|
+
});
|
|
27142
|
+
card.addEventListener("drop", (event) => {
|
|
27143
|
+
const issue = draggingGithubIssue() || githubIssueFromDataTransfer(event.dataTransfer);
|
|
27144
|
+
if (!draggingTaskId && !issue || draggingTaskId === task.id)
|
|
27145
|
+
return;
|
|
27146
|
+
event.preventDefault();
|
|
27147
|
+
event.stopPropagation();
|
|
27148
|
+
const after = isTaskDropAfter(card, event);
|
|
27149
|
+
const target = after ? { after_id: task.id } : { before_id: task.id };
|
|
27150
|
+
lastTaskDropTarget = null;
|
|
27151
|
+
if (issue)
|
|
27152
|
+
linkGithubIssueToTask(issue, target);
|
|
27153
|
+
else
|
|
27154
|
+
moveDraggedTask(target);
|
|
27155
|
+
});
|
|
27156
|
+
card.addEventListener("dragend", (event) => finishNativeTaskDrag(event));
|
|
27157
|
+
card.addEventListener("pointerdown", (event) => {
|
|
27158
|
+
beginPointerTaskDrag(task.id, event);
|
|
27159
|
+
});
|
|
27160
|
+
const top = document.createElement("span");
|
|
27161
|
+
top.className = "journal-task-card-top";
|
|
27162
|
+
const priority = document.createElement("span");
|
|
27163
|
+
priority.className = `journal-priority journal-priority-${task.priority}`;
|
|
27164
|
+
priority.textContent = text2().priorityLabels[task.priority];
|
|
27165
|
+
const status = document.createElement("span");
|
|
27166
|
+
status.className = "journal-task-status";
|
|
27167
|
+
status.textContent = text2().statusLabels[task.status];
|
|
27168
|
+
top.append(priority, status);
|
|
27169
|
+
const dragHandle = document.createElement("span");
|
|
27170
|
+
dragHandle.className = "journal-task-drag-handle";
|
|
27171
|
+
dragHandle.draggable = true;
|
|
27172
|
+
dragHandle.title = text2().dragTask;
|
|
27173
|
+
dragHandle.innerHTML = iconSvg("journal-task-drag-icon", GRABBER_16_PATH);
|
|
27174
|
+
dragHandle.setAttribute("aria-hidden", "true");
|
|
27175
|
+
top.appendChild(dragHandle);
|
|
27176
|
+
const title = document.createElement("strong");
|
|
27177
|
+
title.textContent = task.title;
|
|
27178
|
+
const labels = document.createElement("span");
|
|
27179
|
+
labels.className = "journal-task-labels";
|
|
27180
|
+
for (const label of task.labels)
|
|
27181
|
+
labels.appendChild(labelChip(label));
|
|
27182
|
+
card.append(top, title, labels);
|
|
27183
|
+
if (task.due_date) {
|
|
27184
|
+
const due = document.createElement("span");
|
|
27185
|
+
due.className = "journal-task-due";
|
|
27186
|
+
due.textContent = `${text2().duePrefix} ${task.due_date}`;
|
|
27187
|
+
card.appendChild(due);
|
|
27188
|
+
}
|
|
27189
|
+
if (task.claim) {
|
|
27190
|
+
const claim = document.createElement("span");
|
|
27191
|
+
claim.className = "journal-task-claim";
|
|
27192
|
+
claim.textContent = text2().claimedBy(task.claim.by);
|
|
27193
|
+
card.appendChild(claim);
|
|
27194
|
+
}
|
|
27195
|
+
return card;
|
|
27196
|
+
}
|
|
27197
|
+
function taskDragHandleFromTarget(card, target) {
|
|
27198
|
+
const handle = target?.closest(".journal-task-drag-handle");
|
|
27199
|
+
return handle && card.contains(handle) ? handle : null;
|
|
27200
|
+
}
|
|
27201
|
+
function isTaskDropAfter(card, event) {
|
|
27202
|
+
const rect = card.getBoundingClientRect();
|
|
27203
|
+
return event.clientY > rect.top + rect.height / 2;
|
|
27204
|
+
}
|
|
27205
|
+
function clearTaskDropTargets() {
|
|
27206
|
+
root.querySelectorAll(".journal-task-card.drop-target, .journal-task-column.drop-target").forEach((el) => {
|
|
27207
|
+
el.classList.remove("drop-target", "drop-after");
|
|
27208
|
+
});
|
|
27209
|
+
}
|
|
27210
|
+
function clearTaskDragState() {
|
|
27211
|
+
root.querySelectorAll(".journal-task-card.dragging").forEach((el) => {
|
|
27212
|
+
el.classList.remove("dragging");
|
|
27213
|
+
});
|
|
27214
|
+
stopTaskDragAutoScroll();
|
|
27215
|
+
clearTaskDropTargets();
|
|
27216
|
+
lastTaskDropTarget = null;
|
|
27217
|
+
nativeTaskDragActive = false;
|
|
27218
|
+
draggingTaskId = "";
|
|
27219
|
+
suppressTaskClick = true;
|
|
27220
|
+
window.setTimeout(() => {
|
|
27221
|
+
suppressTaskClick = false;
|
|
27222
|
+
}, 50);
|
|
27223
|
+
}
|
|
27224
|
+
function clearGithubIssueDragState() {
|
|
27225
|
+
draggingGithubIssueNumber = null;
|
|
27226
|
+
lastTaskDropTarget = null;
|
|
27227
|
+
stopTaskDragAutoScroll();
|
|
27228
|
+
clearTaskDropTargets();
|
|
27229
|
+
}
|
|
27230
|
+
function taskCardById(taskId) {
|
|
27231
|
+
return root.querySelector(`.journal-task-card[data-task-id="${CSS.escape(taskId)}"]`);
|
|
27232
|
+
}
|
|
27233
|
+
function taskDragScrollTargetAtPoint(clientX, clientY) {
|
|
27234
|
+
const element = document.elementFromPoint(clientX, clientY);
|
|
27235
|
+
let cursor = element?.closest(".journal-github-issues, .journal-github-panel, .journal-task-board-wrap, .journal-tasks-layout") || root.querySelector(".journal-task-board-wrap");
|
|
27236
|
+
while (cursor && cursor !== root) {
|
|
27237
|
+
const style = window.getComputedStyle(cursor);
|
|
27238
|
+
if (/(auto|scroll)/.test(style.overflowY) && cursor.scrollHeight > cursor.clientHeight + 1) {
|
|
27239
|
+
return cursor;
|
|
27240
|
+
}
|
|
27241
|
+
cursor = cursor.parentElement;
|
|
27242
|
+
}
|
|
27243
|
+
const layout = root.querySelector(".journal-tasks-layout");
|
|
27244
|
+
return layout && layout.scrollHeight > layout.clientHeight + 1 ? layout : null;
|
|
27245
|
+
}
|
|
27246
|
+
function taskDragAutoScrollDelta(target, clientY) {
|
|
27247
|
+
const rect = target.getBoundingClientRect();
|
|
27248
|
+
const canScrollUp = target.scrollTop > 0;
|
|
27249
|
+
const canScrollDown = target.scrollTop + target.clientHeight < target.scrollHeight - 1;
|
|
27250
|
+
const topDistance = clientY - rect.top;
|
|
27251
|
+
if (canScrollUp && topDistance < TASK_DRAG_AUTOSCROLL_EDGE_PX) {
|
|
27252
|
+
const intensity = (TASK_DRAG_AUTOSCROLL_EDGE_PX - Math.max(0, topDistance)) / TASK_DRAG_AUTOSCROLL_EDGE_PX;
|
|
27253
|
+
return -Math.max(4, Math.round(TASK_DRAG_AUTOSCROLL_MAX_STEP_PX * intensity));
|
|
27254
|
+
}
|
|
27255
|
+
const bottomDistance = rect.bottom - clientY;
|
|
27256
|
+
if (canScrollDown && bottomDistance < TASK_DRAG_AUTOSCROLL_EDGE_PX) {
|
|
27257
|
+
const intensity = (TASK_DRAG_AUTOSCROLL_EDGE_PX - Math.max(0, bottomDistance)) / TASK_DRAG_AUTOSCROLL_EDGE_PX;
|
|
27258
|
+
return Math.max(4, Math.round(TASK_DRAG_AUTOSCROLL_MAX_STEP_PX * intensity));
|
|
27259
|
+
}
|
|
27260
|
+
return 0;
|
|
27261
|
+
}
|
|
27262
|
+
function stopTaskDragAutoScroll() {
|
|
27263
|
+
if (!taskDragAutoScrollFrame)
|
|
27264
|
+
return;
|
|
27265
|
+
window.cancelAnimationFrame(taskDragAutoScrollFrame);
|
|
27266
|
+
taskDragAutoScrollFrame = 0;
|
|
27267
|
+
}
|
|
27268
|
+
function runTaskDragAutoScroll() {
|
|
27269
|
+
taskDragAutoScrollFrame = 0;
|
|
27270
|
+
if (!draggingTaskId && !draggingGithubIssue())
|
|
27271
|
+
return;
|
|
27272
|
+
const target = taskDragScrollTargetAtPoint(taskDragAutoScrollX, taskDragAutoScrollY);
|
|
27273
|
+
if (!target)
|
|
27274
|
+
return;
|
|
27275
|
+
const delta = taskDragAutoScrollDelta(target, taskDragAutoScrollY);
|
|
27276
|
+
if (!delta)
|
|
27277
|
+
return;
|
|
27278
|
+
target.scrollBy({ top: delta, behavior: "auto" });
|
|
27279
|
+
markPointerDropTarget({
|
|
27280
|
+
clientX: taskDragAutoScrollX,
|
|
27281
|
+
clientY: taskDragAutoScrollY
|
|
27282
|
+
});
|
|
27283
|
+
taskDragAutoScrollFrame = window.requestAnimationFrame(runTaskDragAutoScroll);
|
|
27284
|
+
}
|
|
27285
|
+
function scheduleTaskDragAutoScroll(event) {
|
|
27286
|
+
taskDragAutoScrollX = event.clientX;
|
|
27287
|
+
taskDragAutoScrollY = event.clientY;
|
|
27288
|
+
if (taskDragAutoScrollFrame)
|
|
27289
|
+
return;
|
|
27290
|
+
taskDragAutoScrollFrame = window.requestAnimationFrame(runTaskDragAutoScroll);
|
|
27291
|
+
}
|
|
27292
|
+
function markTaskDropTarget(card, after) {
|
|
27293
|
+
clearTaskDropTargets();
|
|
27294
|
+
card.classList.add("drop-target");
|
|
27295
|
+
card.classList.toggle("drop-after", after);
|
|
27296
|
+
}
|
|
27297
|
+
function markColumnDropTarget(column) {
|
|
27298
|
+
clearTaskDropTargets();
|
|
27299
|
+
column.classList.add("drop-target");
|
|
27300
|
+
}
|
|
27301
|
+
function taskDropTargetFromPoint(event) {
|
|
27302
|
+
const element = document.elementFromPoint(event.clientX, event.clientY);
|
|
27303
|
+
if (!element)
|
|
27304
|
+
return null;
|
|
27305
|
+
const card = element.closest(".journal-task-card");
|
|
27306
|
+
if (card?.dataset.taskId && card.dataset.taskId !== draggingTaskId) {
|
|
27307
|
+
const after = isTaskDropAfter(card, event);
|
|
27308
|
+
return after ? { after_id: card.dataset.taskId } : { before_id: card.dataset.taskId };
|
|
27309
|
+
}
|
|
27310
|
+
const column = element.closest(".journal-task-column");
|
|
27311
|
+
const status = column?.dataset.status;
|
|
27312
|
+
return isJournalTaskStatus(status) ? { status } : null;
|
|
27313
|
+
}
|
|
27314
|
+
function finishNativeTaskDrag(event) {
|
|
27315
|
+
const issue = draggingGithubIssue();
|
|
27316
|
+
if (!draggingTaskId && !issue) {
|
|
27317
|
+
clearTaskDropTargets();
|
|
27318
|
+
stopTaskDragAutoScroll();
|
|
27319
|
+
return;
|
|
27320
|
+
}
|
|
27321
|
+
const target = taskDropTargetFromPoint(event) || lastTaskDropTarget;
|
|
27322
|
+
if (!target) {
|
|
27323
|
+
if (issue)
|
|
27324
|
+
clearGithubIssueDragState();
|
|
27325
|
+
else
|
|
27326
|
+
clearTaskDragState();
|
|
27327
|
+
return;
|
|
27328
|
+
}
|
|
27329
|
+
lastTaskDropTarget = null;
|
|
27330
|
+
if (issue)
|
|
27331
|
+
linkGithubIssueToTask(issue, target);
|
|
27332
|
+
else
|
|
27333
|
+
moveDraggedTask(target);
|
|
27334
|
+
}
|
|
27335
|
+
function markPointerDropTarget(event) {
|
|
27336
|
+
const element = document.elementFromPoint(event.clientX, event.clientY);
|
|
27337
|
+
const card = element?.closest(".journal-task-card");
|
|
27338
|
+
if (card?.dataset.taskId && card.dataset.taskId !== draggingTaskId) {
|
|
27339
|
+
markTaskDropTarget(card, isTaskDropAfter(card, event));
|
|
27340
|
+
return;
|
|
27341
|
+
}
|
|
27342
|
+
const column = element?.closest(".journal-task-column");
|
|
27343
|
+
if (column) {
|
|
27344
|
+
markColumnDropTarget(column);
|
|
27345
|
+
return;
|
|
27346
|
+
}
|
|
27347
|
+
clearTaskDropTargets();
|
|
27348
|
+
}
|
|
27349
|
+
function beginPointerTaskDrag(taskId, event) {
|
|
27350
|
+
if (event.button !== 0)
|
|
27351
|
+
return;
|
|
27352
|
+
const source = event.currentTarget;
|
|
27353
|
+
if (!source)
|
|
27354
|
+
return;
|
|
27355
|
+
if (!taskDragHandleFromTarget(source, event.target))
|
|
27356
|
+
return;
|
|
27357
|
+
pointerDrag = {
|
|
27358
|
+
taskId,
|
|
27359
|
+
pointerId: event.pointerId,
|
|
27360
|
+
source,
|
|
27361
|
+
startX: event.clientX,
|
|
27362
|
+
startY: event.clientY,
|
|
27363
|
+
active: false
|
|
27364
|
+
};
|
|
27365
|
+
source.setPointerCapture?.(event.pointerId);
|
|
27366
|
+
document.addEventListener("pointermove", handlePointerTaskDragMove);
|
|
27367
|
+
document.addEventListener("pointerup", handlePointerTaskDragEnd);
|
|
27368
|
+
document.addEventListener("pointercancel", handlePointerTaskDragCancel);
|
|
27369
|
+
}
|
|
27370
|
+
function stopPointerTaskDragListeners() {
|
|
27371
|
+
document.removeEventListener("pointermove", handlePointerTaskDragMove);
|
|
27372
|
+
document.removeEventListener("pointerup", handlePointerTaskDragEnd);
|
|
27373
|
+
document.removeEventListener("pointercancel", handlePointerTaskDragCancel);
|
|
27374
|
+
}
|
|
27375
|
+
function handlePointerTaskDragMove(event) {
|
|
27376
|
+
if (!pointerDrag)
|
|
27377
|
+
return;
|
|
27378
|
+
if (event.pointerId !== pointerDrag.pointerId)
|
|
27379
|
+
return;
|
|
27380
|
+
const distance = Math.hypot(event.clientX - pointerDrag.startX, event.clientY - pointerDrag.startY);
|
|
27381
|
+
if (!pointerDrag.active) {
|
|
27382
|
+
if (distance < 5)
|
|
27383
|
+
return;
|
|
27384
|
+
pointerDrag.active = true;
|
|
27385
|
+
draggingTaskId = pointerDrag.taskId;
|
|
27386
|
+
taskCardById(pointerDrag.taskId)?.classList.add("dragging");
|
|
27387
|
+
}
|
|
27388
|
+
event.preventDefault();
|
|
27389
|
+
scheduleTaskDragAutoScroll(event);
|
|
27390
|
+
markPointerDropTarget(event);
|
|
27391
|
+
}
|
|
27392
|
+
function handlePointerTaskDragEnd(event) {
|
|
27393
|
+
const state = pointerDrag;
|
|
27394
|
+
if (state && event.pointerId !== state.pointerId)
|
|
27395
|
+
return;
|
|
27396
|
+
pointerDrag = null;
|
|
27397
|
+
stopPointerTaskDragListeners();
|
|
27398
|
+
state?.source.releasePointerCapture?.(event.pointerId);
|
|
27399
|
+
if (!state?.active)
|
|
27400
|
+
return;
|
|
27401
|
+
const target = taskDropTargetFromPoint(event);
|
|
27402
|
+
if (target)
|
|
27403
|
+
moveDraggedTask(target);
|
|
27404
|
+
else
|
|
27405
|
+
clearTaskDragState();
|
|
27406
|
+
}
|
|
27407
|
+
function handlePointerTaskDragCancel(event) {
|
|
27408
|
+
const state = pointerDrag;
|
|
27409
|
+
if (state && event.pointerId !== state.pointerId)
|
|
27410
|
+
return;
|
|
27411
|
+
pointerDrag = null;
|
|
27412
|
+
stopPointerTaskDragListeners();
|
|
27413
|
+
state?.source.releasePointerCapture?.(event.pointerId);
|
|
27414
|
+
if (nativeTaskDragActive)
|
|
27415
|
+
return;
|
|
27416
|
+
clearTaskDragState();
|
|
27417
|
+
}
|
|
27418
|
+
async function moveDraggedTask(target) {
|
|
27419
|
+
const id = draggingTaskId;
|
|
27420
|
+
clearTaskDragState();
|
|
27421
|
+
if (!id)
|
|
27422
|
+
return;
|
|
27423
|
+
try {
|
|
27424
|
+
await requestJournal("move-task", { id, ...target }, () => {
|
|
27425
|
+
selectedTaskId = id;
|
|
27426
|
+
});
|
|
27427
|
+
} catch (error2) {
|
|
27428
|
+
message = error2 instanceof Error ? error2.message : text2().moveTaskFailed;
|
|
27429
|
+
deps.setStatus("error");
|
|
27430
|
+
render();
|
|
27431
|
+
}
|
|
27432
|
+
}
|
|
27433
|
+
function renderQueuePreview() {
|
|
27434
|
+
const queue = document.createElement("section");
|
|
27435
|
+
queue.className = "journal-command-preview journal-queue";
|
|
27436
|
+
const title = document.createElement("strong");
|
|
27437
|
+
title.textContent = text2().aiQueue;
|
|
27438
|
+
const command = document.createElement("code");
|
|
27439
|
+
command.textContent = labelFilter ? `code-viewer journal task-next --label ${labelFilter} --limit 5` : "code-viewer journal task-next --limit 5";
|
|
27440
|
+
queue.append(title, command);
|
|
27441
|
+
const next = data ? selectNextJournalTasks(data.tasks, { labels: labelFilter ? [labelFilter] : [] }, 5) : [];
|
|
27442
|
+
if (next.length) {
|
|
27443
|
+
const list2 = document.createElement("ol");
|
|
27444
|
+
for (const task of next) {
|
|
27445
|
+
const item = document.createElement("li");
|
|
27446
|
+
item.textContent = `${text2().priorityLabels[task.priority]} ${task.title}`;
|
|
27447
|
+
list2.appendChild(item);
|
|
27448
|
+
}
|
|
27449
|
+
queue.appendChild(list2);
|
|
27450
|
+
}
|
|
27451
|
+
return queue;
|
|
27452
|
+
}
|
|
27453
|
+
function renderGithubIssueToggle() {
|
|
27454
|
+
const button = document.createElement("button");
|
|
27455
|
+
button.type = "button";
|
|
27456
|
+
button.className = "journal-github-toggle";
|
|
27457
|
+
button.classList.toggle("active", githubInboxOpen);
|
|
27458
|
+
button.setAttribute("aria-expanded", githubInboxOpen ? "true" : "false");
|
|
27459
|
+
button.innerHTML = iconSvg("journal-github-toggle-icon", COMMENT_DISCUSSION_16_PATH);
|
|
27460
|
+
const label = document.createElement("span");
|
|
27461
|
+
label.textContent = text2().githubIssues;
|
|
27462
|
+
button.appendChild(label);
|
|
27463
|
+
const count = document.createElement("span");
|
|
27464
|
+
count.className = "journal-github-toggle-count";
|
|
27465
|
+
count.textContent = githubIssuesLoading ? "..." : githubIssuesLoaded ? String(githubIssues.length) : "0";
|
|
27466
|
+
button.appendChild(count);
|
|
27467
|
+
button.addEventListener("click", () => {
|
|
27468
|
+
githubInboxOpen = !githubInboxOpen;
|
|
27469
|
+
render();
|
|
27470
|
+
if (githubInboxOpen && !githubIssuesAutoRequested && !githubIssuesLoading) {
|
|
27471
|
+
loadGithubIssues();
|
|
27472
|
+
}
|
|
27473
|
+
});
|
|
27474
|
+
return button;
|
|
27475
|
+
}
|
|
27476
|
+
function issueLabel(issueNumber) {
|
|
27477
|
+
return journalIssueLabel(issueNumber);
|
|
27478
|
+
}
|
|
27479
|
+
function linkedTaskForIssue(issueNumber) {
|
|
27480
|
+
const label = issueLabel(issueNumber);
|
|
27481
|
+
const repoLabel = journalIssueRepoLabel(githubRepoFilter || undefined);
|
|
27482
|
+
return data?.tasks.tasks.find((task) => {
|
|
27483
|
+
if (!task.labels.includes("github") || !task.labels.includes(label)) {
|
|
27484
|
+
return false;
|
|
27485
|
+
}
|
|
27486
|
+
if (repoLabel)
|
|
27487
|
+
return task.labels.includes(repoLabel);
|
|
27488
|
+
return !task.labels.some((taskLabel) => taskLabel.startsWith("repo-"));
|
|
27489
|
+
}) || null;
|
|
27490
|
+
}
|
|
27491
|
+
function githubIssueStateLabel(state) {
|
|
27492
|
+
if (state === "open" || state === "closed" || state === "all") {
|
|
27493
|
+
return text2().githubStateLabels[state];
|
|
27494
|
+
}
|
|
27495
|
+
return state;
|
|
27496
|
+
}
|
|
27497
|
+
function closeGithubInbox() {
|
|
27498
|
+
if (!githubInboxOpen)
|
|
27499
|
+
return;
|
|
27500
|
+
githubInboxOpen = false;
|
|
27501
|
+
render();
|
|
27502
|
+
}
|
|
27503
|
+
function handleGithubInboxKeydown(event) {
|
|
27504
|
+
if (event.key !== "Escape" || !githubInboxOpen)
|
|
27505
|
+
return;
|
|
27506
|
+
closeGithubInbox();
|
|
27507
|
+
}
|
|
27508
|
+
function handleGithubInboxOutsidePointerDown(event) {
|
|
27509
|
+
if (!githubInboxOpen)
|
|
27510
|
+
return;
|
|
27511
|
+
const target = event.target;
|
|
27512
|
+
const panel = root.querySelector(".journal-github-panel");
|
|
27513
|
+
const toggle = root.querySelector(".journal-github-toggle");
|
|
27514
|
+
if (target && (panel?.contains(target) || toggle?.contains(target)))
|
|
27515
|
+
return;
|
|
27516
|
+
closeGithubInbox();
|
|
27517
|
+
}
|
|
27518
|
+
function resetGithubIssueFilters(shouldRender = true) {
|
|
27519
|
+
githubIssueGeneration++;
|
|
27520
|
+
githubIssueLimit = GITHUB_ISSUE_INITIAL_LIMIT;
|
|
27521
|
+
githubIssues = [];
|
|
27522
|
+
githubIssuesLoaded = false;
|
|
27523
|
+
githubIssuesLoading = false;
|
|
27524
|
+
githubIssuesAutoRequested = false;
|
|
27525
|
+
githubIssuesError = githubRetrySeconds() > 0 ? text2().githubRateLimited(githubRetrySeconds()) : "";
|
|
27526
|
+
if (shouldRender)
|
|
27527
|
+
render();
|
|
27528
|
+
}
|
|
27529
|
+
function stableGithubIssues(issues) {
|
|
27530
|
+
const byNumber = new Map;
|
|
27531
|
+
for (const issue of issues)
|
|
27532
|
+
byNumber.set(issue.number, issue);
|
|
27533
|
+
return [...byNumber.values()].sort((a2, b2) => b2.number - a2.number);
|
|
27534
|
+
}
|
|
27535
|
+
function githubRetrySeconds() {
|
|
27536
|
+
return Math.max(0, Math.ceil((githubIssuesRetryAt - Date.now()) / 1000));
|
|
27537
|
+
}
|
|
27538
|
+
function githubCanRequest() {
|
|
27539
|
+
return !githubIssuesLoading && githubRetrySeconds() === 0;
|
|
27540
|
+
}
|
|
27541
|
+
async function loadGithubIssues(limit = githubIssueLimit) {
|
|
27542
|
+
if (!githubCanRequest()) {
|
|
27543
|
+
render();
|
|
27544
|
+
return;
|
|
27545
|
+
}
|
|
27546
|
+
const seq = ++githubIssueGeneration;
|
|
27547
|
+
githubIssuesAutoRequested = true;
|
|
27548
|
+
githubIssuesLoading = true;
|
|
27549
|
+
githubIssueLimit = Math.min(Math.max(GITHUB_ISSUE_INITIAL_LIMIT, limit), GITHUB_ISSUE_LIMIT_MAX);
|
|
27550
|
+
githubIssuesError = "";
|
|
27551
|
+
render();
|
|
27552
|
+
deps.setStatus("refreshing");
|
|
27553
|
+
try {
|
|
27554
|
+
const res = await deps.trackLoad(fetch("/_journal", {
|
|
27555
|
+
method: "POST",
|
|
27556
|
+
headers: {
|
|
27557
|
+
"Content-Type": "application/json",
|
|
27558
|
+
"X-Code-Viewer-Action": "1"
|
|
27559
|
+
},
|
|
27560
|
+
body: JSON.stringify({
|
|
27561
|
+
action: "list-github-issues",
|
|
27562
|
+
repo: githubRepoFilter || undefined,
|
|
27563
|
+
labels: parseGithubLabels(githubLabelFilter),
|
|
27564
|
+
search: githubSearchFilter || undefined,
|
|
27565
|
+
state: githubStateFilter,
|
|
27566
|
+
limit: githubIssueLimit
|
|
27567
|
+
})
|
|
27568
|
+
}));
|
|
27569
|
+
if (!res.ok)
|
|
27570
|
+
throw new Error(await res.text());
|
|
27571
|
+
const response = await res.json();
|
|
27572
|
+
if (seq !== githubIssueGeneration)
|
|
27573
|
+
return;
|
|
27574
|
+
githubIssues = stableGithubIssues(response.issues || []);
|
|
27575
|
+
githubIssuesLoaded = true;
|
|
27576
|
+
deps.setStatus("live");
|
|
27577
|
+
} catch (error2) {
|
|
27578
|
+
if (seq !== githubIssueGeneration)
|
|
27579
|
+
return;
|
|
27580
|
+
if (error2 instanceof Error && /rate limit|secondary rate limit/i.test(error2.message)) {
|
|
27581
|
+
githubIssuesRetryAt = Date.now() + GITHUB_ISSUE_RATE_LIMIT_RETRY_MS;
|
|
27582
|
+
githubIssuesError = text2().githubRateLimited(githubRetrySeconds());
|
|
27583
|
+
window.setTimeout(() => {
|
|
27584
|
+
if (githubRetrySeconds() === 0) {
|
|
27585
|
+
githubIssuesError = "";
|
|
27586
|
+
render();
|
|
27587
|
+
}
|
|
27588
|
+
}, GITHUB_ISSUE_RATE_LIMIT_RETRY_MS + 50);
|
|
27589
|
+
} else {
|
|
27590
|
+
githubIssuesError = error2 instanceof Error ? error2.message : text2().githubLoadFailed;
|
|
27591
|
+
}
|
|
27592
|
+
githubIssuesLoaded = false;
|
|
27593
|
+
deps.setStatus("error");
|
|
27594
|
+
} finally {
|
|
27595
|
+
if (seq === githubIssueGeneration) {
|
|
27596
|
+
githubIssuesLoading = false;
|
|
27597
|
+
render();
|
|
27598
|
+
}
|
|
27599
|
+
}
|
|
27600
|
+
}
|
|
27601
|
+
async function linkGithubIssueToTask(issue, target) {
|
|
27602
|
+
try {
|
|
27603
|
+
const response = await requestJournal("link-github-issue", {
|
|
27604
|
+
issue_number: issue.number,
|
|
27605
|
+
repo: githubRepoFilter || undefined,
|
|
27606
|
+
title: issue.title,
|
|
27607
|
+
url: issue.url,
|
|
27608
|
+
memo_label: text2().githubMemoLabel,
|
|
27609
|
+
status: target.status,
|
|
27610
|
+
before_id: target.before_id,
|
|
27611
|
+
after_id: target.after_id,
|
|
27612
|
+
priority: "p2",
|
|
27613
|
+
labels: normalizeJournalLabels([labelFilter])
|
|
27614
|
+
});
|
|
27615
|
+
if (response.task) {
|
|
27616
|
+
selectedTaskId = response.task.id;
|
|
27617
|
+
githubInboxOpen = false;
|
|
27618
|
+
render();
|
|
27619
|
+
return response.task.id;
|
|
27620
|
+
}
|
|
27621
|
+
} catch (error2) {
|
|
27622
|
+
message = error2 instanceof Error ? error2.message : text2().githubLinkTaskFailed;
|
|
27623
|
+
deps.setStatus("error");
|
|
27624
|
+
render();
|
|
27625
|
+
} finally {
|
|
27626
|
+
stopTaskDragAutoScroll();
|
|
27627
|
+
draggingGithubIssueNumber = null;
|
|
27628
|
+
}
|
|
27629
|
+
return null;
|
|
27630
|
+
}
|
|
27631
|
+
function draggingGithubIssue() {
|
|
27632
|
+
if (draggingGithubIssueNumber === null)
|
|
27633
|
+
return null;
|
|
27634
|
+
return githubIssues.find((issue) => issue.number === draggingGithubIssueNumber) || null;
|
|
27635
|
+
}
|
|
27636
|
+
function githubIssueFromDataTransfer(dataTransfer) {
|
|
27637
|
+
const raw = dataTransfer?.getData("text/plain") || "";
|
|
27638
|
+
const match2 = raw.trim().match(/^#?(\d+)$/);
|
|
27639
|
+
if (!match2)
|
|
27640
|
+
return null;
|
|
27641
|
+
const issueNumber = Number(match2[1]);
|
|
27642
|
+
if (!Number.isInteger(issueNumber))
|
|
27643
|
+
return null;
|
|
27644
|
+
return githubIssues.find((issue) => issue.number === issueNumber) || null;
|
|
27645
|
+
}
|
|
27646
|
+
function renderGithubIssueCard(issue) {
|
|
27647
|
+
const card = document.createElement("article");
|
|
27648
|
+
card.className = "journal-github-issue-card";
|
|
27649
|
+
card.dataset.issueNumber = String(issue.number);
|
|
27650
|
+
const linked = linkedTaskForIssue(issue.number);
|
|
27651
|
+
card.draggable = !linked;
|
|
27652
|
+
if (linked)
|
|
27653
|
+
card.classList.add("linked");
|
|
27654
|
+
if (!linked) {
|
|
27655
|
+
card.addEventListener("dragstart", (event) => {
|
|
27656
|
+
draggingGithubIssueNumber = issue.number;
|
|
27657
|
+
lastTaskDropTarget = null;
|
|
27658
|
+
event.dataTransfer?.setData("text/plain", `#${issue.number}`);
|
|
27659
|
+
if (event.dataTransfer)
|
|
27660
|
+
event.dataTransfer.effectAllowed = "copyMove";
|
|
27661
|
+
});
|
|
27662
|
+
card.addEventListener("dragend", (event) => finishNativeTaskDrag(event));
|
|
27663
|
+
}
|
|
27664
|
+
const head = document.createElement("div");
|
|
27665
|
+
head.className = "journal-github-issue-head";
|
|
27666
|
+
const number = document.createElement("span");
|
|
27667
|
+
number.textContent = `#${issue.number}`;
|
|
27668
|
+
const state = document.createElement("span");
|
|
27669
|
+
state.textContent = githubIssueStateLabel(issue.state);
|
|
27670
|
+
head.append(number, state);
|
|
27671
|
+
if (linked) {
|
|
27672
|
+
const linkedBadge = document.createElement("span");
|
|
27673
|
+
linkedBadge.className = "journal-github-linked";
|
|
27674
|
+
linkedBadge.textContent = text2().githubLinked;
|
|
27675
|
+
head.appendChild(linkedBadge);
|
|
27676
|
+
}
|
|
27677
|
+
const title = document.createElement("strong");
|
|
27678
|
+
title.textContent = issue.title;
|
|
27679
|
+
const labels = document.createElement("div");
|
|
27680
|
+
labels.className = "journal-github-issue-labels";
|
|
27681
|
+
for (const label of issue.labels.slice(0, 4)) {
|
|
27682
|
+
labels.appendChild(labelChip(label));
|
|
27683
|
+
}
|
|
27684
|
+
const action = document.createElement("button");
|
|
27685
|
+
action.type = "button";
|
|
27686
|
+
action.draggable = false;
|
|
27687
|
+
action.className = "journal-github-issue-action";
|
|
27688
|
+
action.textContent = linked ? text2().githubOpenTask : text2().githubAddToBoard;
|
|
27689
|
+
action.addEventListener("click", async (event) => {
|
|
27690
|
+
event.preventDefault();
|
|
27691
|
+
event.stopPropagation();
|
|
27692
|
+
if (linked) {
|
|
27693
|
+
githubInboxOpen = false;
|
|
27694
|
+
selectTask(linked.id, { focusEditor: true, revealEditor: true });
|
|
27695
|
+
return;
|
|
27696
|
+
}
|
|
27697
|
+
action.disabled = true;
|
|
27698
|
+
const taskId = await linkGithubIssueToTask(issue, { status: "draft" });
|
|
27699
|
+
if (taskId)
|
|
27700
|
+
selectTask(taskId, { focusEditor: true, revealEditor: true });
|
|
27701
|
+
});
|
|
27702
|
+
card.append(head, title, labels, action);
|
|
27703
|
+
return card;
|
|
27704
|
+
}
|
|
27705
|
+
function renderGithubIssuesPanel() {
|
|
27706
|
+
const panel = document.createElement("section");
|
|
27707
|
+
panel.className = "journal-github-panel";
|
|
27708
|
+
const title = document.createElement("strong");
|
|
27709
|
+
title.textContent = text2().githubIssues;
|
|
27710
|
+
const controls = document.createElement("div");
|
|
27711
|
+
controls.className = "journal-github-controls";
|
|
27712
|
+
const repo = document.createElement("input");
|
|
27713
|
+
repo.type = "text";
|
|
27714
|
+
repo.placeholder = text2().githubRepoPlaceholder;
|
|
27715
|
+
repo.value = githubRepoFilter;
|
|
27716
|
+
const ghLabel = document.createElement("input");
|
|
27717
|
+
ghLabel.type = "text";
|
|
27718
|
+
ghLabel.placeholder = text2().githubLabelPlaceholder;
|
|
27719
|
+
ghLabel.value = githubLabelFilter;
|
|
27720
|
+
const search = document.createElement("input");
|
|
27721
|
+
search.type = "search";
|
|
27722
|
+
search.placeholder = text2().githubSearchPlaceholder;
|
|
27723
|
+
search.value = githubSearchFilter;
|
|
27724
|
+
const state = document.createElement("select");
|
|
27725
|
+
for (const value of ["open", "closed", "all"]) {
|
|
27726
|
+
const option = document.createElement("option");
|
|
27727
|
+
option.value = value;
|
|
27728
|
+
option.textContent = text2().githubStateLabels[value];
|
|
27729
|
+
option.selected = value === githubStateFilter;
|
|
27730
|
+
state.appendChild(option);
|
|
27731
|
+
}
|
|
27732
|
+
const syncControls = (shouldRender = true, shouldLoad = shouldRender) => {
|
|
27733
|
+
const nextState = state.value === "closed" || state.value === "all" ? state.value : "open";
|
|
27734
|
+
const nextRepo = repo.value.trim();
|
|
27735
|
+
const nextLabel = ghLabel.value.trim();
|
|
27736
|
+
const nextSearch = search.value.trim();
|
|
27737
|
+
const changed = nextRepo !== githubRepoFilter || nextLabel !== githubLabelFilter || nextSearch !== githubSearchFilter || nextState !== githubStateFilter;
|
|
27738
|
+
githubRepoFilter = nextRepo;
|
|
27739
|
+
githubLabelFilter = nextLabel;
|
|
27740
|
+
githubSearchFilter = nextSearch;
|
|
27741
|
+
githubStateFilter = nextState;
|
|
27742
|
+
if (!changed)
|
|
27743
|
+
return;
|
|
27744
|
+
resetGithubIssueFilters(shouldRender);
|
|
27745
|
+
if (shouldLoad && githubInboxOpen)
|
|
27746
|
+
loadGithubIssues();
|
|
27747
|
+
};
|
|
27748
|
+
const loadOnEnter = (event) => {
|
|
27749
|
+
if (event.key !== "Enter")
|
|
27750
|
+
return;
|
|
27751
|
+
event.preventDefault();
|
|
27752
|
+
syncControls(false, false);
|
|
27753
|
+
loadGithubIssues();
|
|
27754
|
+
};
|
|
27755
|
+
repo.addEventListener("change", () => syncControls());
|
|
27756
|
+
repo.addEventListener("keydown", loadOnEnter);
|
|
27757
|
+
ghLabel.addEventListener("change", () => syncControls());
|
|
27758
|
+
ghLabel.addEventListener("keydown", loadOnEnter);
|
|
27759
|
+
search.addEventListener("change", () => syncControls());
|
|
27760
|
+
search.addEventListener("keydown", loadOnEnter);
|
|
27761
|
+
state.addEventListener("change", () => syncControls());
|
|
27762
|
+
const load = document.createElement("button");
|
|
27763
|
+
load.type = "button";
|
|
27764
|
+
load.textContent = githubIssuesLoading ? text2().githubLoading : text2().githubLoad;
|
|
27765
|
+
load.disabled = !githubCanRequest();
|
|
27766
|
+
load.addEventListener("click", () => {
|
|
27767
|
+
syncControls(false, false);
|
|
27768
|
+
loadGithubIssues();
|
|
27769
|
+
});
|
|
27770
|
+
controls.append(repo, ghLabel, search, state, load);
|
|
27771
|
+
const hint = document.createElement("p");
|
|
27772
|
+
hint.className = "journal-github-hint";
|
|
27773
|
+
const showing = githubIssuesLoaded && !githubIssuesError ? ` ${text2().githubShowing(githubIssues.length, githubIssueLimit)}` : "";
|
|
27774
|
+
hint.textContent = githubIssuesError || `${text2().githubDragHint}${showing}`;
|
|
27775
|
+
const list2 = document.createElement("div");
|
|
27776
|
+
list2.className = "journal-github-issues";
|
|
27777
|
+
list2.addEventListener("dragover", (event) => {
|
|
27778
|
+
if (!draggingGithubIssue())
|
|
27779
|
+
return;
|
|
27780
|
+
event.preventDefault();
|
|
27781
|
+
if (event.dataTransfer)
|
|
27782
|
+
event.dataTransfer.dropEffect = "copy";
|
|
27783
|
+
scheduleTaskDragAutoScroll(event);
|
|
27784
|
+
});
|
|
27785
|
+
if (githubIssues.length) {
|
|
27786
|
+
for (const issue of githubIssues) {
|
|
27787
|
+
list2.appendChild(renderGithubIssueCard(issue));
|
|
27788
|
+
}
|
|
27789
|
+
} else {
|
|
27790
|
+
const empty = document.createElement("div");
|
|
27791
|
+
empty.className = "journal-empty";
|
|
27792
|
+
empty.textContent = githubIssuesLoading ? text2().githubLoading : githubIssuesLoaded ? text2().githubNoIssues : text2().githubNotLoaded;
|
|
27793
|
+
list2.appendChild(empty);
|
|
27794
|
+
}
|
|
27795
|
+
const head = document.createElement("div");
|
|
27796
|
+
head.className = "journal-github-panel-head";
|
|
27797
|
+
const close = document.createElement("button");
|
|
27798
|
+
close.type = "button";
|
|
27799
|
+
close.className = "journal-github-close";
|
|
27800
|
+
close.title = text2().githubClose;
|
|
27801
|
+
close.setAttribute("aria-label", text2().githubClose);
|
|
27802
|
+
close.innerHTML = iconSvg("journal-github-close-icon", X_16_PATH);
|
|
27803
|
+
close.addEventListener("click", () => {
|
|
27804
|
+
closeGithubInbox();
|
|
27805
|
+
});
|
|
27806
|
+
head.append(title, close);
|
|
27807
|
+
panel.append(head, controls, hint, list2);
|
|
27808
|
+
if (githubIssuesLoaded && githubIssues.length >= githubIssueLimit && githubIssueLimit < GITHUB_ISSUE_LIMIT_MAX) {
|
|
27809
|
+
const more = document.createElement("button");
|
|
27810
|
+
more.type = "button";
|
|
27811
|
+
more.className = "journal-github-load-more";
|
|
27812
|
+
more.textContent = text2().githubLoadMore;
|
|
27813
|
+
more.disabled = !githubCanRequest();
|
|
27814
|
+
more.addEventListener("click", () => loadGithubIssues(githubIssueLimit + GITHUB_ISSUE_LIMIT_STEP));
|
|
27815
|
+
panel.appendChild(more);
|
|
27816
|
+
}
|
|
27817
|
+
return panel;
|
|
27818
|
+
}
|
|
27819
|
+
function taskLabelCounts() {
|
|
27820
|
+
const counts = new Map;
|
|
27821
|
+
for (const task of data?.tasks.tasks || []) {
|
|
27822
|
+
for (const label of task.labels) {
|
|
27823
|
+
counts.set(label, (counts.get(label) || 0) + 1);
|
|
27824
|
+
}
|
|
27825
|
+
}
|
|
27826
|
+
return counts;
|
|
27827
|
+
}
|
|
27828
|
+
function renderLabelFilterButton(label, count, active, value = label) {
|
|
27829
|
+
const button = document.createElement("button");
|
|
27830
|
+
button.type = "button";
|
|
27831
|
+
button.className = "journal-label-filter";
|
|
27832
|
+
button.classList.toggle("active", active);
|
|
27833
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
27834
|
+
const name = document.createElement("span");
|
|
27835
|
+
name.className = "journal-label-filter-name";
|
|
27836
|
+
name.textContent = label;
|
|
27837
|
+
const badge = document.createElement("span");
|
|
27838
|
+
badge.className = "journal-label-filter-count";
|
|
27839
|
+
badge.textContent = String(count);
|
|
27840
|
+
button.append(name, badge);
|
|
27841
|
+
button.addEventListener("click", () => {
|
|
27842
|
+
labelFilter = active ? "" : value;
|
|
27843
|
+
setRoute({ tab: "tasks", label: labelFilter || undefined });
|
|
27844
|
+
});
|
|
27845
|
+
return button;
|
|
27846
|
+
}
|
|
27847
|
+
function renderLabelFilters() {
|
|
27848
|
+
const wrap = document.createElement("div");
|
|
27849
|
+
wrap.className = "journal-label-filters";
|
|
27850
|
+
wrap.setAttribute("aria-label", text2().labelFilters);
|
|
27851
|
+
const counts = taskLabelCounts();
|
|
27852
|
+
const total = data?.tasks.tasks.length || 0;
|
|
27853
|
+
wrap.appendChild(renderLabelFilterButton(text2().allLabels, total, !labelFilter, ""));
|
|
27854
|
+
for (const [label, count] of [...counts.entries()].sort((a2, b2) => a2[0].localeCompare(b2[0]))) {
|
|
27855
|
+
wrap.appendChild(renderLabelFilterButton(label, count, label === labelFilter));
|
|
27856
|
+
}
|
|
27857
|
+
return wrap;
|
|
27858
|
+
}
|
|
27859
|
+
function renderTaskBoard() {
|
|
27860
|
+
const board = document.createElement("div");
|
|
27861
|
+
board.className = "journal-task-board";
|
|
27862
|
+
for (const status of JOURNAL_TASK_STATUSES) {
|
|
27863
|
+
const column = document.createElement("section");
|
|
27864
|
+
column.className = `journal-task-column journal-task-column-${status}`;
|
|
27865
|
+
column.dataset.status = status;
|
|
27866
|
+
column.addEventListener("dragover", (event) => {
|
|
27867
|
+
const issue = draggingGithubIssue() || githubIssueFromDataTransfer(event.dataTransfer);
|
|
27868
|
+
if (!draggingTaskId && !issue)
|
|
27869
|
+
return;
|
|
27870
|
+
const targetCard = event.target?.closest(".journal-task-card");
|
|
27871
|
+
if (targetCard && column.contains(targetCard))
|
|
27872
|
+
return;
|
|
27873
|
+
event.preventDefault();
|
|
27874
|
+
if (event.dataTransfer)
|
|
27875
|
+
event.dataTransfer.dropEffect = issue ? "copy" : "move";
|
|
27876
|
+
scheduleTaskDragAutoScroll(event);
|
|
27877
|
+
lastTaskDropTarget = { status };
|
|
27878
|
+
markColumnDropTarget(column);
|
|
27879
|
+
});
|
|
27880
|
+
column.addEventListener("dragleave", (event) => {
|
|
27881
|
+
const related = event.relatedTarget;
|
|
27882
|
+
if (related && column.contains(related))
|
|
27883
|
+
return;
|
|
27884
|
+
lastTaskDropTarget = null;
|
|
27885
|
+
column.classList.remove("drop-target");
|
|
27886
|
+
});
|
|
27887
|
+
column.addEventListener("drop", (event) => {
|
|
27888
|
+
const issue = draggingGithubIssue();
|
|
27889
|
+
if (!draggingTaskId && !issue)
|
|
27890
|
+
return;
|
|
27891
|
+
const targetCard = event.target?.closest(".journal-task-card");
|
|
27892
|
+
if (targetCard && column.contains(targetCard))
|
|
27893
|
+
return;
|
|
27894
|
+
event.preventDefault();
|
|
27895
|
+
lastTaskDropTarget = null;
|
|
27896
|
+
if (issue)
|
|
27897
|
+
linkGithubIssueToTask(issue, { status });
|
|
27898
|
+
else
|
|
27899
|
+
moveDraggedTask({ status });
|
|
27900
|
+
});
|
|
27901
|
+
const head = document.createElement("div");
|
|
27902
|
+
head.className = "journal-task-column-head";
|
|
27903
|
+
const title = document.createElement("strong");
|
|
27904
|
+
title.textContent = text2().statusLabels[status];
|
|
27905
|
+
const count = document.createElement("span");
|
|
27906
|
+
const tasks = (data?.tasks.tasks || []).filter((task) => task.status === status && taskVisible(task));
|
|
27907
|
+
count.textContent = String(tasks.length);
|
|
27908
|
+
head.append(title, count);
|
|
27909
|
+
column.appendChild(head);
|
|
27910
|
+
if (!tasks.length) {
|
|
27911
|
+
const empty = document.createElement("div");
|
|
27912
|
+
empty.className = "journal-column-empty";
|
|
27913
|
+
empty.textContent = text2().empty;
|
|
27914
|
+
column.appendChild(empty);
|
|
27915
|
+
}
|
|
27916
|
+
for (const task of tasks)
|
|
27917
|
+
column.appendChild(renderTaskCard(task));
|
|
27918
|
+
board.appendChild(column);
|
|
27919
|
+
}
|
|
27920
|
+
return board;
|
|
27921
|
+
}
|
|
27922
|
+
function insertMarkdown(textarea, before, after = "") {
|
|
27923
|
+
const start = textarea.selectionStart;
|
|
27924
|
+
const end = textarea.selectionEnd;
|
|
27925
|
+
const selected = textarea.value.slice(start, end);
|
|
27926
|
+
textarea.value = textarea.value.slice(0, start) + before + selected + after + textarea.value.slice(end);
|
|
27927
|
+
textarea.focus();
|
|
27928
|
+
textarea.selectionStart = start + before.length;
|
|
27929
|
+
textarea.selectionEnd = end + before.length;
|
|
27930
|
+
}
|
|
27931
|
+
async function renderTaskPreview(host, body) {
|
|
27932
|
+
host.replaceChildren();
|
|
27933
|
+
const rendered = await renderMarkdownPreview(body || `_${text2().noBody}_`, { path: "journal-task.md", ref: "worktree" }, { syntaxHighlight: false });
|
|
27934
|
+
host.appendChild(rendered);
|
|
27935
|
+
}
|
|
27936
|
+
function renderTaskEditor() {
|
|
27937
|
+
const task = selectedTask();
|
|
27938
|
+
const panel = document.createElement("aside");
|
|
27939
|
+
panel.className = "journal-task-editor";
|
|
27940
|
+
const resizer = document.createElement("div");
|
|
27941
|
+
resizer.className = "journal-task-editor-resizer";
|
|
27942
|
+
resizer.role = "separator";
|
|
27943
|
+
resizer.tabIndex = 0;
|
|
27944
|
+
resizer.setAttribute("aria-orientation", "vertical");
|
|
27945
|
+
resizer.setAttribute("aria-label", text2().resizeTaskPanel);
|
|
27946
|
+
resizer.addEventListener("pointerdown", (event) => startTaskEditorResize(event, panel));
|
|
27947
|
+
const heading2 = document.createElement("div");
|
|
27948
|
+
heading2.className = "journal-section-head";
|
|
27949
|
+
const titleText = document.createElement("strong");
|
|
27950
|
+
titleText.textContent = task ? text2().taskHeading : text2().newTaskHeading;
|
|
27951
|
+
const newTask = document.createElement("button");
|
|
27952
|
+
newTask.type = "button";
|
|
27953
|
+
newTask.innerHTML = `${iconSvg("octicon-plus", PLUS_16_PATH)}<span>${text2().new}</span>`;
|
|
27954
|
+
newTask.addEventListener("click", () => {
|
|
27955
|
+
selectedTaskId = "";
|
|
27956
|
+
setRoute({ tab: "tasks", task: undefined });
|
|
27957
|
+
});
|
|
27958
|
+
heading2.append(titleText, newTask);
|
|
27959
|
+
const title = document.createElement("input");
|
|
27960
|
+
title.className = "journal-title-input";
|
|
27961
|
+
title.placeholder = text2().titlePlaceholder;
|
|
27962
|
+
title.value = task?.title || "";
|
|
27963
|
+
const row = document.createElement("div");
|
|
27964
|
+
row.className = "journal-task-fields";
|
|
27965
|
+
const status = document.createElement("select");
|
|
27966
|
+
for (const value of JOURNAL_TASK_STATUSES) {
|
|
27967
|
+
const option = document.createElement("option");
|
|
27968
|
+
option.value = value;
|
|
27969
|
+
option.textContent = text2().statusLabels[value];
|
|
27970
|
+
option.selected = (task?.status || "todo") === value;
|
|
27971
|
+
status.appendChild(option);
|
|
27972
|
+
}
|
|
27973
|
+
const priority = document.createElement("select");
|
|
27974
|
+
for (const value of JOURNAL_TASK_PRIORITIES) {
|
|
27975
|
+
const option = document.createElement("option");
|
|
27976
|
+
option.value = value;
|
|
27977
|
+
option.textContent = text2().priorityLabels[value];
|
|
27978
|
+
option.selected = (task?.priority || "p2") === value;
|
|
27979
|
+
priority.appendChild(option);
|
|
27980
|
+
}
|
|
27981
|
+
const due = document.createElement("input");
|
|
27982
|
+
due.type = "date";
|
|
27983
|
+
due.title = text2().endDate;
|
|
27984
|
+
due.setAttribute("aria-label", text2().endDate);
|
|
27985
|
+
due.value = task?.due_date || "";
|
|
27986
|
+
const sourceDate = document.createElement("input");
|
|
27987
|
+
sourceDate.type = "date";
|
|
27988
|
+
sourceDate.title = text2().startDate;
|
|
27989
|
+
sourceDate.setAttribute("aria-label", text2().startDate);
|
|
27990
|
+
sourceDate.value = task?.source_date || selectedDate;
|
|
27991
|
+
row.append(taskField(text2().statusField, status), taskField(text2().priorityField, priority), taskField(text2().startDate, sourceDate), taskField(text2().endDate, due));
|
|
27992
|
+
const labels = createLabelEditor(task ? task.labels : normalizeJournalLabels([labelFilter]), text2().labelPlaceholder, labelSuggestions(), text2().removeLabel);
|
|
27993
|
+
const toolbar = document.createElement("div");
|
|
27994
|
+
toolbar.className = "journal-markdown-toolbar";
|
|
27995
|
+
const body = document.createElement("textarea");
|
|
27996
|
+
body.className = "journal-body-input journal-task-body-input";
|
|
27997
|
+
body.placeholder = text2().taskBodyPlaceholder;
|
|
27998
|
+
body.value = task?.body || "";
|
|
27999
|
+
const preview = document.createElement("div");
|
|
28000
|
+
preview.className = "journal-markdown-preview";
|
|
28001
|
+
const syncMode = () => {
|
|
28002
|
+
body.hidden = editorMode === "preview";
|
|
28003
|
+
preview.hidden = editorMode === "write";
|
|
28004
|
+
renderTaskPreview(preview, body.value);
|
|
28005
|
+
};
|
|
28006
|
+
for (const spec of [
|
|
28007
|
+
["B", "**", "**"],
|
|
28008
|
+
["`", "`", "`"],
|
|
28009
|
+
["[]", "- [ ] ", ""],
|
|
28010
|
+
[">", "> ", ""]
|
|
28011
|
+
]) {
|
|
28012
|
+
const button = document.createElement("button");
|
|
28013
|
+
button.type = "button";
|
|
28014
|
+
button.textContent = spec[0];
|
|
28015
|
+
button.addEventListener("click", () => insertMarkdown(body, spec[1], spec[2]));
|
|
28016
|
+
toolbar.appendChild(button);
|
|
28017
|
+
}
|
|
28018
|
+
const modes = document.createElement("div");
|
|
28019
|
+
modes.className = "seg journal-editor-mode";
|
|
28020
|
+
for (const mode of ["write", "preview", "split"]) {
|
|
28021
|
+
const button = document.createElement("button");
|
|
28022
|
+
button.type = "button";
|
|
28023
|
+
button.classList.toggle("active", editorMode === mode);
|
|
28024
|
+
button.textContent = text2().editorModes[mode];
|
|
28025
|
+
button.addEventListener("click", () => {
|
|
28026
|
+
editorMode = mode;
|
|
28027
|
+
render();
|
|
28028
|
+
});
|
|
28029
|
+
modes.appendChild(button);
|
|
28030
|
+
}
|
|
28031
|
+
toolbar.appendChild(modes);
|
|
28032
|
+
body.addEventListener("input", () => {
|
|
28033
|
+
if (editorMode !== "write")
|
|
28034
|
+
renderTaskPreview(preview, body.value);
|
|
28035
|
+
});
|
|
28036
|
+
syncMode();
|
|
28037
|
+
const actions = document.createElement("div");
|
|
28038
|
+
actions.className = "journal-editor-actions";
|
|
28039
|
+
const save = document.createElement("button");
|
|
28040
|
+
save.type = "button";
|
|
28041
|
+
save.className = "journal-primary-action";
|
|
28042
|
+
save.textContent = task ? text2().saveTask : text2().addTask;
|
|
28043
|
+
save.addEventListener("click", async () => {
|
|
28044
|
+
setButtonBusy(save, true);
|
|
28045
|
+
const payload = {
|
|
28046
|
+
title: title.value,
|
|
28047
|
+
status: status.value,
|
|
28048
|
+
priority: priority.value,
|
|
28049
|
+
labels: labels.getLabels(),
|
|
28050
|
+
due_date: due.value || null,
|
|
28051
|
+
source_date: sourceDate.value || null,
|
|
28052
|
+
body: body.value
|
|
28053
|
+
};
|
|
28054
|
+
try {
|
|
28055
|
+
if (task)
|
|
28056
|
+
await requestJournal("update-task", { id: task.id, ...payload });
|
|
28057
|
+
else {
|
|
28058
|
+
const response = await requestJournal("add-task", payload, (next) => {
|
|
28059
|
+
if (next.task)
|
|
28060
|
+
selectedTaskId = next.task.id;
|
|
28061
|
+
});
|
|
28062
|
+
if (response.task)
|
|
28063
|
+
setRoute({ tab: "tasks", task: response.task.id });
|
|
28064
|
+
}
|
|
28065
|
+
} catch (error2) {
|
|
28066
|
+
message = error2 instanceof Error ? error2.message : text2().saveTaskFailed;
|
|
28067
|
+
deps.setStatus("error");
|
|
28068
|
+
render();
|
|
28069
|
+
} finally {
|
|
28070
|
+
setButtonBusy(save, false);
|
|
28071
|
+
}
|
|
28072
|
+
});
|
|
28073
|
+
actions.appendChild(save);
|
|
28074
|
+
if (task) {
|
|
28075
|
+
const claim = document.createElement("button");
|
|
28076
|
+
claim.type = "button";
|
|
28077
|
+
claim.textContent = text2().claim;
|
|
28078
|
+
claim.addEventListener("click", async () => {
|
|
28079
|
+
setButtonBusy(claim, true);
|
|
28080
|
+
try {
|
|
28081
|
+
await requestJournal("claim-task", { id: task.id, by: "user" });
|
|
28082
|
+
} catch (error2) {
|
|
28083
|
+
message = error2 instanceof Error ? error2.message : text2().claimTaskFailed;
|
|
28084
|
+
deps.setStatus("error");
|
|
28085
|
+
render();
|
|
28086
|
+
} finally {
|
|
28087
|
+
setButtonBusy(claim, false);
|
|
28088
|
+
}
|
|
28089
|
+
});
|
|
28090
|
+
const done = document.createElement("button");
|
|
28091
|
+
done.type = "button";
|
|
28092
|
+
done.textContent = text2().done;
|
|
28093
|
+
done.disabled = task.status !== "doing" || task.claim?.by !== "user";
|
|
28094
|
+
done.addEventListener("click", async () => {
|
|
28095
|
+
setButtonBusy(done, true);
|
|
28096
|
+
try {
|
|
28097
|
+
await requestJournal("complete-task", {
|
|
28098
|
+
id: task.id,
|
|
28099
|
+
by: "user",
|
|
28100
|
+
source: "user"
|
|
28101
|
+
});
|
|
28102
|
+
} catch (error2) {
|
|
28103
|
+
message = error2 instanceof Error ? error2.message : text2().doneTaskFailed;
|
|
28104
|
+
deps.setStatus("error");
|
|
28105
|
+
render();
|
|
28106
|
+
} finally {
|
|
28107
|
+
setButtonBusy(done, false);
|
|
28108
|
+
}
|
|
28109
|
+
});
|
|
28110
|
+
const del = document.createElement("button");
|
|
28111
|
+
del.type = "button";
|
|
28112
|
+
del.className = "journal-danger-action";
|
|
28113
|
+
del.innerHTML = `${iconSvg("octicon-trash", TRASH_16_PATH)}<span>${text2().delete}</span>`;
|
|
28114
|
+
del.addEventListener("click", async () => {
|
|
28115
|
+
const ok = await showConfirmDialog({
|
|
28116
|
+
title: `${text2().delete}?`,
|
|
28117
|
+
body: task.title,
|
|
28118
|
+
confirmLabel: text2().delete,
|
|
28119
|
+
danger: true,
|
|
28120
|
+
focusReturnTarget: del
|
|
28121
|
+
});
|
|
28122
|
+
if (!ok)
|
|
28123
|
+
return;
|
|
28124
|
+
setButtonBusy(del, true);
|
|
28125
|
+
try {
|
|
28126
|
+
const result = await requestJournal("delete-task", { id: task.id });
|
|
28127
|
+
if (!result.removed)
|
|
28128
|
+
throw new Error(text2().deleteTaskFailed);
|
|
28129
|
+
selectedTaskId = "";
|
|
28130
|
+
setRoute({ tab: "tasks", task: undefined });
|
|
28131
|
+
} catch (error2) {
|
|
28132
|
+
message = error2 instanceof Error ? error2.message : text2().deleteTaskFailed;
|
|
28133
|
+
deps.setStatus("error");
|
|
28134
|
+
render();
|
|
28135
|
+
} finally {
|
|
28136
|
+
setButtonBusy(del, false);
|
|
28137
|
+
}
|
|
28138
|
+
});
|
|
28139
|
+
actions.append(claim, done, del);
|
|
28140
|
+
}
|
|
28141
|
+
panel.append(resizer, heading2, title, row, labels.element, toolbar, body, preview, actions);
|
|
28142
|
+
return panel;
|
|
28143
|
+
}
|
|
28144
|
+
function renderTasksTab() {
|
|
28145
|
+
const wrap = document.createElement("div");
|
|
28146
|
+
wrap.className = "journal-tasks-layout";
|
|
28147
|
+
const toolbar = document.createElement("div");
|
|
28148
|
+
toolbar.className = "journal-task-toolbar";
|
|
28149
|
+
const labelInput = document.createElement("input");
|
|
28150
|
+
labelInput.type = "search";
|
|
28151
|
+
labelInput.placeholder = text2().labelFilterPlaceholder;
|
|
28152
|
+
labelInput.value = labelFilter;
|
|
28153
|
+
labelInput.addEventListener("change", () => {
|
|
28154
|
+
labelFilter = normalizeJournalLabel(labelInput.value) || "";
|
|
28155
|
+
setRoute({ tab: "tasks", label: labelFilter || undefined });
|
|
28156
|
+
});
|
|
28157
|
+
toolbar.append(labelInput, renderQueuePreview(), renderGithubIssueToggle(), renderLabelFilters());
|
|
28158
|
+
if (githubInboxOpen)
|
|
28159
|
+
toolbar.appendChild(renderGithubIssuesPanel());
|
|
28160
|
+
const boardWrap = document.createElement("div");
|
|
28161
|
+
boardWrap.className = "journal-task-board-wrap";
|
|
28162
|
+
boardWrap.appendChild(renderTaskBoard());
|
|
28163
|
+
wrap.append(toolbar, boardWrap, renderTaskEditor());
|
|
28164
|
+
return wrap;
|
|
28165
|
+
}
|
|
28166
|
+
function render() {
|
|
28167
|
+
root.setAttribute("aria-label", text2().ariaLabel);
|
|
28168
|
+
root.replaceChildren(renderHeader());
|
|
28169
|
+
const content = document.createElement("div");
|
|
28170
|
+
content.className = "journal-content";
|
|
28171
|
+
content.appendChild(activeTab === "journal" ? renderJournalTab() : renderTasksTab());
|
|
28172
|
+
root.appendChild(content);
|
|
28173
|
+
if (pendingTaskEditorFocus || pendingTaskEditorReveal)
|
|
28174
|
+
scheduleTaskEditorInteraction();
|
|
28175
|
+
}
|
|
28176
|
+
async function enter() {
|
|
28177
|
+
applyRoute();
|
|
28178
|
+
mount();
|
|
28179
|
+
render();
|
|
28180
|
+
await refresh();
|
|
28181
|
+
}
|
|
28182
|
+
function handleSse() {
|
|
28183
|
+
if (!mounted)
|
|
28184
|
+
return;
|
|
28185
|
+
if (draggingTaskId || draggingGithubIssueNumber !== null || pointerDrag || journalEditorHasFocus())
|
|
28186
|
+
return;
|
|
28187
|
+
refresh();
|
|
28188
|
+
}
|
|
28189
|
+
function localize() {
|
|
28190
|
+
if (mounted)
|
|
28191
|
+
render();
|
|
28192
|
+
}
|
|
28193
|
+
restoreTaskEditorWidth();
|
|
28194
|
+
document.addEventListener("keydown", handleGithubInboxKeydown);
|
|
28195
|
+
document.addEventListener("pointerdown", handleGithubInboxOutsidePointerDown);
|
|
28196
|
+
window.addEventListener("pointermove", handleTaskEditorResizeMove);
|
|
28197
|
+
window.addEventListener("pointerup", finishTaskEditorResize);
|
|
28198
|
+
window.addEventListener("pointercancel", finishTaskEditorResize);
|
|
28199
|
+
window.addEventListener("resize", () => {
|
|
28200
|
+
const layout = root.querySelector(".journal-tasks-layout");
|
|
28201
|
+
if (layout && layout.clientWidth <= 1100)
|
|
28202
|
+
return;
|
|
28203
|
+
const panel = root.querySelector(".journal-task-editor");
|
|
28204
|
+
if (panel)
|
|
28205
|
+
applyTaskEditorWidth(panel.getBoundingClientRect().width, false);
|
|
28206
|
+
});
|
|
28207
|
+
return { enter, suspend, handleSse, localize };
|
|
28208
|
+
}
|
|
28209
|
+
|
|
26172
28210
|
// web-src/core/html-escape.ts
|
|
26173
28211
|
function escapeHtml2(value) {
|
|
26174
28212
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -27692,7 +29730,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
27692
29730
|
} else {
|
|
27693
29731
|
renderFlat(files, ul, onFileClick);
|
|
27694
29732
|
}
|
|
27695
|
-
$("#totals").textContent = files.length ? `${files.length} file${files.length === 1 ? "" : "s"}` : "";
|
|
29733
|
+
$("#totals").textContent = !repoSidebar && files.length ? `${files.length} file${files.length === 1 ? "" : "s"}` : "";
|
|
27696
29734
|
const effectiveView = treeMode ? "tree" : STATE.sbView;
|
|
27697
29735
|
$$(".sb-view-seg button").forEach((b2) => {
|
|
27698
29736
|
b2.classList.toggle("active", b2.dataset.view === effectiveView);
|
|
@@ -32276,6 +34314,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32276
34314
|
repo: "Repository",
|
|
32277
34315
|
diff: "Diff Viewer",
|
|
32278
34316
|
history: "History",
|
|
34317
|
+
journal: "Work Log",
|
|
32279
34318
|
database: "Datastores",
|
|
32280
34319
|
help: "Help"
|
|
32281
34320
|
},
|
|
@@ -32413,6 +34452,103 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32413
34452
|
filterTitle: "Filter commits by message, SHA, author:name, or path:file.",
|
|
32414
34453
|
refreshTitle: "Refresh commit history"
|
|
32415
34454
|
},
|
|
34455
|
+
journal: {
|
|
34456
|
+
locale: "en",
|
|
34457
|
+
ariaLabel: "Work log and tasks",
|
|
34458
|
+
title: "Work Log",
|
|
34459
|
+
tabs: {
|
|
34460
|
+
journal: "Log",
|
|
34461
|
+
tasks: "Tasks"
|
|
34462
|
+
},
|
|
34463
|
+
refresh: "Refresh work log",
|
|
34464
|
+
loading: "loading...",
|
|
34465
|
+
loadFailed: "failed to load work log",
|
|
34466
|
+
statusLabels: {
|
|
34467
|
+
draft: "Draft",
|
|
34468
|
+
todo: "Todo",
|
|
34469
|
+
doing: "Doing",
|
|
34470
|
+
blocked: "Blocked",
|
|
34471
|
+
done: "Done"
|
|
34472
|
+
},
|
|
34473
|
+
priorityLabels: {
|
|
34474
|
+
p0: "P0",
|
|
34475
|
+
p1: "P1",
|
|
34476
|
+
p2: "P2",
|
|
34477
|
+
p3: "P3"
|
|
34478
|
+
},
|
|
34479
|
+
statusField: "Status",
|
|
34480
|
+
priorityField: "Priority",
|
|
34481
|
+
previousMonth: "Previous month",
|
|
34482
|
+
nextMonth: "Next month",
|
|
34483
|
+
weekDays: ["S", "M", "T", "W", "T", "F", "S"],
|
|
34484
|
+
noEntries: "No logs",
|
|
34485
|
+
noRelatedTasks: "No related tasks",
|
|
34486
|
+
noBody: "No body",
|
|
34487
|
+
relatedTasks: "Tasks",
|
|
34488
|
+
new: "New",
|
|
34489
|
+
titlePlaceholder: "Title",
|
|
34490
|
+
labelPlaceholder: "labels",
|
|
34491
|
+
entryBodyPlaceholder: "What did you work on today?",
|
|
34492
|
+
addEntry: "Add log",
|
|
34493
|
+
saveEntry: "Save log",
|
|
34494
|
+
delete: "Delete",
|
|
34495
|
+
deleteEntryFailed: "failed to delete log",
|
|
34496
|
+
saveEntryFailed: "failed to save log",
|
|
34497
|
+
moveTaskFailed: "failed to move task",
|
|
34498
|
+
aiQueue: "AI queue",
|
|
34499
|
+
empty: "Empty",
|
|
34500
|
+
duePrefix: "due",
|
|
34501
|
+
startDate: "Start date",
|
|
34502
|
+
endDate: "End date",
|
|
34503
|
+
removeLabel: (label) => `Remove ${label}`,
|
|
34504
|
+
claimedBy: (name) => `claimed by ${name}`,
|
|
34505
|
+
taskHeading: "Task",
|
|
34506
|
+
newTaskHeading: "New task",
|
|
34507
|
+
taskBodyPlaceholder: "Task details, acceptance checklist, notes",
|
|
34508
|
+
addTask: "Add task",
|
|
34509
|
+
saveTask: "Save task",
|
|
34510
|
+
saveTaskFailed: "failed to save task",
|
|
34511
|
+
claim: "Claim",
|
|
34512
|
+
claimTaskFailed: "failed to claim task",
|
|
34513
|
+
done: "Done",
|
|
34514
|
+
doneTaskFailed: "failed to complete task",
|
|
34515
|
+
deleteTaskFailed: "failed to delete task",
|
|
34516
|
+
labelFilterPlaceholder: "label",
|
|
34517
|
+
allLabels: "All",
|
|
34518
|
+
labelFilters: "Label filters",
|
|
34519
|
+
githubIssues: "GitHub Issues",
|
|
34520
|
+
githubRepoPlaceholder: "repo (optional)",
|
|
34521
|
+
githubLabelPlaceholder: "GitHub labels, comma separated",
|
|
34522
|
+
githubSearchPlaceholder: "search issues",
|
|
34523
|
+
githubStateLabels: {
|
|
34524
|
+
open: "Open",
|
|
34525
|
+
closed: "Closed",
|
|
34526
|
+
all: "All"
|
|
34527
|
+
},
|
|
34528
|
+
githubLoad: "Load issues",
|
|
34529
|
+
githubLoadMore: "Load more",
|
|
34530
|
+
githubLoading: "loading issues...",
|
|
34531
|
+
githubLoadFailed: "failed to load GitHub issues",
|
|
34532
|
+
githubShowing: (count, limit) => `Showing ${count} of ${limit}.`,
|
|
34533
|
+
githubRateLimited: (seconds) => `GitHub rate limit hit. Try again in ${seconds}s.`,
|
|
34534
|
+
githubNotLoaded: "GitHub issues are not loaded",
|
|
34535
|
+
githubNoIssues: "No GitHub issues",
|
|
34536
|
+
githubClose: "Close GitHub issues",
|
|
34537
|
+
githubLinked: "linked",
|
|
34538
|
+
githubAddToBoard: "Add to board",
|
|
34539
|
+
githubOpenTask: "Open task",
|
|
34540
|
+
githubDragHint: "Add or drag an issue to link it to a local task.",
|
|
34541
|
+
githubLinkTaskFailed: "failed to link GitHub issue",
|
|
34542
|
+
githubMemoLabel: "Memo:",
|
|
34543
|
+
moreTasks: (count) => `${count} more`,
|
|
34544
|
+
resizeTaskPanel: "Resize task panel",
|
|
34545
|
+
dragTask: "Drag task",
|
|
34546
|
+
editorModes: {
|
|
34547
|
+
write: "write",
|
|
34548
|
+
preview: "preview",
|
|
34549
|
+
split: "split"
|
|
34550
|
+
}
|
|
34551
|
+
},
|
|
32416
34552
|
quickHelp: {
|
|
32417
34553
|
buttonTitle: "quick help (shortcuts)",
|
|
32418
34554
|
panelTitle: "Quick Help",
|
|
@@ -32468,6 +34604,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32468
34604
|
repo: "リポジトリ",
|
|
32469
34605
|
diff: "Diff ビューア",
|
|
32470
34606
|
history: "履歴",
|
|
34607
|
+
journal: "ワークログ",
|
|
32471
34608
|
database: "データストア",
|
|
32472
34609
|
help: "ヘルプ"
|
|
32473
34610
|
},
|
|
@@ -32605,6 +34742,103 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32605
34742
|
filterTitle: "メッセージ、SHA、author:name、path:file でコミットを絞り込みます。",
|
|
32606
34743
|
refreshTitle: "コミット履歴を更新"
|
|
32607
34744
|
},
|
|
34745
|
+
journal: {
|
|
34746
|
+
locale: "ja",
|
|
34747
|
+
ariaLabel: "ワークログとタスク",
|
|
34748
|
+
title: "ワークログ",
|
|
34749
|
+
tabs: {
|
|
34750
|
+
journal: "ログ",
|
|
34751
|
+
tasks: "タスク"
|
|
34752
|
+
},
|
|
34753
|
+
refresh: "ワークログを更新",
|
|
34754
|
+
loading: "読み込み中...",
|
|
34755
|
+
loadFailed: "ワークログの読み込みに失敗しました",
|
|
34756
|
+
statusLabels: {
|
|
34757
|
+
draft: "下書き",
|
|
34758
|
+
todo: "未着手",
|
|
34759
|
+
doing: "進行中",
|
|
34760
|
+
blocked: "ブロック",
|
|
34761
|
+
done: "完了"
|
|
34762
|
+
},
|
|
34763
|
+
priorityLabels: {
|
|
34764
|
+
p0: "P0",
|
|
34765
|
+
p1: "P1",
|
|
34766
|
+
p2: "P2",
|
|
34767
|
+
p3: "P3"
|
|
34768
|
+
},
|
|
34769
|
+
statusField: "ステータス",
|
|
34770
|
+
priorityField: "優先度",
|
|
34771
|
+
previousMonth: "前の月",
|
|
34772
|
+
nextMonth: "次の月",
|
|
34773
|
+
weekDays: ["日", "月", "火", "水", "木", "金", "土"],
|
|
34774
|
+
noEntries: "ログはありません",
|
|
34775
|
+
noRelatedTasks: "関連タスクはありません",
|
|
34776
|
+
noBody: "本文はありません",
|
|
34777
|
+
relatedTasks: "タスク",
|
|
34778
|
+
new: "新規",
|
|
34779
|
+
titlePlaceholder: "タイトル",
|
|
34780
|
+
labelPlaceholder: "ラベル",
|
|
34781
|
+
entryBodyPlaceholder: "今日の作業を記録",
|
|
34782
|
+
addEntry: "ログを追加",
|
|
34783
|
+
saveEntry: "ログを保存",
|
|
34784
|
+
delete: "削除",
|
|
34785
|
+
deleteEntryFailed: "ログの削除に失敗しました",
|
|
34786
|
+
saveEntryFailed: "ログの保存に失敗しました",
|
|
34787
|
+
moveTaskFailed: "タスクの移動に失敗しました",
|
|
34788
|
+
aiQueue: "AIキュー",
|
|
34789
|
+
empty: "空",
|
|
34790
|
+
duePrefix: "期限",
|
|
34791
|
+
startDate: "開始日",
|
|
34792
|
+
endDate: "終了日",
|
|
34793
|
+
removeLabel: (label) => `${label} を削除`,
|
|
34794
|
+
claimedBy: (name) => `${name} が確保中`,
|
|
34795
|
+
taskHeading: "タスク",
|
|
34796
|
+
newTaskHeading: "新規タスク",
|
|
34797
|
+
taskBodyPlaceholder: "詳細、受け入れ条件、メモ",
|
|
34798
|
+
addTask: "タスクを追加",
|
|
34799
|
+
saveTask: "タスクを保存",
|
|
34800
|
+
saveTaskFailed: "タスクの保存に失敗しました",
|
|
34801
|
+
claim: "確保",
|
|
34802
|
+
claimTaskFailed: "タスクの確保に失敗しました",
|
|
34803
|
+
done: "完了",
|
|
34804
|
+
doneTaskFailed: "タスクの完了に失敗しました",
|
|
34805
|
+
deleteTaskFailed: "タスクの削除に失敗しました",
|
|
34806
|
+
labelFilterPlaceholder: "ラベル",
|
|
34807
|
+
allLabels: "すべて",
|
|
34808
|
+
labelFilters: "ラベルフィルタ",
|
|
34809
|
+
githubIssues: "GitHub Issue",
|
|
34810
|
+
githubRepoPlaceholder: "リポジトリ(任意)",
|
|
34811
|
+
githubLabelPlaceholder: "GitHubラベル(カンマ区切り)",
|
|
34812
|
+
githubSearchPlaceholder: "Issue検索",
|
|
34813
|
+
githubStateLabels: {
|
|
34814
|
+
open: "未解決",
|
|
34815
|
+
closed: "解決済み",
|
|
34816
|
+
all: "すべて"
|
|
34817
|
+
},
|
|
34818
|
+
githubLoad: "Issueを表示",
|
|
34819
|
+
githubLoadMore: "さらに読む",
|
|
34820
|
+
githubLoading: "Issueを読み込み中...",
|
|
34821
|
+
githubLoadFailed: "GitHub Issueの読み込みに失敗しました",
|
|
34822
|
+
githubShowing: (count, limit) => `${limit}件中${count}件を表示中。`,
|
|
34823
|
+
githubRateLimited: (seconds) => `GitHubの制限に達しました。${seconds}秒後に再試行してください。`,
|
|
34824
|
+
githubNotLoaded: "GitHub Issueは未読み込みです",
|
|
34825
|
+
githubNoIssues: "GitHub Issueは0件です",
|
|
34826
|
+
githubClose: "GitHub Issueを閉じる",
|
|
34827
|
+
githubLinked: "紐づき済み",
|
|
34828
|
+
githubAddToBoard: "看板へ追加",
|
|
34829
|
+
githubOpenTask: "タスクを開く",
|
|
34830
|
+
githubDragHint: "Issueを追加またはドラッグすると、自分用タスクとして紐づけます。",
|
|
34831
|
+
githubLinkTaskFailed: "GitHub Issueの紐づけに失敗しました",
|
|
34832
|
+
githubMemoLabel: "メモ:",
|
|
34833
|
+
moreTasks: (count) => `他${count}件`,
|
|
34834
|
+
resizeTaskPanel: "タスクパネル幅を変更",
|
|
34835
|
+
dragTask: "タスクをドラッグ",
|
|
34836
|
+
editorModes: {
|
|
34837
|
+
write: "編集",
|
|
34838
|
+
preview: "プレビュー",
|
|
34839
|
+
split: "分割"
|
|
34840
|
+
}
|
|
34841
|
+
},
|
|
32608
34842
|
quickHelp: {
|
|
32609
34843
|
buttonTitle: "クイックヘルプ(ショートカット)",
|
|
32610
34844
|
panelTitle: "クイックヘルプ",
|
|
@@ -32808,6 +35042,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32808
35042
|
button.setAttribute("aria-label", text2.history.refreshTitle);
|
|
32809
35043
|
});
|
|
32810
35044
|
relocalizeHistory?.();
|
|
35045
|
+
relocalizeJournal?.();
|
|
32811
35046
|
setElementText(".scope-settings-head strong", text2.settings.title);
|
|
32812
35047
|
const settingsClose = document.querySelector("#scope-settings-close");
|
|
32813
35048
|
settingsClose?.setAttribute("aria-label", text2.settings.close);
|
|
@@ -32877,6 +35112,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32877
35112
|
relocalizeDatabase?.();
|
|
32878
35113
|
}
|
|
32879
35114
|
let relocalizeHistory = null;
|
|
35115
|
+
let relocalizeJournal = null;
|
|
32880
35116
|
let relocalizeDatabase = null;
|
|
32881
35117
|
let QUICK_HELP = null;
|
|
32882
35118
|
function setViewerLanguage(language, persist = true) {
|
|
@@ -33328,6 +35564,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33328
35564
|
return mount;
|
|
33329
35565
|
}
|
|
33330
35566
|
let ANNOTATIONS_UI = null;
|
|
35567
|
+
let JOURNAL_VIEW = null;
|
|
33331
35568
|
function applyInlineAnnotations() {
|
|
33332
35569
|
ANNOTATIONS_UI?.applyInlineAnnotations();
|
|
33333
35570
|
}
|
|
@@ -33405,6 +35642,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33405
35642
|
preHistoryRange = null;
|
|
33406
35643
|
removeFileHistoryShell2();
|
|
33407
35644
|
}
|
|
35645
|
+
if (previousRoute.screen === "journal" && nextRoute.screen !== "journal") {
|
|
35646
|
+
JOURNAL_VIEW?.suspend();
|
|
35647
|
+
}
|
|
33408
35648
|
STATE.route = nextRoute;
|
|
33409
35649
|
STATE.from = nextRoute.range.from;
|
|
33410
35650
|
STATE.to = nextRoute.range.to;
|
|
@@ -33427,6 +35667,12 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33427
35667
|
if (shouldDispatchFileRouteAfterSetRoute(previousRoute, nextRoute)) {
|
|
33428
35668
|
dispatchFileRoute(nextRoute);
|
|
33429
35669
|
}
|
|
35670
|
+
if (nextRoute.screen === "journal") {
|
|
35671
|
+
cancelActiveSourceLoad("navigation");
|
|
35672
|
+
setPageMode();
|
|
35673
|
+
removeStandaloneSource();
|
|
35674
|
+
JOURNAL_VIEW?.enter();
|
|
35675
|
+
}
|
|
33430
35676
|
}
|
|
33431
35677
|
function setQueryHistoryPanelOpen(open) {
|
|
33432
35678
|
const panel = document.getElementById("query-history-panel");
|
|
@@ -33451,6 +35697,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33451
35697
|
document.body.classList.toggle("gdp-history-page", historyPanelRoute);
|
|
33452
35698
|
document.body.classList.toggle("gdp-file-history-page", fileHistoryRoute);
|
|
33453
35699
|
document.body.classList.toggle("gdp-database-page", STATE.route.screen === "database");
|
|
35700
|
+
document.body.classList.toggle("gdp-journal-page", STATE.route.screen === "journal");
|
|
33454
35701
|
const repoTargetWrap = document.querySelector("#repo-target-wrap");
|
|
33455
35702
|
if (!repoSidebarRoute && repoTargetWrap) {
|
|
33456
35703
|
repoTargetWrap.hidden = true;
|
|
@@ -33501,6 +35748,12 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33501
35748
|
range: currentRange()
|
|
33502
35749
|
});
|
|
33503
35750
|
}
|
|
35751
|
+
if (link2.dataset.route === "journal") {
|
|
35752
|
+
link2.href = buildRoute({
|
|
35753
|
+
screen: "journal",
|
|
35754
|
+
range: currentRange()
|
|
35755
|
+
});
|
|
35756
|
+
}
|
|
33504
35757
|
if (link2.dataset.route === "database") {
|
|
33505
35758
|
link2.href = buildRoute({
|
|
33506
35759
|
screen: "database",
|
|
@@ -33674,6 +35927,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33674
35927
|
focusDiffLine,
|
|
33675
35928
|
scrollDiffElementIntoView,
|
|
33676
35929
|
expandAllFileContext,
|
|
35930
|
+
loadDiffFile,
|
|
33677
35931
|
applyViewedState,
|
|
33678
35932
|
enqueueInitialLoads
|
|
33679
35933
|
} = DIFF_VIEW;
|
|
@@ -34315,6 +36569,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34315
36569
|
function reloadDiffFromUi(trigger) {
|
|
34316
36570
|
const topbarButton = $("#reload-prom");
|
|
34317
36571
|
topbarButton.classList.add("spinning");
|
|
36572
|
+
topbarButton.setAttribute("aria-busy", "true");
|
|
34318
36573
|
if (trigger && trigger !== topbarButton) {
|
|
34319
36574
|
trigger.classList.add("spinning");
|
|
34320
36575
|
trigger.setAttribute("aria-busy", "true");
|
|
@@ -34322,6 +36577,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34322
36577
|
load().finally(() => {
|
|
34323
36578
|
setTimeout(() => {
|
|
34324
36579
|
topbarButton.classList.remove("spinning");
|
|
36580
|
+
topbarButton.setAttribute("aria-busy", "false");
|
|
34325
36581
|
if (trigger && trigger !== topbarButton) {
|
|
34326
36582
|
trigger.classList.remove("spinning");
|
|
34327
36583
|
trigger.setAttribute("aria-busy", "false");
|
|
@@ -34341,6 +36597,11 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34341
36597
|
setStatus("live");
|
|
34342
36598
|
return Promise.resolve(null);
|
|
34343
36599
|
}
|
|
36600
|
+
if (STATE.route.screen === "journal") {
|
|
36601
|
+
JOURNAL_VIEW?.enter();
|
|
36602
|
+
setStatus("live");
|
|
36603
|
+
return Promise.resolve(null);
|
|
36604
|
+
}
|
|
34344
36605
|
if (STATE.route.screen === "file" && !(isFileHistoryRoute(STATE.route) && activeHistoryPathFilter) && dispatchFileRoute(STATE.route)) {
|
|
34345
36606
|
return Promise.resolve({
|
|
34346
36607
|
structureChanged: false,
|
|
@@ -34380,7 +36641,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34380
36641
|
return null;
|
|
34381
36642
|
const result = renderShell(data, options.changedPaths);
|
|
34382
36643
|
applyHideTestsToMeta();
|
|
34383
|
-
setStatus("live");
|
|
36644
|
+
setStatus(data.error ? "error" : "live");
|
|
34384
36645
|
return result;
|
|
34385
36646
|
}).catch(() => {
|
|
34386
36647
|
if (!isCurrentDiffRequest())
|
|
@@ -34402,6 +36663,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34402
36663
|
} else if (STATE.route.screen === "database") {
|
|
34403
36664
|
setStatus("live");
|
|
34404
36665
|
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
36666
|
+
} else if (STATE.route.screen === "journal") {
|
|
36667
|
+
setStatus("live");
|
|
36668
|
+
JOURNAL_VIEW?.enter();
|
|
34405
36669
|
} else
|
|
34406
36670
|
load();
|
|
34407
36671
|
syncLineRefPill();
|
|
@@ -34581,6 +36845,17 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34581
36845
|
event.preventDefault();
|
|
34582
36846
|
closeDoctorSheet();
|
|
34583
36847
|
});
|
|
36848
|
+
JOURNAL_VIEW = createJournalView({
|
|
36849
|
+
getRoute: () => STATE.route,
|
|
36850
|
+
setRoute,
|
|
36851
|
+
currentRange,
|
|
36852
|
+
trackLoad,
|
|
36853
|
+
getText: () => uiText().journal,
|
|
36854
|
+
setPageMode,
|
|
36855
|
+
syncHeaderMenu,
|
|
36856
|
+
setStatus
|
|
36857
|
+
});
|
|
36858
|
+
relocalizeJournal = () => JOURNAL_VIEW?.localize();
|
|
34584
36859
|
const DATABASE_VIEW = createDatabaseView({
|
|
34585
36860
|
setRoute,
|
|
34586
36861
|
setPageMode,
|
|
@@ -34629,6 +36904,8 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34629
36904
|
nextRoute = normalizeInternalFileRoute(nextRoute);
|
|
34630
36905
|
if (previousRoute.screen === "database" && nextRoute.screen !== "database")
|
|
34631
36906
|
DATABASE_VIEW.suspend();
|
|
36907
|
+
if (previousRoute.screen === "journal" && nextRoute.screen !== "journal")
|
|
36908
|
+
JOURNAL_VIEW?.suspend();
|
|
34632
36909
|
if (isHistoryPanelRoute(previousRoute) && !isHistoryPanelRoute(nextRoute))
|
|
34633
36910
|
HISTORY_VIEW.leaveHistory();
|
|
34634
36911
|
if (isHistoryPanelRoute(previousRoute) && !isHistoryPanelRoute(nextRoute))
|
|
@@ -34683,6 +36960,14 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34683
36960
|
setStatus("live");
|
|
34684
36961
|
return;
|
|
34685
36962
|
}
|
|
36963
|
+
if (STATE.route.screen === "journal") {
|
|
36964
|
+
cancelActiveSourceLoad("navigation");
|
|
36965
|
+
setPageMode();
|
|
36966
|
+
removeStandaloneSource();
|
|
36967
|
+
JOURNAL_VIEW?.enter();
|
|
36968
|
+
setStatus("live");
|
|
36969
|
+
return;
|
|
36970
|
+
}
|
|
34686
36971
|
if (STATE.route.screen !== "file") {
|
|
34687
36972
|
cancelActiveSourceLoad("navigation");
|
|
34688
36973
|
setPageMode();
|
|
@@ -34803,6 +37088,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34803
37088
|
focusDiffLine,
|
|
34804
37089
|
scrollDiffElementIntoView,
|
|
34805
37090
|
expandAllFileContext,
|
|
37091
|
+
loadDiffFile,
|
|
34806
37092
|
scrollToFile,
|
|
34807
37093
|
renderStandaloneSource,
|
|
34808
37094
|
removeStandaloneSource,
|
|
@@ -35150,6 +37436,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
35150
37436
|
es.addEventListener("annotation", (event) => {
|
|
35151
37437
|
ANNOTATIONS_UI?.handleSse(event.data);
|
|
35152
37438
|
});
|
|
37439
|
+
es.addEventListener("journal", () => {
|
|
37440
|
+
JOURNAL_VIEW?.handleSse();
|
|
37441
|
+
});
|
|
35153
37442
|
es.addEventListener("db-query", (event) => {
|
|
35154
37443
|
DATABASE_VIEW.handleSse("db-query", event.data);
|
|
35155
37444
|
});
|