@youtyan/code-viewer 0.6.3 → 0.6.5
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 +14 -12
- package/dist/code-viewer.js +2395 -203
- package/package.json +4 -3
- package/skills/code-viewer-journal/SKILL.md +131 -0
- package/web/app.js +2609 -265
- package/web/index.html +1 -0
- package/web/style.css +1653 -169
- package/web/vendor/README.md +10 -6
- package/web/vendor/THIRD_PARTY_NOTICES.txt +41 -1
- package/web/vendor/highlight.js/highlight.min.js +47301 -1213
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)
|
|
@@ -1157,9 +1186,22 @@ ${lines.join(`
|
|
|
1157
1186
|
hcl: "terraform",
|
|
1158
1187
|
xml: "xml",
|
|
1159
1188
|
html: "xml",
|
|
1160
|
-
vue: "
|
|
1189
|
+
vue: "vue",
|
|
1190
|
+
svelte: "svelte",
|
|
1191
|
+
astro: "astro",
|
|
1192
|
+
erb: "erb",
|
|
1193
|
+
rhtml: "erb",
|
|
1194
|
+
ejs: "html",
|
|
1195
|
+
hbs: "handlebars",
|
|
1196
|
+
mustache: "handlebars",
|
|
1197
|
+
liquid: "liquid",
|
|
1198
|
+
pug: "pug",
|
|
1199
|
+
twig: "twig",
|
|
1200
|
+
haml: "haml",
|
|
1161
1201
|
css: "css",
|
|
1162
1202
|
scss: "scss",
|
|
1203
|
+
sass: "sass",
|
|
1204
|
+
less: "less",
|
|
1163
1205
|
md: "markdown",
|
|
1164
1206
|
dockerfile: "dockerfile",
|
|
1165
1207
|
proto: "protobuf",
|
|
@@ -1202,7 +1244,26 @@ ${lines.join(`
|
|
|
1202
1244
|
mm: "objective-cpp",
|
|
1203
1245
|
tex: "tex",
|
|
1204
1246
|
bib: "bibtex",
|
|
1205
|
-
rst: "rst"
|
|
1247
|
+
rst: "rst",
|
|
1248
|
+
graphql: "graphql",
|
|
1249
|
+
graphqls: "graphql",
|
|
1250
|
+
gql: "graphql",
|
|
1251
|
+
ps1: "powershell",
|
|
1252
|
+
psm1: "powershell",
|
|
1253
|
+
psd1: "powershell",
|
|
1254
|
+
ini: "ini",
|
|
1255
|
+
conf: "ini",
|
|
1256
|
+
env: "dotenv",
|
|
1257
|
+
prisma: "prisma",
|
|
1258
|
+
pas: "pascal",
|
|
1259
|
+
adoc: "asciidoc",
|
|
1260
|
+
asciidoc: "asciidoc",
|
|
1261
|
+
jsonc: "jsonc",
|
|
1262
|
+
mts: "typescript",
|
|
1263
|
+
cts: "typescript",
|
|
1264
|
+
kts: "kotlin",
|
|
1265
|
+
cxx: "cpp",
|
|
1266
|
+
hxx: "cpp"
|
|
1206
1267
|
};
|
|
1207
1268
|
var TEXT_SOURCE_EXTENSIONS = new Set([
|
|
1208
1269
|
...Object.keys(EXT_TO_LANG),
|
|
@@ -1354,6 +1415,12 @@ ${lines.join(`
|
|
|
1354
1415
|
"rakefile",
|
|
1355
1416
|
"procfile",
|
|
1356
1417
|
"brewfile",
|
|
1418
|
+
"guardfile",
|
|
1419
|
+
"capfile",
|
|
1420
|
+
"vagrantfile",
|
|
1421
|
+
"podfile",
|
|
1422
|
+
"fastfile",
|
|
1423
|
+
"berksfile",
|
|
1357
1424
|
"gnumakefile",
|
|
1358
1425
|
"bsdmakefile",
|
|
1359
1426
|
".gitattributes",
|
|
@@ -1374,7 +1441,16 @@ ${lines.join(`
|
|
|
1374
1441
|
"go.mod": "go",
|
|
1375
1442
|
"build.bazel": "starlark",
|
|
1376
1443
|
"workspace.bazel": "starlark",
|
|
1377
|
-
"module.bazel": "starlark"
|
|
1444
|
+
"module.bazel": "starlark",
|
|
1445
|
+
gemfile: "ruby",
|
|
1446
|
+
rakefile: "ruby",
|
|
1447
|
+
brewfile: "ruby",
|
|
1448
|
+
guardfile: "ruby",
|
|
1449
|
+
capfile: "ruby",
|
|
1450
|
+
vagrantfile: "ruby",
|
|
1451
|
+
podfile: "ruby",
|
|
1452
|
+
fastfile: "ruby",
|
|
1453
|
+
berksfile: "ruby"
|
|
1378
1454
|
};
|
|
1379
1455
|
function sourceFileName(path) {
|
|
1380
1456
|
return (path.split("/").pop() || path).toLowerCase();
|
|
@@ -8056,6 +8132,132 @@ ${frontmatter.yaml}
|
|
|
8056
8132
|
return { width: rect.width || 800, height: rect.height || 600 };
|
|
8057
8133
|
}
|
|
8058
8134
|
|
|
8135
|
+
// web-src/views/diff-line-select.ts
|
|
8136
|
+
var SELECTED_CLASS = "gdp-diff-line-selected";
|
|
8137
|
+
function cardPath(el) {
|
|
8138
|
+
return el.closest(".gdp-file-shell[data-path]")?.dataset.path || "";
|
|
8139
|
+
}
|
|
8140
|
+
function afterLineFromCell(cell) {
|
|
8141
|
+
const sideCell = cell.closest("td.d2h-code-side-linenumber");
|
|
8142
|
+
if (sideCell) {
|
|
8143
|
+
const side = sideCell.closest(".d2h-file-side-diff");
|
|
8144
|
+
const wrapper = sideCell.closest(".d2h-file-wrapper");
|
|
8145
|
+
if (!side || !wrapper)
|
|
8146
|
+
return null;
|
|
8147
|
+
const sides = wrapper.querySelectorAll(".d2h-file-side-diff");
|
|
8148
|
+
if (sides.length < 2 || side !== sides[1])
|
|
8149
|
+
return null;
|
|
8150
|
+
const line2 = Number((sideCell.textContent || "").trim());
|
|
8151
|
+
return Number.isInteger(line2) && line2 > 0 ? line2 : null;
|
|
8152
|
+
}
|
|
8153
|
+
const numCell = cell.closest("td.d2h-code-linenumber");
|
|
8154
|
+
if (!numCell)
|
|
8155
|
+
return null;
|
|
8156
|
+
const raw = (numCell.querySelector(".line-num2")?.textContent || "").trim();
|
|
8157
|
+
const line = Number(raw);
|
|
8158
|
+
return Number.isInteger(line) && line > 0 ? line : null;
|
|
8159
|
+
}
|
|
8160
|
+
function afterLineCellFromRow(row) {
|
|
8161
|
+
const cells = row.querySelectorAll("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
8162
|
+
for (const cell of cells) {
|
|
8163
|
+
if (afterLineFromCell(cell) !== null)
|
|
8164
|
+
return cell;
|
|
8165
|
+
}
|
|
8166
|
+
return null;
|
|
8167
|
+
}
|
|
8168
|
+
function diffRowAfterLineNumber(row) {
|
|
8169
|
+
const cell = afterLineCellFromRow(row);
|
|
8170
|
+
return cell ? afterLineFromCell(cell) : null;
|
|
8171
|
+
}
|
|
8172
|
+
function diffRowHasAfterChange(row) {
|
|
8173
|
+
const cell = afterLineCellFromRow(row);
|
|
8174
|
+
return !!cell && cell.classList.contains("d2h-ins");
|
|
8175
|
+
}
|
|
8176
|
+
function rowsWithAfterLines(card) {
|
|
8177
|
+
const out = [];
|
|
8178
|
+
card.querySelectorAll("table.d2h-diff-table tr").forEach((row) => {
|
|
8179
|
+
const line = diffRowAfterLineNumber(row);
|
|
8180
|
+
if (line !== null)
|
|
8181
|
+
out.push({ row, line });
|
|
8182
|
+
});
|
|
8183
|
+
return out;
|
|
8184
|
+
}
|
|
8185
|
+
function createDiffLineSelect(deps) {
|
|
8186
|
+
let drag = null;
|
|
8187
|
+
let selection = null;
|
|
8188
|
+
function clearHighlights() {
|
|
8189
|
+
document.querySelectorAll(`.${SELECTED_CLASS}`).forEach((row) => {
|
|
8190
|
+
row.classList.remove(SELECTED_CLASS);
|
|
8191
|
+
});
|
|
8192
|
+
}
|
|
8193
|
+
function applySelection(next) {
|
|
8194
|
+
selection = next;
|
|
8195
|
+
clearHighlights();
|
|
8196
|
+
if (!next) {
|
|
8197
|
+
deps.pill.hide();
|
|
8198
|
+
return;
|
|
8199
|
+
}
|
|
8200
|
+
const start = Math.min(next.start, next.end);
|
|
8201
|
+
const end = Math.max(next.start, next.end);
|
|
8202
|
+
const card = document.querySelector(`.gdp-file-shell[data-path="${CSS.escape(next.path)}"]`);
|
|
8203
|
+
if (card) {
|
|
8204
|
+
for (const item of rowsWithAfterLines(card)) {
|
|
8205
|
+
if (item.line >= start && item.line <= end)
|
|
8206
|
+
item.row.classList.add(SELECTED_CLASS);
|
|
8207
|
+
}
|
|
8208
|
+
}
|
|
8209
|
+
deps.pill.show(next.path, start, end);
|
|
8210
|
+
}
|
|
8211
|
+
function clear() {
|
|
8212
|
+
const hadSelection = !!selection || !!drag;
|
|
8213
|
+
drag = null;
|
|
8214
|
+
applySelection(null);
|
|
8215
|
+
return hadSelection;
|
|
8216
|
+
}
|
|
8217
|
+
const diff = document.querySelector("#diff");
|
|
8218
|
+
if (!diff)
|
|
8219
|
+
return { clear };
|
|
8220
|
+
diff.addEventListener("mousedown", (e2) => {
|
|
8221
|
+
const target = e2.target;
|
|
8222
|
+
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
8223
|
+
if (!cell)
|
|
8224
|
+
return;
|
|
8225
|
+
const line = afterLineFromCell(cell);
|
|
8226
|
+
const path = cardPath(cell);
|
|
8227
|
+
if (line === null || !path) {
|
|
8228
|
+
if (selection)
|
|
8229
|
+
clear();
|
|
8230
|
+
return;
|
|
8231
|
+
}
|
|
8232
|
+
e2.preventDefault();
|
|
8233
|
+
drag = { path, start: line };
|
|
8234
|
+
applySelection({ path, start: line, end: line });
|
|
8235
|
+
});
|
|
8236
|
+
diff.addEventListener("mouseover", (e2) => {
|
|
8237
|
+
if (!drag)
|
|
8238
|
+
return;
|
|
8239
|
+
const target = e2.target;
|
|
8240
|
+
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
8241
|
+
if (!cell || cardPath(cell) !== drag.path)
|
|
8242
|
+
return;
|
|
8243
|
+
const line = afterLineFromCell(cell);
|
|
8244
|
+
if (line === null)
|
|
8245
|
+
return;
|
|
8246
|
+
applySelection({ path: drag.path, start: drag.start, end: line });
|
|
8247
|
+
});
|
|
8248
|
+
document.addEventListener("mouseup", () => {
|
|
8249
|
+
drag = null;
|
|
8250
|
+
});
|
|
8251
|
+
document.addEventListener("keydown", (e2) => {
|
|
8252
|
+
if (isImeComposing(e2))
|
|
8253
|
+
return;
|
|
8254
|
+
if (e2.key === "Escape" && selection && !drag && !isEditableKeyTarget(e2.target)) {
|
|
8255
|
+
clear();
|
|
8256
|
+
}
|
|
8257
|
+
});
|
|
8258
|
+
return { clear };
|
|
8259
|
+
}
|
|
8260
|
+
|
|
8059
8261
|
// web-src/views/ui-dialog.ts
|
|
8060
8262
|
var BACKDROP_CLASS = "gdp-dialog-backdrop";
|
|
8061
8263
|
var DIALOG_CLASS = "gdp-dialog";
|
|
@@ -8478,15 +8680,34 @@ ${frontmatter.yaml}
|
|
|
8478
8680
|
function inlineAnnotationTargetRow(entry) {
|
|
8479
8681
|
if (!entry.line)
|
|
8480
8682
|
return null;
|
|
8481
|
-
const line = entry.line.end;
|
|
8482
8683
|
const card = document.querySelector(deps.diffCardSelector(entry.path));
|
|
8483
8684
|
if (!card)
|
|
8484
8685
|
return null;
|
|
8686
|
+
const line = entry.line.end;
|
|
8485
8687
|
const sourceRow = card.querySelector(`.gdp-source-table tr[data-line="${String(line)}"]`);
|
|
8486
8688
|
if (sourceRow)
|
|
8487
8689
|
return sourceRow;
|
|
8690
|
+
const start = entry.line.start;
|
|
8691
|
+
const end = entry.line.end;
|
|
8488
8692
|
const rows = Array.from(card.querySelectorAll("table.d2h-diff-table tr"));
|
|
8489
|
-
|
|
8693
|
+
const byLine = new Map;
|
|
8694
|
+
let hasChangedAfterRow = false;
|
|
8695
|
+
for (const row of rows) {
|
|
8696
|
+
const rowLine = deps.diffRowLineNumber(row);
|
|
8697
|
+
if (rowLine === null || rowLine < start || rowLine > end)
|
|
8698
|
+
continue;
|
|
8699
|
+
if (!byLine.has(rowLine))
|
|
8700
|
+
byLine.set(rowLine, row);
|
|
8701
|
+
if (diffRowHasAfterChange(row))
|
|
8702
|
+
hasChangedAfterRow = true;
|
|
8703
|
+
}
|
|
8704
|
+
if (!hasChangedAfterRow)
|
|
8705
|
+
return null;
|
|
8706
|
+
for (let rowLine = start;rowLine <= end; rowLine++) {
|
|
8707
|
+
if (!byLine.has(rowLine))
|
|
8708
|
+
return null;
|
|
8709
|
+
}
|
|
8710
|
+
return byLine.get(end) || null;
|
|
8490
8711
|
}
|
|
8491
8712
|
function siblingSideRow(row) {
|
|
8492
8713
|
const side = row.closest(".d2h-file-side-diff");
|
|
@@ -8517,10 +8738,6 @@ ${frontmatter.yaml}
|
|
|
8517
8738
|
row.remove();
|
|
8518
8739
|
});
|
|
8519
8740
|
const session = ANNOTATIONS.sessions.find((s2) => s2.id === activeSessionId);
|
|
8520
|
-
if (!annotationPanel.hidden) {
|
|
8521
|
-
applyDatabaseAnnotations(undefined);
|
|
8522
|
-
return;
|
|
8523
|
-
}
|
|
8524
8741
|
applyDatabaseAnnotations(session);
|
|
8525
8742
|
if (!session)
|
|
8526
8743
|
return;
|
|
@@ -8589,21 +8806,18 @@ ${frontmatter.yaml}
|
|
|
8589
8806
|
renderAnnotationPanel();
|
|
8590
8807
|
applyInlineAnnotations();
|
|
8591
8808
|
}
|
|
8592
|
-
async function
|
|
8593
|
-
if (!entry.line
|
|
8594
|
-
return;
|
|
8809
|
+
async function waitForAnnotationDiffTarget(entry) {
|
|
8810
|
+
if (!entry.line)
|
|
8811
|
+
return null;
|
|
8812
|
+
const ready = inlineAnnotationTargetRow(entry);
|
|
8813
|
+
if (ready)
|
|
8814
|
+
return ready;
|
|
8595
8815
|
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));
|
|
8816
|
+
if (!card || card.classList.contains("gdp-standalone-source"))
|
|
8817
|
+
return null;
|
|
8818
|
+
if (!card.classList.contains("loaded") && !await deps.loadDiffFile(entry.path))
|
|
8819
|
+
return null;
|
|
8820
|
+
return inlineAnnotationTargetRow(entry);
|
|
8607
8821
|
}
|
|
8608
8822
|
function syncInlineAnnotationActive() {
|
|
8609
8823
|
document.querySelectorAll(".gdp-annotation-row").forEach((row) => {
|
|
@@ -9097,11 +9311,13 @@ ${frontmatter.yaml}
|
|
|
9097
9311
|
return;
|
|
9098
9312
|
emptyDiffRangeKey = deps.getFiles().length ? null : rangeKey;
|
|
9099
9313
|
}
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9314
|
+
let showSourceView = !deps.getFiles().some((f2) => f2.path === entry.path);
|
|
9315
|
+
let replaceSourceRoute = needDiffLoad;
|
|
9316
|
+
if (!showSourceView) {
|
|
9103
9317
|
const hasDiffCards = !!document.querySelector(".gdp-file-shell:not(.gdp-standalone-source)");
|
|
9104
9318
|
if (!hasDiffCards) {
|
|
9319
|
+
deps.setRoute({ screen: "diff", range, path: entry.path, line }, needDiffLoad);
|
|
9320
|
+
deps.setPageMode();
|
|
9105
9321
|
deps.removeStandaloneSource();
|
|
9106
9322
|
await deps.load();
|
|
9107
9323
|
if (stale())
|
|
@@ -9109,14 +9325,22 @@ ${frontmatter.yaml}
|
|
|
9109
9325
|
}
|
|
9110
9326
|
deps.removeStandaloneSource();
|
|
9111
9327
|
deps.scrollToFile(entry.path, line);
|
|
9112
|
-
await
|
|
9328
|
+
const target = await waitForAnnotationDiffTarget(entry);
|
|
9113
9329
|
if (stale())
|
|
9114
9330
|
return;
|
|
9115
|
-
|
|
9331
|
+
if (!target) {
|
|
9332
|
+
showSourceView = true;
|
|
9333
|
+
replaceSourceRoute = true;
|
|
9334
|
+
} else {
|
|
9335
|
+
deps.setRoute({ screen: "diff", range, path: entry.path, line }, needDiffLoad);
|
|
9336
|
+
deps.setPageMode();
|
|
9337
|
+
}
|
|
9338
|
+
}
|
|
9339
|
+
if (showSourceView) {
|
|
9116
9340
|
const ref = annotationRefForEntry(entry);
|
|
9117
9341
|
const card = document.querySelector(".gdp-standalone-source");
|
|
9118
9342
|
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 },
|
|
9343
|
+
deps.setRoute({ screen: "file", path: entry.path, ref, view: "blob", line, range }, replaceSourceRoute);
|
|
9120
9344
|
deps.setPageMode();
|
|
9121
9345
|
if (reusable && card) {
|
|
9122
9346
|
focusStandaloneSourceLines(card, entry);
|
|
@@ -14544,7 +14768,7 @@ ${frontmatter.yaml}
|
|
|
14544
14768
|
refreshBtn.setAttribute("aria-busy", refreshBusy ? "true" : "false");
|
|
14545
14769
|
refreshBtn.disabled = refreshBusy;
|
|
14546
14770
|
refreshBtn.classList.toggle("spinning", refreshBusy);
|
|
14547
|
-
refreshLabel.textContent =
|
|
14771
|
+
refreshLabel.textContent = "";
|
|
14548
14772
|
}
|
|
14549
14773
|
function setRefreshBusy(busy) {
|
|
14550
14774
|
refreshBusy = busy;
|
|
@@ -16200,7 +16424,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16200
16424
|
refreshBtn.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
16201
16425
|
const refreshLabel = document.createElement("span");
|
|
16202
16426
|
refreshLabel.className = "db-grid-refresh-label";
|
|
16203
|
-
refreshLabel.textContent =
|
|
16427
|
+
refreshLabel.textContent = "";
|
|
16204
16428
|
refreshBtn.appendChild(refreshLabel);
|
|
16205
16429
|
const refreshResult = document.createElement("span");
|
|
16206
16430
|
refreshResult.className = "db-refresh-result db-grid-refresh-result";
|
|
@@ -16308,7 +16532,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16308
16532
|
filteredEmptyActions.hidden = true;
|
|
16309
16533
|
const filteredEmptyReloadAction = document.createElement("button");
|
|
16310
16534
|
filteredEmptyReloadAction.type = "button";
|
|
16311
|
-
filteredEmptyReloadAction.className = "db-btn db-btn-
|
|
16535
|
+
filteredEmptyReloadAction.className = "db-btn db-btn-icon db-grid-refresh";
|
|
16536
|
+
filteredEmptyReloadAction.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
16312
16537
|
filteredEmptyReloadAction.hidden = true;
|
|
16313
16538
|
filteredEmptyReloadAction.disabled = true;
|
|
16314
16539
|
const filteredEmptyAction = document.createElement("button");
|
|
@@ -16404,7 +16629,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16404
16629
|
let rafId = 0;
|
|
16405
16630
|
let statusEl = null;
|
|
16406
16631
|
let isRefreshing = false;
|
|
16407
|
-
let refreshResultState = null;
|
|
16408
16632
|
let filterTimer = null;
|
|
16409
16633
|
let selectedRowIndex = -1;
|
|
16410
16634
|
let activeCellRowIndex = -1;
|
|
@@ -16524,7 +16748,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16524
16748
|
function syncRefreshButton() {
|
|
16525
16749
|
const count = activeFilterCount();
|
|
16526
16750
|
const t2 = text2().grid;
|
|
16527
|
-
refreshLabel.textContent =
|
|
16751
|
+
refreshLabel.textContent = "";
|
|
16528
16752
|
const action = count > 0 ? t2.refreshActionWithFilters(count) : t2.refreshAction;
|
|
16529
16753
|
refreshBtn.classList.toggle("has-filters", count > 0);
|
|
16530
16754
|
refreshBtn.title = action;
|
|
@@ -16532,26 +16756,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16532
16756
|
refreshBtn.setAttribute("aria-busy", isRefreshing ? "true" : "false");
|
|
16533
16757
|
}
|
|
16534
16758
|
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);
|
|
16759
|
+
refreshResult.hidden = true;
|
|
16760
|
+
refreshResult.textContent = "";
|
|
16761
|
+
refreshResult.classList.toggle("changed", false);
|
|
16545
16762
|
}
|
|
16546
16763
|
function clearRefreshResult() {
|
|
16547
|
-
refreshResultState = null;
|
|
16548
|
-
syncRefreshResult();
|
|
16549
|
-
}
|
|
16550
|
-
function setRefreshResult(previousTotalRows, nextTotalRows) {
|
|
16551
|
-
refreshResultState = {
|
|
16552
|
-
delta: nextTotalRows - previousTotalRows,
|
|
16553
|
-
totalRows: nextTotalRows
|
|
16554
|
-
};
|
|
16555
16764
|
syncRefreshResult();
|
|
16556
16765
|
}
|
|
16557
16766
|
function syncFilterClearButton() {
|
|
@@ -16578,7 +16787,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16578
16787
|
const reloadAction = t2.refreshActionWithFilters(count);
|
|
16579
16788
|
filteredEmptyTitle.textContent = t2.filteredEmptyTitle(count);
|
|
16580
16789
|
filteredEmptyHint.textContent = t2.filteredEmptyHint;
|
|
16581
|
-
filteredEmptyReloadAction.
|
|
16790
|
+
filteredEmptyReloadAction.classList.toggle("spinning", isRefreshing);
|
|
16791
|
+
filteredEmptyReloadAction.textContent = "";
|
|
16792
|
+
filteredEmptyReloadAction.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
16582
16793
|
filteredEmptyReloadAction.title = reloadAction;
|
|
16583
16794
|
filteredEmptyReloadAction.setAttribute("aria-label", reloadAction);
|
|
16584
16795
|
filteredEmptyReloadAction.setAttribute("aria-busy", isRefreshing ? "true" : "false");
|
|
@@ -16628,7 +16839,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16628
16839
|
if (!currentTable || refreshBtn.disabled)
|
|
16629
16840
|
return;
|
|
16630
16841
|
const refreshTable = currentTable;
|
|
16631
|
-
const previousTotalRows = totalRows;
|
|
16632
16842
|
const refreshFilterKey = JSON.stringify(collectFilters());
|
|
16633
16843
|
const scrollTop = viewport.scrollTop;
|
|
16634
16844
|
const pageStart = Math.floor(scrollTop / ROW_HEIGHT / PAGE_SIZE) * PAGE_SIZE;
|
|
@@ -16661,7 +16871,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
16661
16871
|
syncFilteredEmptyState();
|
|
16662
16872
|
if (!endedInError) {
|
|
16663
16873
|
if (loadGeneration === refreshGeneration && JSON.stringify(collectFilters()) === refreshFilterKey) {
|
|
16664
|
-
|
|
16874
|
+
syncRefreshResult();
|
|
16665
16875
|
callbacks.onRefreshComplete?.({
|
|
16666
16876
|
table: refreshTable,
|
|
16667
16877
|
totalRows,
|
|
@@ -18563,7 +18773,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
18563
18773
|
const tableSelectGuard = createAbortGuard();
|
|
18564
18774
|
let historyRefreshPending = null;
|
|
18565
18775
|
let isRefreshingDatastores = false;
|
|
18566
|
-
let datastoreRefreshResult = null;
|
|
18567
18776
|
const dbSelect = document.createElement("select");
|
|
18568
18777
|
dbSelect.className = "db-file-select";
|
|
18569
18778
|
reloc(() => {
|
|
@@ -19682,46 +19891,24 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19682
19891
|
handleSchemaSelectChange();
|
|
19683
19892
|
});
|
|
19684
19893
|
function syncDbRefreshButton() {
|
|
19685
|
-
|
|
19686
|
-
dbRefreshLabel.textContent = isRefreshingDatastores ? text2.refreshDatastoresBusy : text2.refreshDatastoresShort;
|
|
19894
|
+
dbRefreshLabel.textContent = "";
|
|
19687
19895
|
dbRefreshBtn.setAttribute("aria-busy", isRefreshingDatastores ? "true" : "false");
|
|
19688
19896
|
syncDatastoreRefreshResult();
|
|
19689
19897
|
}
|
|
19690
19898
|
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 };
|
|
19899
|
+
dbRefreshResult.hidden = true;
|
|
19900
|
+
dbRefreshResult.textContent = "";
|
|
19901
|
+
dbRefreshResult.classList.toggle("changed", false);
|
|
19713
19902
|
}
|
|
19714
19903
|
async function refreshDatastoreList() {
|
|
19715
19904
|
if (dbRefreshBtn.disabled)
|
|
19716
19905
|
return;
|
|
19717
|
-
const beforeFiles = [...lastFiles];
|
|
19718
19906
|
isRefreshingDatastores = true;
|
|
19719
19907
|
dbRefreshBtn.disabled = true;
|
|
19720
19908
|
dbRefreshBtn.classList.add("spinning");
|
|
19721
19909
|
syncDbRefreshButton();
|
|
19722
19910
|
try {
|
|
19723
19911
|
await outerDeps.refreshDatastores();
|
|
19724
|
-
datastoreRefreshResult = cb.isActive() ? diffDatastoreFiles(beforeFiles, lastFiles) : null;
|
|
19725
19912
|
} finally {
|
|
19726
19913
|
isRefreshingDatastores = false;
|
|
19727
19914
|
dbRefreshBtn.classList.remove("spinning");
|
|
@@ -19757,7 +19944,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
19757
19944
|
const dbId = dbSelect.value;
|
|
19758
19945
|
if (!dbId)
|
|
19759
19946
|
return;
|
|
19760
|
-
datastoreRefreshResult = null;
|
|
19761
19947
|
syncDatastoreRefreshResult();
|
|
19762
19948
|
const generation = ++loadGeneration;
|
|
19763
19949
|
const file = lastFiles.find((f2) => f2.id === dbId);
|
|
@@ -21146,119 +21332,6 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21146
21332
|
return `${bytes} B`;
|
|
21147
21333
|
}
|
|
21148
21334
|
|
|
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
21335
|
// web-src/core/ws-highlight.ts
|
|
21263
21336
|
function isWhitespaceOnlyInlineHighlight(text2) {
|
|
21264
21337
|
return !!text2 && !/\S/.test(text2);
|
|
@@ -21552,22 +21625,31 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
21552
21625
|
});
|
|
21553
21626
|
}
|
|
21554
21627
|
function diffRowLineNumber(row) {
|
|
21555
|
-
|
|
21556
|
-
const raw = (newLine?.textContent || "").trim();
|
|
21557
|
-
const line = Number(raw);
|
|
21558
|
-
return Number.isInteger(line) && line > 0 ? line : null;
|
|
21628
|
+
return diffRowAfterLineNumber(row);
|
|
21559
21629
|
}
|
|
21560
21630
|
function focusDiffLine(card, line) {
|
|
21561
21631
|
const start = lineTargetStart(line);
|
|
21562
21632
|
if (!start)
|
|
21563
21633
|
return false;
|
|
21634
|
+
const end = typeof line === "object" ? line.end : start;
|
|
21564
21635
|
const rows = Array.from(card.querySelectorAll("table.d2h-diff-table tr"));
|
|
21565
|
-
const
|
|
21566
|
-
|
|
21636
|
+
const targetRows = rows.filter((candidate) => {
|
|
21637
|
+
const rowLine = diffRowLineNumber(candidate);
|
|
21638
|
+
return rowLine !== null && rowLine >= start && rowLine <= end;
|
|
21639
|
+
});
|
|
21640
|
+
const targetLines = new Set(targetRows.map((candidate) => diffRowLineNumber(candidate)).filter((value) => value !== null));
|
|
21641
|
+
for (let lineNo = start;lineNo <= end; lineNo++) {
|
|
21642
|
+
if (!targetLines.has(lineNo))
|
|
21643
|
+
return false;
|
|
21644
|
+
}
|
|
21645
|
+
const firstRow = targetRows[0];
|
|
21646
|
+
if (!firstRow)
|
|
21567
21647
|
return false;
|
|
21568
21648
|
clearDiffLineFocus();
|
|
21569
|
-
|
|
21570
|
-
|
|
21649
|
+
targetRows.forEach((row) => {
|
|
21650
|
+
row.classList.add("gdp-diff-line-target");
|
|
21651
|
+
});
|
|
21652
|
+
scrollDiffElementIntoView(firstRow, "center");
|
|
21571
21653
|
return true;
|
|
21572
21654
|
}
|
|
21573
21655
|
function scrollDiffElementIntoView(element, block2) {
|
|
@@ -22075,7 +22157,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22075
22157
|
}
|
|
22076
22158
|
});
|
|
22077
22159
|
}
|
|
22078
|
-
function loadFile(file, card, urlOverride) {
|
|
22160
|
+
function loadFile(file, card, urlOverride, options) {
|
|
22079
22161
|
card.classList.remove("pending");
|
|
22080
22162
|
card.classList.add("loading");
|
|
22081
22163
|
if (lazyObserver)
|
|
@@ -22109,7 +22191,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22109
22191
|
retryStale();
|
|
22110
22192
|
return;
|
|
22111
22193
|
}
|
|
22112
|
-
|
|
22194
|
+
if (!options?.immediate)
|
|
22195
|
+
await nextIdle();
|
|
22113
22196
|
if (String(myReq) !== card.dataset.reqId)
|
|
22114
22197
|
return;
|
|
22115
22198
|
renderFile(file, data, card);
|
|
@@ -22132,6 +22215,18 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22132
22215
|
});
|
|
22133
22216
|
});
|
|
22134
22217
|
}
|
|
22218
|
+
async function loadDiffFile(path) {
|
|
22219
|
+
const card = document.querySelector(diffCardSelector(path));
|
|
22220
|
+
if (!card || card.classList.contains("gdp-standalone-source"))
|
|
22221
|
+
return false;
|
|
22222
|
+
if (card.classList.contains("loaded"))
|
|
22223
|
+
return true;
|
|
22224
|
+
const file = STATE.files.find((x) => x.path === path);
|
|
22225
|
+
if (!file)
|
|
22226
|
+
return false;
|
|
22227
|
+
await loadFile(file, card, undefined, { immediate: true });
|
|
22228
|
+
return card.classList.contains("loaded");
|
|
22229
|
+
}
|
|
22135
22230
|
function mountDiff(card, file, data) {
|
|
22136
22231
|
const head = card.querySelector(".gdp-shell-header");
|
|
22137
22232
|
if (head)
|
|
@@ -22656,6 +22751,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22656
22751
|
enqueueInitialLoads,
|
|
22657
22752
|
enqueueLoad,
|
|
22658
22753
|
loadFile,
|
|
22754
|
+
loadDiffFile,
|
|
22659
22755
|
createFileBreadcrumb,
|
|
22660
22756
|
highlightInsertedSpans,
|
|
22661
22757
|
setFileCollapsed,
|
|
@@ -22937,13 +23033,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22937
23033
|
bodyExpandClose: "Collapse",
|
|
22938
23034
|
bodyExpandMore: (n2) => `Show more (${n2} lines)`,
|
|
22939
23035
|
refreshLabel: "Refresh",
|
|
22940
|
-
refreshLabelPending: "Update",
|
|
22941
23036
|
refreshTitle: "Refresh commit history",
|
|
22942
23037
|
refreshTitlePending: "History may have changed. Refresh",
|
|
22943
|
-
refreshPendingStatus: "History may have changed",
|
|
22944
|
-
refreshResultUpdated: (sha) => `Updated: ${sha} is now latest`,
|
|
22945
|
-
refreshResultUnchanged: "No new commits",
|
|
22946
|
-
refreshResultUnchangedInView: "No new commits in this view",
|
|
22947
23038
|
filterClearLabel: "Clear",
|
|
22948
23039
|
filterClearTitle: "Clear commit filter"
|
|
22949
23040
|
},
|
|
@@ -22952,13 +23043,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22952
23043
|
bodyExpandClose: "閉じる",
|
|
22953
23044
|
bodyExpandMore: (n2) => `もっと見る (${n2} 行)`,
|
|
22954
23045
|
refreshLabel: "更新",
|
|
22955
|
-
refreshLabelPending: "更新あり",
|
|
22956
23046
|
refreshTitle: "コミット履歴を更新",
|
|
22957
23047
|
refreshTitlePending: "新しい履歴がある可能性があります。更新",
|
|
22958
|
-
refreshPendingStatus: "新しい履歴がある可能性があります",
|
|
22959
|
-
refreshResultUpdated: (sha) => `更新: ${sha} が最新です`,
|
|
22960
|
-
refreshResultUnchanged: "新しいコミットはありません",
|
|
22961
|
-
refreshResultUnchangedInView: "この表示では新しいコミットはありません",
|
|
22962
23048
|
filterClearLabel: "解除",
|
|
22963
23049
|
filterClearTitle: "コミットフィルタを解除"
|
|
22964
23050
|
}
|
|
@@ -22994,7 +23080,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
22994
23080
|
refreshButton.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
22995
23081
|
const refreshLabel = document.createElement("span");
|
|
22996
23082
|
refreshLabel.className = "history-refresh-label";
|
|
22997
|
-
refreshLabel.textContent =
|
|
23083
|
+
refreshLabel.textContent = "";
|
|
22998
23084
|
refreshButton.appendChild(refreshLabel);
|
|
22999
23085
|
const refreshResult = document.createElement("span");
|
|
23000
23086
|
refreshResult.className = "db-refresh-result history-refresh-result";
|
|
@@ -23245,28 +23331,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
23245
23331
|
}
|
|
23246
23332
|
function setRefreshResult(previousTopSha, nextTopSha) {
|
|
23247
23333
|
freshSha = previousTopSha && nextTopSha && previousTopSha !== nextTopSha ? nextTopSha : "";
|
|
23248
|
-
refreshStatus =
|
|
23334
|
+
refreshStatus = { type: "none" };
|
|
23249
23335
|
renderList();
|
|
23250
23336
|
}
|
|
23251
|
-
function refreshStatusMessage() {
|
|
23252
|
-
const text2 = historyText(deps.getLanguage());
|
|
23253
|
-
switch (refreshStatus.type) {
|
|
23254
|
-
case "pending":
|
|
23255
|
-
return text2.refreshPendingStatus;
|
|
23256
|
-
case "updated":
|
|
23257
|
-
return text2.refreshResultUpdated(refreshStatus.sha);
|
|
23258
|
-
case "unchanged":
|
|
23259
|
-
return refreshStatus.scoped ? text2.refreshResultUnchangedInView : text2.refreshResultUnchanged;
|
|
23260
|
-
case "none":
|
|
23261
|
-
return "";
|
|
23262
|
-
default: {
|
|
23263
|
-
const exhaustive = refreshStatus;
|
|
23264
|
-
return exhaustive;
|
|
23265
|
-
}
|
|
23266
|
-
}
|
|
23267
|
-
}
|
|
23268
23337
|
function syncRefreshStatusText() {
|
|
23269
|
-
setStatusText(loading ? "loading..." :
|
|
23338
|
+
setStatusText(loading ? "loading..." : refreshStatus.type === "pending" ? "" : commits.length ? "" : "no commits");
|
|
23270
23339
|
}
|
|
23271
23340
|
function commitInfoElement() {
|
|
23272
23341
|
return activeMount.commitInfo || document.querySelector("#history-commit-info");
|
|
@@ -23377,19 +23446,17 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
23377
23446
|
button.setAttribute("aria-label", title);
|
|
23378
23447
|
button.classList.toggle("has-update", hasPendingUpdate);
|
|
23379
23448
|
const label = button.querySelector(".history-refresh-label");
|
|
23380
|
-
if (label)
|
|
23381
|
-
label.textContent =
|
|
23382
|
-
}
|
|
23449
|
+
if (label)
|
|
23450
|
+
label.textContent = "";
|
|
23383
23451
|
}
|
|
23384
23452
|
function syncRefreshResult(result) {
|
|
23385
23453
|
const el = result ?? activeMount.refreshResult;
|
|
23386
23454
|
if (!el)
|
|
23387
23455
|
return;
|
|
23388
|
-
|
|
23389
|
-
el.
|
|
23390
|
-
el.
|
|
23391
|
-
el.classList.toggle("
|
|
23392
|
-
el.classList.toggle("pending", refreshStatus.type === "pending");
|
|
23456
|
+
el.textContent = "";
|
|
23457
|
+
el.hidden = true;
|
|
23458
|
+
el.classList.toggle("changed", false);
|
|
23459
|
+
el.classList.toggle("pending", false);
|
|
23393
23460
|
}
|
|
23394
23461
|
function syncFilterClearButton(button) {
|
|
23395
23462
|
const clearButton = button ?? activeMount.filterClearButton;
|
|
@@ -24404,11 +24471,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
24404
24471
|
},
|
|
24405
24472
|
{
|
|
24406
24473
|
kind: "paragraph",
|
|
24407
|
-
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)."
|
|
24474
|
+
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)."
|
|
24408
24475
|
},
|
|
24409
24476
|
{
|
|
24410
24477
|
kind: "paragraph",
|
|
24411
|
-
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
|
|
24478
|
+
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.'
|
|
24412
24479
|
},
|
|
24413
24480
|
{
|
|
24414
24481
|
kind: "command",
|
|
@@ -24420,7 +24487,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
24420
24487
|
title: "Doctor JSON for agents and CI",
|
|
24421
24488
|
command: `code-viewer doctor --json
|
|
24422
24489
|
code-viewer doctor --cwd /path/to/repo --port 64160 --json
|
|
24423
|
-
code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --json`
|
|
24490
|
+
code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=/opt/bin/gh --json`
|
|
24424
24491
|
}
|
|
24425
24492
|
]
|
|
24426
24493
|
}
|
|
@@ -24776,7 +24843,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
24776
24843
|
skills: {
|
|
24777
24844
|
nav: "Agent Skill",
|
|
24778
24845
|
title: "Agent Skill Setup",
|
|
24779
|
-
intro: "The package bundles
|
|
24846
|
+
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.",
|
|
24780
24847
|
groups: [
|
|
24781
24848
|
{
|
|
24782
24849
|
title: "Install the skills",
|
|
@@ -24821,12 +24888,16 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
24821
24888
|
"Code reviews, onboarding walkthroughs, and explanations of changes."
|
|
24822
24889
|
],
|
|
24823
24890
|
[
|
|
24824
|
-
"How to
|
|
24825
|
-
"Use
|
|
24891
|
+
"How to manage Work Log tasks",
|
|
24892
|
+
"Use the journal CLI for task lists, task creation, claim/done flows, and GitHub Issue linking."
|
|
24893
|
+
],
|
|
24894
|
+
[
|
|
24895
|
+
"How to inspect data",
|
|
24896
|
+
"Use read-only query commands for schema, SQL, saved query history, and table-wide search."
|
|
24826
24897
|
],
|
|
24827
24898
|
[
|
|
24828
|
-
"How to
|
|
24829
|
-
"Use
|
|
24899
|
+
"How to verify data changes",
|
|
24900
|
+
"Use snapshot create/list/diff workflows to compare before and after states."
|
|
24830
24901
|
]
|
|
24831
24902
|
]
|
|
24832
24903
|
}
|
|
@@ -24841,8 +24912,10 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
24841
24912
|
},
|
|
24842
24913
|
{
|
|
24843
24914
|
kind: "command",
|
|
24844
|
-
title: "Show the agent
|
|
24845
|
-
command:
|
|
24915
|
+
title: "Show the agent guides",
|
|
24916
|
+
command: `code-viewer annotate agent-help
|
|
24917
|
+
code-viewer journal agent-help
|
|
24918
|
+
code-viewer query agent-help`
|
|
24846
24919
|
}
|
|
24847
24920
|
]
|
|
24848
24921
|
}
|
|
@@ -25057,11 +25130,11 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25057
25130
|
},
|
|
25058
25131
|
{
|
|
25059
25132
|
kind: "paragraph",
|
|
25060
|
-
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 (待ち受けポート)
|
|
25133
|
+
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 を再実行する手順を表示します。"
|
|
25061
25134
|
},
|
|
25062
25135
|
{
|
|
25063
25136
|
kind: "paragraph",
|
|
25064
|
-
text: 'ターミナルからは `code-viewer doctor` で同じレポートを取得できます。AI エージェントや CI からは `--json` で /_doctor と同じ DoctorReport を受け取れます。PATH と別の実行ファイルを使いたい場合は `--bin git=/absolute/path
|
|
25137
|
+
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 ガードに直接使えます。'
|
|
25065
25138
|
},
|
|
25066
25139
|
{
|
|
25067
25140
|
kind: "command",
|
|
@@ -25073,7 +25146,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25073
25146
|
title: "AI / CI 用 doctor JSON",
|
|
25074
25147
|
command: `code-viewer doctor --json
|
|
25075
25148
|
code-viewer doctor --cwd /path/to/repo --port 64160 --json
|
|
25076
|
-
code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --json`
|
|
25149
|
+
code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=/opt/bin/gh --json`
|
|
25077
25150
|
}
|
|
25078
25151
|
]
|
|
25079
25152
|
}
|
|
@@ -25429,7 +25502,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25429
25502
|
skills: {
|
|
25430
25503
|
nav: "スキル登録",
|
|
25431
25504
|
title: "Agent Skill の登録",
|
|
25432
|
-
intro: "このパッケージには
|
|
25505
|
+
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 スキルがコピーされます。",
|
|
25433
25506
|
groups: [
|
|
25434
25507
|
{
|
|
25435
25508
|
title: "スキルをインストールする",
|
|
@@ -25474,12 +25547,16 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25474
25547
|
"コードレビュー、オンボーディング、変更内容の解説で使うこと。"
|
|
25475
25548
|
],
|
|
25476
25549
|
[
|
|
25477
|
-
"
|
|
25478
|
-
"
|
|
25550
|
+
"Work Log タスクをどう扱うか",
|
|
25551
|
+
"journal CLI でタスク一覧取得、作成、claim/done、GitHub Issue 紐づけを行うこと。"
|
|
25479
25552
|
],
|
|
25480
25553
|
[
|
|
25481
|
-
"
|
|
25482
|
-
"
|
|
25554
|
+
"データをどう調査するか",
|
|
25555
|
+
"read-only な query コマンドでスキーマ、SQL、履歴、横断検索を扱うこと。"
|
|
25556
|
+
],
|
|
25557
|
+
[
|
|
25558
|
+
"データ変更をどう検証するか",
|
|
25559
|
+
"snapshot create/list/diff で before/after を比較すること。"
|
|
25483
25560
|
]
|
|
25484
25561
|
]
|
|
25485
25562
|
}
|
|
@@ -25495,7 +25572,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
25495
25572
|
{
|
|
25496
25573
|
kind: "command",
|
|
25497
25574
|
title: "AI 向けガイドを表示",
|
|
25498
|
-
command:
|
|
25575
|
+
command: `code-viewer annotate agent-help
|
|
25576
|
+
code-viewer journal agent-help
|
|
25577
|
+
code-viewer query agent-help`
|
|
25499
25578
|
}
|
|
25500
25579
|
]
|
|
25501
25580
|
}
|
|
@@ -26177,6 +26256,2004 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
26177
26256
|
return { setupHunkExpand };
|
|
26178
26257
|
}
|
|
26179
26258
|
|
|
26259
|
+
// web-src/core/journal.ts
|
|
26260
|
+
var JOURNAL_TASK_STATUSES = [
|
|
26261
|
+
"draft",
|
|
26262
|
+
"todo",
|
|
26263
|
+
"doing",
|
|
26264
|
+
"blocked",
|
|
26265
|
+
"done"
|
|
26266
|
+
];
|
|
26267
|
+
var JOURNAL_TASK_PRIORITIES = ["p0", "p1", "p2", "p3"];
|
|
26268
|
+
var JOURNAL_LABEL_MAX_CHARS = 48;
|
|
26269
|
+
var JOURNAL_MAX_LABELS = 24;
|
|
26270
|
+
var PRIORITY_SCORE = {
|
|
26271
|
+
p0: 0,
|
|
26272
|
+
p1: 1,
|
|
26273
|
+
p2: 2,
|
|
26274
|
+
p3: 3
|
|
26275
|
+
};
|
|
26276
|
+
function isJournalTaskStatus(value) {
|
|
26277
|
+
return JOURNAL_TASK_STATUSES.includes(value);
|
|
26278
|
+
}
|
|
26279
|
+
function todayIsoDate(now = new Date) {
|
|
26280
|
+
const year = now.getFullYear();
|
|
26281
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
26282
|
+
const date = String(now.getDate()).padStart(2, "0");
|
|
26283
|
+
return `${year}-${month}-${date}`;
|
|
26284
|
+
}
|
|
26285
|
+
function taskClaimActive(task, now = Date.now()) {
|
|
26286
|
+
if (!task.claim)
|
|
26287
|
+
return false;
|
|
26288
|
+
const expires = Date.parse(task.claim.lease_expires_at);
|
|
26289
|
+
return Number.isFinite(expires) && expires > now;
|
|
26290
|
+
}
|
|
26291
|
+
function normalizeJournalLabel(value) {
|
|
26292
|
+
if (typeof value !== "string")
|
|
26293
|
+
return null;
|
|
26294
|
+
const label = value.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^\p{L}\p{N}._:-]+/gu, "").slice(0, JOURNAL_LABEL_MAX_CHARS).replace(/^[-_.:]+|[-_.:]+$/g, "");
|
|
26295
|
+
return label || null;
|
|
26296
|
+
}
|
|
26297
|
+
function normalizeJournalLabels(raw) {
|
|
26298
|
+
if (!Array.isArray(raw))
|
|
26299
|
+
return [];
|
|
26300
|
+
const labels = [];
|
|
26301
|
+
const seen = new Set;
|
|
26302
|
+
for (const item of raw) {
|
|
26303
|
+
const label = normalizeJournalLabel(item);
|
|
26304
|
+
if (!label || seen.has(label))
|
|
26305
|
+
continue;
|
|
26306
|
+
seen.add(label);
|
|
26307
|
+
labels.push(label);
|
|
26308
|
+
if (labels.length >= JOURNAL_MAX_LABELS)
|
|
26309
|
+
break;
|
|
26310
|
+
}
|
|
26311
|
+
return labels;
|
|
26312
|
+
}
|
|
26313
|
+
function journalIssueLabel(issueNumber) {
|
|
26314
|
+
return `issue-${issueNumber}`;
|
|
26315
|
+
}
|
|
26316
|
+
function journalIssueRepoLabel(repo) {
|
|
26317
|
+
if (typeof repo !== "string")
|
|
26318
|
+
return null;
|
|
26319
|
+
return normalizeJournalLabel(`repo-${repo.replace(/[\\/]+/g, "-")}`);
|
|
26320
|
+
}
|
|
26321
|
+
function filterJournalTasks(state, filter = {}) {
|
|
26322
|
+
const statuses = Array.isArray(filter.status) ? filter.status : filter.status ? [filter.status] : undefined;
|
|
26323
|
+
const labels = filter.labels?.filter(Boolean) || [];
|
|
26324
|
+
return state.tasks.filter((task) => {
|
|
26325
|
+
if (statuses && !statuses.includes(task.status))
|
|
26326
|
+
return false;
|
|
26327
|
+
if (labels.length && !labels.every((label) => task.labels.includes(label))) {
|
|
26328
|
+
return false;
|
|
26329
|
+
}
|
|
26330
|
+
if (!filter.includeClaimed && taskClaimActive(task, filter.now))
|
|
26331
|
+
return false;
|
|
26332
|
+
return true;
|
|
26333
|
+
});
|
|
26334
|
+
}
|
|
26335
|
+
function selectNextJournalTasks(state, filter = {}, limit = 1) {
|
|
26336
|
+
const status = filter.status || "todo";
|
|
26337
|
+
const indexed = filterJournalTasks(state, { ...filter, status }).map((task, index) => ({ task, index }));
|
|
26338
|
+
indexed.sort((a2, b2) => {
|
|
26339
|
+
const priority = PRIORITY_SCORE[a2.task.priority] - PRIORITY_SCORE[b2.task.priority];
|
|
26340
|
+
if (priority !== 0)
|
|
26341
|
+
return priority;
|
|
26342
|
+
if (a2.index !== b2.index)
|
|
26343
|
+
return a2.index - b2.index;
|
|
26344
|
+
return a2.task.created_at.localeCompare(b2.task.created_at);
|
|
26345
|
+
});
|
|
26346
|
+
return indexed.slice(0, Math.max(0, limit)).map((item) => item.task);
|
|
26347
|
+
}
|
|
26348
|
+
|
|
26349
|
+
// web-src/views/journal-view.ts
|
|
26350
|
+
var GITHUB_ISSUE_INITIAL_LIMIT = 30;
|
|
26351
|
+
var GITHUB_ISSUE_LIMIT_STEP = 30;
|
|
26352
|
+
var GITHUB_ISSUE_LIMIT_MAX = 100;
|
|
26353
|
+
var GITHUB_ISSUE_RATE_LIMIT_RETRY_MS = 15000;
|
|
26354
|
+
var TASK_DRAG_AUTOSCROLL_EDGE_PX = 72;
|
|
26355
|
+
var TASK_DRAG_AUTOSCROLL_MAX_STEP_PX = 22;
|
|
26356
|
+
var TASK_EDITOR_WIDTH_STORAGE_KEY = "code-viewer:journal-task-editor-width";
|
|
26357
|
+
var TASK_EDITOR_DEFAULT_WIDTH = 380;
|
|
26358
|
+
var TASK_EDITOR_MIN_WIDTH = 320;
|
|
26359
|
+
var TASK_EDITOR_MAX_WIDTH = 760;
|
|
26360
|
+
function isJournalRoute(route) {
|
|
26361
|
+
return route.screen === "journal";
|
|
26362
|
+
}
|
|
26363
|
+
function parseGithubLabels(raw) {
|
|
26364
|
+
const labels = [];
|
|
26365
|
+
const seen = new Set;
|
|
26366
|
+
for (const item of raw.split(",")) {
|
|
26367
|
+
const label = item.trim().slice(0, 200);
|
|
26368
|
+
if (!label || seen.has(label))
|
|
26369
|
+
continue;
|
|
26370
|
+
seen.add(label);
|
|
26371
|
+
labels.push(label);
|
|
26372
|
+
}
|
|
26373
|
+
return labels;
|
|
26374
|
+
}
|
|
26375
|
+
function taskDateRange(task) {
|
|
26376
|
+
const first = task.source_date || task.due_date;
|
|
26377
|
+
const last = task.due_date || task.source_date;
|
|
26378
|
+
if (!first || !last)
|
|
26379
|
+
return null;
|
|
26380
|
+
return first <= last ? { start: first, end: last } : { start: last, end: first };
|
|
26381
|
+
}
|
|
26382
|
+
function taskCoversDate(task, date) {
|
|
26383
|
+
const range = taskDateRange(task);
|
|
26384
|
+
return !!range && range.start <= date && date <= range.end;
|
|
26385
|
+
}
|
|
26386
|
+
function monthKey(date) {
|
|
26387
|
+
return date.slice(0, 7);
|
|
26388
|
+
}
|
|
26389
|
+
function monthTitle(date, locale) {
|
|
26390
|
+
const parsed = new Date(`${monthKey(date)}-01T00:00:00`);
|
|
26391
|
+
return parsed.toLocaleString(locale, { month: "long", year: "numeric" });
|
|
26392
|
+
}
|
|
26393
|
+
function offsetMonth(date, delta) {
|
|
26394
|
+
const parsed = new Date(`${monthKey(date)}-01T00:00:00`);
|
|
26395
|
+
parsed.setMonth(parsed.getMonth() + delta);
|
|
26396
|
+
return `${todayIsoDate(parsed).slice(0, 7)}-01`;
|
|
26397
|
+
}
|
|
26398
|
+
function labelChip(label) {
|
|
26399
|
+
const chip = document.createElement("span");
|
|
26400
|
+
chip.className = "journal-label-chip";
|
|
26401
|
+
chip.textContent = label;
|
|
26402
|
+
return chip;
|
|
26403
|
+
}
|
|
26404
|
+
function taskField(labelText, control) {
|
|
26405
|
+
const field = document.createElement("label");
|
|
26406
|
+
field.className = "journal-field";
|
|
26407
|
+
const caption = document.createElement("span");
|
|
26408
|
+
caption.className = "journal-field-label";
|
|
26409
|
+
caption.textContent = labelText;
|
|
26410
|
+
field.append(caption, control);
|
|
26411
|
+
return field;
|
|
26412
|
+
}
|
|
26413
|
+
function createLabelEditor(initialLabels, placeholder, suggestionLabels = [], removeLabelText = (label) => `Remove ${label}`) {
|
|
26414
|
+
const root = document.createElement("div");
|
|
26415
|
+
root.className = "journal-label-editor";
|
|
26416
|
+
const input = document.createElement("input");
|
|
26417
|
+
input.className = "journal-label-editor-input";
|
|
26418
|
+
input.type = "text";
|
|
26419
|
+
input.autocomplete = "off";
|
|
26420
|
+
const labels = normalizeJournalLabels(initialLabels);
|
|
26421
|
+
const suggestions = normalizeJournalLabels(suggestionLabels);
|
|
26422
|
+
const menu = document.createElement("div");
|
|
26423
|
+
menu.className = "journal-label-editor-suggestions";
|
|
26424
|
+
menu.hidden = true;
|
|
26425
|
+
const quick = document.createElement("div");
|
|
26426
|
+
quick.className = "journal-label-editor-quick";
|
|
26427
|
+
quick.hidden = true;
|
|
26428
|
+
let highlightedSuggestion = -1;
|
|
26429
|
+
const suggestionItems = () => {
|
|
26430
|
+
const raw = input.value.trim().toLowerCase();
|
|
26431
|
+
const normalized = normalizeJournalLabel(raw) || raw;
|
|
26432
|
+
return suggestions.filter((label) => !labels.includes(label) && (!normalized || label.includes(normalized))).slice(0, 8);
|
|
26433
|
+
};
|
|
26434
|
+
const renderSuggestions = () => {
|
|
26435
|
+
const items = suggestionItems();
|
|
26436
|
+
const hasQuery = !!(normalizeJournalLabel(input.value) || input.value.trim());
|
|
26437
|
+
menu.replaceChildren();
|
|
26438
|
+
if (!items.length || !hasQuery || document.activeElement !== input) {
|
|
26439
|
+
menu.hidden = true;
|
|
26440
|
+
highlightedSuggestion = -1;
|
|
26441
|
+
return;
|
|
26442
|
+
}
|
|
26443
|
+
menu.hidden = false;
|
|
26444
|
+
if (highlightedSuggestion >= items.length)
|
|
26445
|
+
highlightedSuggestion = 0;
|
|
26446
|
+
items.forEach((label, index) => {
|
|
26447
|
+
const button = document.createElement("button");
|
|
26448
|
+
button.type = "button";
|
|
26449
|
+
button.className = "journal-label-editor-suggestion";
|
|
26450
|
+
button.classList.toggle("active", index === highlightedSuggestion);
|
|
26451
|
+
button.textContent = label;
|
|
26452
|
+
button.addEventListener("mousedown", (event) => {
|
|
26453
|
+
event.preventDefault();
|
|
26454
|
+
commit(label);
|
|
26455
|
+
});
|
|
26456
|
+
menu.appendChild(button);
|
|
26457
|
+
});
|
|
26458
|
+
};
|
|
26459
|
+
const renderQuickSuggestions = () => {
|
|
26460
|
+
const items = suggestionItems().slice(0, 6);
|
|
26461
|
+
quick.replaceChildren();
|
|
26462
|
+
quick.hidden = !items.length;
|
|
26463
|
+
for (const label of items) {
|
|
26464
|
+
const button = document.createElement("button");
|
|
26465
|
+
button.type = "button";
|
|
26466
|
+
button.className = "journal-label-editor-quick-chip";
|
|
26467
|
+
button.textContent = label;
|
|
26468
|
+
button.addEventListener("click", (event) => {
|
|
26469
|
+
event.preventDefault();
|
|
26470
|
+
event.stopPropagation();
|
|
26471
|
+
commit(label);
|
|
26472
|
+
});
|
|
26473
|
+
quick.appendChild(button);
|
|
26474
|
+
}
|
|
26475
|
+
};
|
|
26476
|
+
const render = () => {
|
|
26477
|
+
root.replaceChildren();
|
|
26478
|
+
for (const label of labels) {
|
|
26479
|
+
const chip = document.createElement("span");
|
|
26480
|
+
chip.className = "journal-label-editor-chip";
|
|
26481
|
+
const name = document.createElement("span");
|
|
26482
|
+
name.textContent = label;
|
|
26483
|
+
const remove = document.createElement("button");
|
|
26484
|
+
remove.type = "button";
|
|
26485
|
+
remove.className = "journal-label-editor-remove";
|
|
26486
|
+
remove.setAttribute("aria-label", removeLabelText(label));
|
|
26487
|
+
remove.innerHTML = iconSvg("octicon-x", X_16_PATH);
|
|
26488
|
+
remove.addEventListener("click", (event) => {
|
|
26489
|
+
event.stopPropagation();
|
|
26490
|
+
const index = labels.indexOf(label);
|
|
26491
|
+
if (index >= 0)
|
|
26492
|
+
labels.splice(index, 1);
|
|
26493
|
+
render();
|
|
26494
|
+
input.focus();
|
|
26495
|
+
});
|
|
26496
|
+
chip.append(name, remove);
|
|
26497
|
+
root.appendChild(chip);
|
|
26498
|
+
}
|
|
26499
|
+
input.placeholder = labels.length ? "" : placeholder;
|
|
26500
|
+
root.append(input, quick, menu);
|
|
26501
|
+
renderQuickSuggestions();
|
|
26502
|
+
renderSuggestions();
|
|
26503
|
+
};
|
|
26504
|
+
const commit = (raw) => {
|
|
26505
|
+
const label = normalizeJournalLabel(raw);
|
|
26506
|
+
input.value = "";
|
|
26507
|
+
if (!label || labels.includes(label))
|
|
26508
|
+
return false;
|
|
26509
|
+
labels.push(label);
|
|
26510
|
+
highlightedSuggestion = -1;
|
|
26511
|
+
render();
|
|
26512
|
+
input.focus();
|
|
26513
|
+
return true;
|
|
26514
|
+
};
|
|
26515
|
+
input.addEventListener("keydown", (event) => {
|
|
26516
|
+
const items = suggestionItems();
|
|
26517
|
+
if (event.key === "ArrowDown" && items.length) {
|
|
26518
|
+
event.preventDefault();
|
|
26519
|
+
highlightedSuggestion = (highlightedSuggestion + 1) % items.length;
|
|
26520
|
+
renderSuggestions();
|
|
26521
|
+
return;
|
|
26522
|
+
}
|
|
26523
|
+
if (event.key === "ArrowUp" && items.length) {
|
|
26524
|
+
event.preventDefault();
|
|
26525
|
+
highlightedSuggestion = (highlightedSuggestion - 1 + items.length) % items.length;
|
|
26526
|
+
renderSuggestions();
|
|
26527
|
+
return;
|
|
26528
|
+
}
|
|
26529
|
+
const value = input.value.trim();
|
|
26530
|
+
if (event.key === "Enter" || event.key === ",") {
|
|
26531
|
+
event.preventDefault();
|
|
26532
|
+
commit(event.key === "Enter" && highlightedSuggestion >= 0 && items[highlightedSuggestion] ? items[highlightedSuggestion] : value);
|
|
26533
|
+
return;
|
|
26534
|
+
}
|
|
26535
|
+
if (event.key === "Tab" && value) {
|
|
26536
|
+
event.preventDefault();
|
|
26537
|
+
commit(value);
|
|
26538
|
+
return;
|
|
26539
|
+
}
|
|
26540
|
+
if (event.key === "Backspace" && !value && labels.length) {
|
|
26541
|
+
labels.pop();
|
|
26542
|
+
render();
|
|
26543
|
+
input.focus();
|
|
26544
|
+
}
|
|
26545
|
+
});
|
|
26546
|
+
input.addEventListener("input", () => {
|
|
26547
|
+
highlightedSuggestion = -1;
|
|
26548
|
+
renderQuickSuggestions();
|
|
26549
|
+
renderSuggestions();
|
|
26550
|
+
});
|
|
26551
|
+
input.addEventListener("focus", renderSuggestions);
|
|
26552
|
+
input.addEventListener("paste", (event) => {
|
|
26553
|
+
const pasted = event.clipboardData?.getData("text") || "";
|
|
26554
|
+
if (!/[,\n]/.test(pasted))
|
|
26555
|
+
return;
|
|
26556
|
+
event.preventDefault();
|
|
26557
|
+
for (const item of pasted.split(/[,\n]+/))
|
|
26558
|
+
commit(item);
|
|
26559
|
+
});
|
|
26560
|
+
input.addEventListener("blur", () => {
|
|
26561
|
+
if (input.value.trim())
|
|
26562
|
+
commit(input.value);
|
|
26563
|
+
window.setTimeout(() => {
|
|
26564
|
+
menu.hidden = true;
|
|
26565
|
+
highlightedSuggestion = -1;
|
|
26566
|
+
}, 120);
|
|
26567
|
+
});
|
|
26568
|
+
root.addEventListener("click", () => input.focus());
|
|
26569
|
+
render();
|
|
26570
|
+
return {
|
|
26571
|
+
element: root,
|
|
26572
|
+
getLabels: () => [...labels]
|
|
26573
|
+
};
|
|
26574
|
+
}
|
|
26575
|
+
function setButtonBusy(button, busy) {
|
|
26576
|
+
button.disabled = busy;
|
|
26577
|
+
button.setAttribute("aria-busy", busy ? "true" : "false");
|
|
26578
|
+
}
|
|
26579
|
+
function createJournalView(deps) {
|
|
26580
|
+
const root = document.createElement("section");
|
|
26581
|
+
root.className = "journal-page";
|
|
26582
|
+
root.hidden = true;
|
|
26583
|
+
root.setAttribute("aria-label", deps.getText().ariaLabel);
|
|
26584
|
+
let mounted = false;
|
|
26585
|
+
let lifecycle = 0;
|
|
26586
|
+
let data = null;
|
|
26587
|
+
let activeTab = "journal";
|
|
26588
|
+
let selectedDate = todayIsoDate();
|
|
26589
|
+
let selectedEntryId = "";
|
|
26590
|
+
let creatingEntry = false;
|
|
26591
|
+
let selectedTaskId = "";
|
|
26592
|
+
let labelFilter = "";
|
|
26593
|
+
let editorMode = "split";
|
|
26594
|
+
let message = "";
|
|
26595
|
+
let draggingTaskId = "";
|
|
26596
|
+
let draggingGithubIssueNumber = null;
|
|
26597
|
+
let githubIssues = [];
|
|
26598
|
+
let githubIssuesLoading = false;
|
|
26599
|
+
let githubIssuesLoaded = false;
|
|
26600
|
+
let githubIssuesError = "";
|
|
26601
|
+
let githubRepoFilter = "";
|
|
26602
|
+
let githubLabelFilter = "";
|
|
26603
|
+
let githubSearchFilter = "";
|
|
26604
|
+
let githubStateFilter = "open";
|
|
26605
|
+
let githubIssueLimit = GITHUB_ISSUE_INITIAL_LIMIT;
|
|
26606
|
+
let githubIssuesRetryAt = 0;
|
|
26607
|
+
let githubIssueGeneration = 0;
|
|
26608
|
+
let githubIssuesAutoRequested = false;
|
|
26609
|
+
let githubInboxOpen = false;
|
|
26610
|
+
let suppressTaskClick = false;
|
|
26611
|
+
let pendingTaskEditorFocus = false;
|
|
26612
|
+
let pendingTaskEditorReveal = false;
|
|
26613
|
+
let lastTaskDropTarget = null;
|
|
26614
|
+
let nativeTaskDragActive = false;
|
|
26615
|
+
let taskEditorResizePointerId = null;
|
|
26616
|
+
let taskEditorResizeStartX = 0;
|
|
26617
|
+
let taskEditorResizeStartWidth = TASK_EDITOR_DEFAULT_WIDTH;
|
|
26618
|
+
let taskEditorFocusRequest = 0;
|
|
26619
|
+
let taskDragAutoScrollFrame = 0;
|
|
26620
|
+
let taskDragAutoScrollX = 0;
|
|
26621
|
+
let taskDragAutoScrollY = 0;
|
|
26622
|
+
let pointerDrag = null;
|
|
26623
|
+
function text2() {
|
|
26624
|
+
return deps.getText();
|
|
26625
|
+
}
|
|
26626
|
+
function selectedEntry() {
|
|
26627
|
+
if (!data)
|
|
26628
|
+
return null;
|
|
26629
|
+
return data.journal.entries.find((entry) => entry.id === selectedEntryId) || null;
|
|
26630
|
+
}
|
|
26631
|
+
function selectedTask() {
|
|
26632
|
+
if (!data)
|
|
26633
|
+
return null;
|
|
26634
|
+
return data.tasks.tasks.find((task) => task.id === selectedTaskId) || null;
|
|
26635
|
+
}
|
|
26636
|
+
function journalEditorHasFocus() {
|
|
26637
|
+
const active = document.activeElement;
|
|
26638
|
+
return !!(active && root.contains(active) && active.closest(".journal-editor, .journal-task-editor"));
|
|
26639
|
+
}
|
|
26640
|
+
function labelSuggestions() {
|
|
26641
|
+
return (data?.labels || []).filter((label) => !/^issue-\d+$/.test(label) && !label.startsWith("repo-"));
|
|
26642
|
+
}
|
|
26643
|
+
function taskEditorMaxWidth() {
|
|
26644
|
+
const layoutWidth = root.querySelector(".journal-tasks-layout")?.clientWidth || window.innerWidth;
|
|
26645
|
+
if (layoutWidth <= 1100)
|
|
26646
|
+
return TASK_EDITOR_MAX_WIDTH;
|
|
26647
|
+
return Math.max(TASK_EDITOR_MIN_WIDTH, Math.min(TASK_EDITOR_MAX_WIDTH, layoutWidth - 560));
|
|
26648
|
+
}
|
|
26649
|
+
function clampTaskEditorWidth(width) {
|
|
26650
|
+
return Math.max(TASK_EDITOR_MIN_WIDTH, Math.min(taskEditorMaxWidth(), Math.round(width)));
|
|
26651
|
+
}
|
|
26652
|
+
function applyTaskEditorWidth(width, persist = true) {
|
|
26653
|
+
const clamped = clampTaskEditorWidth(width);
|
|
26654
|
+
document.documentElement.style.setProperty("--journal-task-editor-w", `${clamped}px`);
|
|
26655
|
+
if (!persist)
|
|
26656
|
+
return;
|
|
26657
|
+
try {
|
|
26658
|
+
window.localStorage.setItem(TASK_EDITOR_WIDTH_STORAGE_KEY, String(clamped));
|
|
26659
|
+
} catch {}
|
|
26660
|
+
}
|
|
26661
|
+
function restoreTaskEditorWidth() {
|
|
26662
|
+
let width = TASK_EDITOR_DEFAULT_WIDTH;
|
|
26663
|
+
try {
|
|
26664
|
+
const raw = window.localStorage.getItem(TASK_EDITOR_WIDTH_STORAGE_KEY);
|
|
26665
|
+
const parsed = raw ? Number(raw) : NaN;
|
|
26666
|
+
if (Number.isFinite(parsed))
|
|
26667
|
+
width = parsed;
|
|
26668
|
+
} catch {
|
|
26669
|
+
width = TASK_EDITOR_DEFAULT_WIDTH;
|
|
26670
|
+
}
|
|
26671
|
+
applyTaskEditorWidth(width, false);
|
|
26672
|
+
}
|
|
26673
|
+
function startTaskEditorResize(event, panel) {
|
|
26674
|
+
if (event.button !== 0)
|
|
26675
|
+
return;
|
|
26676
|
+
taskEditorResizePointerId = event.pointerId;
|
|
26677
|
+
taskEditorResizeStartX = event.clientX;
|
|
26678
|
+
taskEditorResizeStartWidth = panel.getBoundingClientRect().width;
|
|
26679
|
+
panel.setPointerCapture?.(event.pointerId);
|
|
26680
|
+
document.body.classList.add("journal-task-editor-resizing");
|
|
26681
|
+
event.preventDefault();
|
|
26682
|
+
}
|
|
26683
|
+
function handleTaskEditorResizeMove(event) {
|
|
26684
|
+
if (taskEditorResizePointerId !== event.pointerId)
|
|
26685
|
+
return;
|
|
26686
|
+
applyTaskEditorWidth(taskEditorResizeStartWidth - (event.clientX - taskEditorResizeStartX), false);
|
|
26687
|
+
}
|
|
26688
|
+
function finishTaskEditorResize(event) {
|
|
26689
|
+
if (taskEditorResizePointerId !== event.pointerId)
|
|
26690
|
+
return;
|
|
26691
|
+
taskEditorResizePointerId = null;
|
|
26692
|
+
document.body.classList.remove("journal-task-editor-resizing");
|
|
26693
|
+
const panel = root.querySelector(".journal-task-editor");
|
|
26694
|
+
if (panel)
|
|
26695
|
+
applyTaskEditorWidth(panel.getBoundingClientRect().width);
|
|
26696
|
+
}
|
|
26697
|
+
function revealTaskEditorIfNeeded(editor) {
|
|
26698
|
+
const layout = root.querySelector(".journal-tasks-layout");
|
|
26699
|
+
if (!layout || layout.scrollHeight <= layout.clientHeight + 1)
|
|
26700
|
+
return;
|
|
26701
|
+
const layoutRect = layout.getBoundingClientRect();
|
|
26702
|
+
const editorRect = editor.getBoundingClientRect();
|
|
26703
|
+
const topPadding = 8;
|
|
26704
|
+
const bottomPadding = 8;
|
|
26705
|
+
const visibleTop = layoutRect.top + topPadding;
|
|
26706
|
+
const visibleBottom = layoutRect.bottom - bottomPadding;
|
|
26707
|
+
const minVisibleTopGap = Math.min(160, editorRect.height);
|
|
26708
|
+
if (editorRect.top >= visibleTop && editorRect.top <= visibleBottom - minVisibleTopGap)
|
|
26709
|
+
return;
|
|
26710
|
+
layout.scrollTop += editorRect.top - visibleTop;
|
|
26711
|
+
}
|
|
26712
|
+
function scheduleTaskEditorInteraction(options = {}) {
|
|
26713
|
+
if (options.focusEditor)
|
|
26714
|
+
pendingTaskEditorFocus = true;
|
|
26715
|
+
if (options.revealEditor)
|
|
26716
|
+
pendingTaskEditorReveal = true;
|
|
26717
|
+
const request = ++taskEditorFocusRequest;
|
|
26718
|
+
const interact = (final = false) => {
|
|
26719
|
+
if (request !== taskEditorFocusRequest)
|
|
26720
|
+
return;
|
|
26721
|
+
const editor = root.querySelector(".journal-task-editor");
|
|
26722
|
+
const input = editor?.querySelector(".journal-title-input") || null;
|
|
26723
|
+
if (pendingTaskEditorReveal && editor) {
|
|
26724
|
+
revealTaskEditorIfNeeded(editor);
|
|
26725
|
+
pendingTaskEditorReveal = false;
|
|
26726
|
+
}
|
|
26727
|
+
if (pendingTaskEditorFocus && input)
|
|
26728
|
+
input.focus({ preventScroll: true });
|
|
26729
|
+
if (final && request === taskEditorFocusRequest) {
|
|
26730
|
+
pendingTaskEditorFocus = false;
|
|
26731
|
+
pendingTaskEditorReveal = false;
|
|
26732
|
+
}
|
|
26733
|
+
};
|
|
26734
|
+
window.requestAnimationFrame(() => interact());
|
|
26735
|
+
window.setTimeout(() => interact(), 50);
|
|
26736
|
+
window.setTimeout(() => interact(true), 250);
|
|
26737
|
+
}
|
|
26738
|
+
function selectTask(taskId, options = {}) {
|
|
26739
|
+
activeTab = "tasks";
|
|
26740
|
+
selectedTaskId = taskId;
|
|
26741
|
+
setRoute({ tab: "tasks", task: taskId });
|
|
26742
|
+
render();
|
|
26743
|
+
if (options.focusEditor || options.revealEditor)
|
|
26744
|
+
scheduleTaskEditorInteraction(options);
|
|
26745
|
+
}
|
|
26746
|
+
function routeFor(next) {
|
|
26747
|
+
return {
|
|
26748
|
+
screen: "journal",
|
|
26749
|
+
tab: activeTab,
|
|
26750
|
+
date: selectedDate,
|
|
26751
|
+
...labelFilter ? { label: labelFilter } : {},
|
|
26752
|
+
...selectedTaskId ? { task: selectedTaskId } : {},
|
|
26753
|
+
...next,
|
|
26754
|
+
range: deps.currentRange()
|
|
26755
|
+
};
|
|
26756
|
+
}
|
|
26757
|
+
function applyRoute(route = deps.getRoute()) {
|
|
26758
|
+
if (!isJournalRoute(route))
|
|
26759
|
+
return;
|
|
26760
|
+
activeTab = route.tab || activeTab || "journal";
|
|
26761
|
+
selectedDate = route.date || selectedDate || todayIsoDate();
|
|
26762
|
+
labelFilter = route.label || "";
|
|
26763
|
+
selectedTaskId = route.task || "";
|
|
26764
|
+
}
|
|
26765
|
+
function mount() {
|
|
26766
|
+
if (mounted)
|
|
26767
|
+
return;
|
|
26768
|
+
const content = document.getElementById("content");
|
|
26769
|
+
if (!content)
|
|
26770
|
+
return;
|
|
26771
|
+
document.getElementById("diff")?.setAttribute("hidden", "true");
|
|
26772
|
+
document.getElementById("empty")?.classList.add("hidden");
|
|
26773
|
+
document.getElementById("history-commit-info")?.setAttribute("hidden", "true");
|
|
26774
|
+
root.hidden = false;
|
|
26775
|
+
content.appendChild(root);
|
|
26776
|
+
mounted = true;
|
|
26777
|
+
document.body.classList.add("gdp-journal-page");
|
|
26778
|
+
deps.setPageMode();
|
|
26779
|
+
deps.syncHeaderMenu();
|
|
26780
|
+
}
|
|
26781
|
+
function suspend() {
|
|
26782
|
+
lifecycle++;
|
|
26783
|
+
if (!mounted) {
|
|
26784
|
+
root.remove();
|
|
26785
|
+
root.hidden = false;
|
|
26786
|
+
document.body.classList.remove("gdp-journal-page");
|
|
26787
|
+
document.getElementById("diff")?.removeAttribute("hidden");
|
|
26788
|
+
return;
|
|
26789
|
+
}
|
|
26790
|
+
root.remove();
|
|
26791
|
+
root.hidden = false;
|
|
26792
|
+
document.body.classList.remove("gdp-journal-page");
|
|
26793
|
+
document.getElementById("diff")?.removeAttribute("hidden");
|
|
26794
|
+
mounted = false;
|
|
26795
|
+
}
|
|
26796
|
+
async function requestJournal(action, body, afterResponse) {
|
|
26797
|
+
deps.setStatus("refreshing");
|
|
26798
|
+
const res = await deps.trackLoad(fetch("/_journal", {
|
|
26799
|
+
method: "POST",
|
|
26800
|
+
headers: {
|
|
26801
|
+
"Content-Type": "application/json",
|
|
26802
|
+
"X-Code-Viewer-Action": "1"
|
|
26803
|
+
},
|
|
26804
|
+
body: JSON.stringify({ action, ...body })
|
|
26805
|
+
}));
|
|
26806
|
+
if (!res.ok)
|
|
26807
|
+
throw new Error(await res.text());
|
|
26808
|
+
const response = await res.json();
|
|
26809
|
+
afterResponse?.(response);
|
|
26810
|
+
await refresh();
|
|
26811
|
+
return response;
|
|
26812
|
+
}
|
|
26813
|
+
async function refresh() {
|
|
26814
|
+
const seq = ++lifecycle;
|
|
26815
|
+
message = data ? "" : text2().loading;
|
|
26816
|
+
render();
|
|
26817
|
+
deps.setStatus("refreshing");
|
|
26818
|
+
try {
|
|
26819
|
+
const next = await deps.trackLoad(fetch("/_journal").then(async (res) => {
|
|
26820
|
+
if (!res.ok)
|
|
26821
|
+
throw new Error(await res.text());
|
|
26822
|
+
return await res.json();
|
|
26823
|
+
}));
|
|
26824
|
+
if (seq !== lifecycle)
|
|
26825
|
+
return;
|
|
26826
|
+
data = next;
|
|
26827
|
+
if (selectedEntryId && !next.journal.entries.some((entry) => entry.id === selectedEntryId)) {
|
|
26828
|
+
selectedEntryId = "";
|
|
26829
|
+
}
|
|
26830
|
+
if (!creatingEntry && !selectedEntryId) {
|
|
26831
|
+
selectedEntryId = next.journal.entries.find((entry) => entry.date === selectedDate)?.id || "";
|
|
26832
|
+
}
|
|
26833
|
+
if (selectedTaskId && !next.tasks.tasks.some((task) => task.id === selectedTaskId)) {
|
|
26834
|
+
selectedTaskId = "";
|
|
26835
|
+
}
|
|
26836
|
+
message = "";
|
|
26837
|
+
deps.setStatus("live");
|
|
26838
|
+
render();
|
|
26839
|
+
} catch (error2) {
|
|
26840
|
+
if (seq !== lifecycle)
|
|
26841
|
+
return;
|
|
26842
|
+
message = error2 instanceof Error ? error2.message : text2().loadFailed;
|
|
26843
|
+
deps.setStatus("error");
|
|
26844
|
+
render();
|
|
26845
|
+
}
|
|
26846
|
+
}
|
|
26847
|
+
function setRoute(next, replace2 = true) {
|
|
26848
|
+
deps.setRoute(routeFor(next), replace2);
|
|
26849
|
+
}
|
|
26850
|
+
function renderHeader() {
|
|
26851
|
+
const header = document.createElement("div");
|
|
26852
|
+
header.className = "journal-header";
|
|
26853
|
+
const title = document.createElement("div");
|
|
26854
|
+
title.className = "journal-title";
|
|
26855
|
+
title.textContent = text2().title;
|
|
26856
|
+
const tabs = document.createElement("div");
|
|
26857
|
+
tabs.className = "seg journal-tabs";
|
|
26858
|
+
tabs.setAttribute("role", "tablist");
|
|
26859
|
+
for (const tab of ["journal", "tasks"]) {
|
|
26860
|
+
const button = document.createElement("button");
|
|
26861
|
+
button.type = "button";
|
|
26862
|
+
button.classList.toggle("active", activeTab === tab);
|
|
26863
|
+
button.textContent = text2().tabs[tab];
|
|
26864
|
+
button.addEventListener("click", () => {
|
|
26865
|
+
activeTab = tab;
|
|
26866
|
+
setRoute({ tab });
|
|
26867
|
+
});
|
|
26868
|
+
tabs.appendChild(button);
|
|
26869
|
+
}
|
|
26870
|
+
const refreshButton = document.createElement("button");
|
|
26871
|
+
refreshButton.type = "button";
|
|
26872
|
+
refreshButton.className = "global-icon-action journal-icon-action";
|
|
26873
|
+
refreshButton.title = text2().refresh;
|
|
26874
|
+
refreshButton.setAttribute("aria-label", text2().refresh);
|
|
26875
|
+
refreshButton.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
|
|
26876
|
+
refreshButton.addEventListener("click", () => void refresh());
|
|
26877
|
+
const status = document.createElement("span");
|
|
26878
|
+
status.className = "journal-status";
|
|
26879
|
+
status.textContent = message;
|
|
26880
|
+
status.hidden = !message;
|
|
26881
|
+
status.setAttribute("role", "status");
|
|
26882
|
+
header.append(title, tabs, refreshButton, status);
|
|
26883
|
+
return header;
|
|
26884
|
+
}
|
|
26885
|
+
function renderCalendar() {
|
|
26886
|
+
const panel = document.createElement("aside");
|
|
26887
|
+
panel.className = "journal-calendar";
|
|
26888
|
+
const head = document.createElement("div");
|
|
26889
|
+
head.className = "journal-calendar-head";
|
|
26890
|
+
const prev = document.createElement("button");
|
|
26891
|
+
prev.type = "button";
|
|
26892
|
+
prev.textContent = "<";
|
|
26893
|
+
prev.setAttribute("aria-label", text2().previousMonth);
|
|
26894
|
+
const next = document.createElement("button");
|
|
26895
|
+
next.type = "button";
|
|
26896
|
+
next.textContent = ">";
|
|
26897
|
+
next.setAttribute("aria-label", text2().nextMonth);
|
|
26898
|
+
const title = document.createElement("strong");
|
|
26899
|
+
title.textContent = monthTitle(selectedDate, text2().locale);
|
|
26900
|
+
prev.addEventListener("click", () => {
|
|
26901
|
+
selectedDate = offsetMonth(selectedDate, -1);
|
|
26902
|
+
selectedEntryId = "";
|
|
26903
|
+
creatingEntry = false;
|
|
26904
|
+
setRoute({ date: selectedDate });
|
|
26905
|
+
});
|
|
26906
|
+
next.addEventListener("click", () => {
|
|
26907
|
+
selectedDate = offsetMonth(selectedDate, 1);
|
|
26908
|
+
selectedEntryId = "";
|
|
26909
|
+
creatingEntry = false;
|
|
26910
|
+
setRoute({ date: selectedDate });
|
|
26911
|
+
});
|
|
26912
|
+
head.append(prev, title, next);
|
|
26913
|
+
const days = document.createElement("div");
|
|
26914
|
+
days.className = "journal-calendar-grid";
|
|
26915
|
+
for (const label of text2().weekDays) {
|
|
26916
|
+
const day = document.createElement("span");
|
|
26917
|
+
day.className = "journal-weekday";
|
|
26918
|
+
day.textContent = label;
|
|
26919
|
+
days.appendChild(day);
|
|
26920
|
+
}
|
|
26921
|
+
const monthStart = new Date(`${monthKey(selectedDate)}-01T00:00:00`);
|
|
26922
|
+
const first = new Date(monthStart);
|
|
26923
|
+
first.setDate(first.getDate() - first.getDay());
|
|
26924
|
+
const entryDates = new Set((data?.journal.entries || []).map((entry) => entry.date));
|
|
26925
|
+
const monthTasks = (data?.tasks.tasks || []).filter((task) => {
|
|
26926
|
+
const range = taskDateRange(task);
|
|
26927
|
+
return !!range && range.end >= todayIsoDate(first) && range.start <= todayIsoDate(new Date(first.getTime() + 41 * 24 * 60 * 60 * 1000));
|
|
26928
|
+
}).slice(0, 60);
|
|
26929
|
+
const today = todayIsoDate();
|
|
26930
|
+
for (let i2 = 0;i2 < 42; i2++) {
|
|
26931
|
+
const current = new Date(first);
|
|
26932
|
+
current.setDate(first.getDate() + i2);
|
|
26933
|
+
const value = todayIsoDate(current);
|
|
26934
|
+
const button = document.createElement("button");
|
|
26935
|
+
button.type = "button";
|
|
26936
|
+
button.className = "journal-day-button";
|
|
26937
|
+
button.classList.toggle("muted", monthKey(value) !== monthKey(selectedDate));
|
|
26938
|
+
button.classList.toggle("active", value === selectedDate);
|
|
26939
|
+
button.classList.toggle("today", value === today);
|
|
26940
|
+
button.classList.toggle("has-entry", entryDates.has(value));
|
|
26941
|
+
const number = document.createElement("span");
|
|
26942
|
+
number.className = "journal-day-number";
|
|
26943
|
+
number.textContent = String(current.getDate());
|
|
26944
|
+
const bars = document.createElement("span");
|
|
26945
|
+
bars.className = "journal-day-ranges";
|
|
26946
|
+
const tasksForDay = monthTasks.filter((item) => taskCoversDate(item, value));
|
|
26947
|
+
for (const task of tasksForDay.slice(0, 3)) {
|
|
26948
|
+
const range = taskDateRange(task);
|
|
26949
|
+
if (!range)
|
|
26950
|
+
continue;
|
|
26951
|
+
const bar = document.createElement("span");
|
|
26952
|
+
bar.className = `journal-day-range journal-range-${task.priority}`;
|
|
26953
|
+
bar.classList.toggle("start", value === range.start);
|
|
26954
|
+
bar.classList.toggle("end", value === range.end);
|
|
26955
|
+
bar.classList.toggle("single", value === range.start && value === range.end);
|
|
26956
|
+
if (value === range.start)
|
|
26957
|
+
bar.textContent = task.title;
|
|
26958
|
+
bar.title = `${task.title} (${range.start} - ${range.end})`;
|
|
26959
|
+
bars.appendChild(bar);
|
|
26960
|
+
}
|
|
26961
|
+
if (tasksForDay.length > 3) {
|
|
26962
|
+
const more = document.createElement("span");
|
|
26963
|
+
more.className = "journal-day-range-more";
|
|
26964
|
+
more.textContent = text2().moreTasks(tasksForDay.length - 3);
|
|
26965
|
+
more.title = tasksForDay.slice(3).map((task) => task.title).join(`
|
|
26966
|
+
`);
|
|
26967
|
+
bars.appendChild(more);
|
|
26968
|
+
}
|
|
26969
|
+
button.append(number, bars);
|
|
26970
|
+
button.addEventListener("click", () => {
|
|
26971
|
+
selectedDate = value;
|
|
26972
|
+
creatingEntry = false;
|
|
26973
|
+
selectedEntryId = data?.journal.entries.find((entry) => entry.date === value)?.id || "";
|
|
26974
|
+
setRoute({ date: value, tab: "journal" });
|
|
26975
|
+
});
|
|
26976
|
+
days.appendChild(button);
|
|
26977
|
+
}
|
|
26978
|
+
panel.append(head, days);
|
|
26979
|
+
return panel;
|
|
26980
|
+
}
|
|
26981
|
+
function renderJournalEntryList(entries) {
|
|
26982
|
+
const list2 = document.createElement("div");
|
|
26983
|
+
list2.className = "journal-entry-list";
|
|
26984
|
+
if (!entries.length) {
|
|
26985
|
+
const empty = document.createElement("div");
|
|
26986
|
+
empty.className = "journal-empty";
|
|
26987
|
+
empty.textContent = text2().noEntries;
|
|
26988
|
+
list2.appendChild(empty);
|
|
26989
|
+
return list2;
|
|
26990
|
+
}
|
|
26991
|
+
for (const entry of entries) {
|
|
26992
|
+
const row = document.createElement("button");
|
|
26993
|
+
row.type = "button";
|
|
26994
|
+
row.className = "journal-entry-row";
|
|
26995
|
+
row.classList.toggle("active", entry.id === selectedEntryId);
|
|
26996
|
+
row.textContent = entry.title || entry.body.split(`
|
|
26997
|
+
`)[0] || entry.date;
|
|
26998
|
+
row.addEventListener("click", () => {
|
|
26999
|
+
selectedEntryId = entry.id;
|
|
27000
|
+
creatingEntry = false;
|
|
27001
|
+
render();
|
|
27002
|
+
});
|
|
27003
|
+
list2.appendChild(row);
|
|
27004
|
+
}
|
|
27005
|
+
return list2;
|
|
27006
|
+
}
|
|
27007
|
+
function renderJournalEditor() {
|
|
27008
|
+
const selected = selectedEntry();
|
|
27009
|
+
const wrap = document.createElement("section");
|
|
27010
|
+
wrap.className = "journal-editor";
|
|
27011
|
+
const head = document.createElement("div");
|
|
27012
|
+
head.className = "journal-section-head";
|
|
27013
|
+
const dateInput = document.createElement("input");
|
|
27014
|
+
dateInput.type = "date";
|
|
27015
|
+
dateInput.value = selectedDate;
|
|
27016
|
+
dateInput.addEventListener("change", () => {
|
|
27017
|
+
selectedDate = dateInput.value || todayIsoDate();
|
|
27018
|
+
selectedEntryId = "";
|
|
27019
|
+
creatingEntry = false;
|
|
27020
|
+
setRoute({ date: selectedDate });
|
|
27021
|
+
});
|
|
27022
|
+
const newButton = document.createElement("button");
|
|
27023
|
+
newButton.type = "button";
|
|
27024
|
+
newButton.innerHTML = `${iconSvg("octicon-plus", PLUS_16_PATH)}<span>${text2().new}</span>`;
|
|
27025
|
+
newButton.addEventListener("click", () => {
|
|
27026
|
+
selectedEntryId = "";
|
|
27027
|
+
creatingEntry = true;
|
|
27028
|
+
render();
|
|
27029
|
+
});
|
|
27030
|
+
head.append(dateInput, newButton);
|
|
27031
|
+
const entriesForDate = (data?.journal.entries || []).filter((entry) => entry.date === selectedDate);
|
|
27032
|
+
const title = document.createElement("input");
|
|
27033
|
+
title.className = "journal-title-input";
|
|
27034
|
+
title.placeholder = text2().titlePlaceholder;
|
|
27035
|
+
title.value = selected?.title || "";
|
|
27036
|
+
const labels = createLabelEditor(selected ? selected.labels : [], text2().labelPlaceholder, labelSuggestions(), text2().removeLabel);
|
|
27037
|
+
const body = document.createElement("textarea");
|
|
27038
|
+
body.className = "journal-body-input";
|
|
27039
|
+
body.placeholder = text2().entryBodyPlaceholder;
|
|
27040
|
+
body.value = selected?.body || "";
|
|
27041
|
+
const actions = document.createElement("div");
|
|
27042
|
+
actions.className = "journal-editor-actions";
|
|
27043
|
+
const save = document.createElement("button");
|
|
27044
|
+
save.type = "button";
|
|
27045
|
+
save.className = "journal-primary-action";
|
|
27046
|
+
save.textContent = selected ? text2().saveEntry : text2().addEntry;
|
|
27047
|
+
save.addEventListener("click", async () => {
|
|
27048
|
+
setButtonBusy(save, true);
|
|
27049
|
+
try {
|
|
27050
|
+
const payload = {
|
|
27051
|
+
date: selectedDate,
|
|
27052
|
+
title: title.value,
|
|
27053
|
+
labels: labels.getLabels(),
|
|
27054
|
+
body: body.value
|
|
27055
|
+
};
|
|
27056
|
+
if (selected)
|
|
27057
|
+
await requestJournal("update-entry", { id: selected.id, ...payload });
|
|
27058
|
+
else
|
|
27059
|
+
await requestJournal("add-entry", payload, (next) => {
|
|
27060
|
+
if (next.entry) {
|
|
27061
|
+
selectedEntryId = next.entry.id;
|
|
27062
|
+
creatingEntry = false;
|
|
27063
|
+
}
|
|
27064
|
+
});
|
|
27065
|
+
} catch (error2) {
|
|
27066
|
+
message = error2 instanceof Error ? error2.message : text2().saveEntryFailed;
|
|
27067
|
+
deps.setStatus("error");
|
|
27068
|
+
render();
|
|
27069
|
+
} finally {
|
|
27070
|
+
setButtonBusy(save, false);
|
|
27071
|
+
}
|
|
27072
|
+
});
|
|
27073
|
+
actions.appendChild(save);
|
|
27074
|
+
if (selected) {
|
|
27075
|
+
const del = document.createElement("button");
|
|
27076
|
+
del.type = "button";
|
|
27077
|
+
del.className = "journal-danger-action";
|
|
27078
|
+
del.innerHTML = `${iconSvg("octicon-trash", TRASH_16_PATH)}<span>${text2().delete}</span>`;
|
|
27079
|
+
del.addEventListener("click", async () => {
|
|
27080
|
+
const ok = await showConfirmDialog({
|
|
27081
|
+
title: `${text2().delete}?`,
|
|
27082
|
+
body: selected.title || selected.date,
|
|
27083
|
+
confirmLabel: text2().delete,
|
|
27084
|
+
danger: true,
|
|
27085
|
+
focusReturnTarget: del
|
|
27086
|
+
});
|
|
27087
|
+
if (!ok)
|
|
27088
|
+
return;
|
|
27089
|
+
setButtonBusy(del, true);
|
|
27090
|
+
try {
|
|
27091
|
+
await requestJournal("delete-entry", { id: selected.id });
|
|
27092
|
+
selectedEntryId = "";
|
|
27093
|
+
creatingEntry = false;
|
|
27094
|
+
} catch (error2) {
|
|
27095
|
+
message = error2 instanceof Error ? error2.message : text2().deleteEntryFailed;
|
|
27096
|
+
deps.setStatus("error");
|
|
27097
|
+
render();
|
|
27098
|
+
} finally {
|
|
27099
|
+
setButtonBusy(del, false);
|
|
27100
|
+
}
|
|
27101
|
+
});
|
|
27102
|
+
actions.appendChild(del);
|
|
27103
|
+
}
|
|
27104
|
+
wrap.append(head, renderJournalEntryList(entriesForDate), title, labels.element, body, actions);
|
|
27105
|
+
return wrap;
|
|
27106
|
+
}
|
|
27107
|
+
function renderRelatedTasks() {
|
|
27108
|
+
const panel = document.createElement("aside");
|
|
27109
|
+
panel.className = "journal-related";
|
|
27110
|
+
const heading2 = document.createElement("h2");
|
|
27111
|
+
heading2.textContent = text2().relatedTasks;
|
|
27112
|
+
panel.appendChild(heading2);
|
|
27113
|
+
const tasks = (data?.tasks.tasks || []).filter((task) => taskCoversDate(task, selectedDate));
|
|
27114
|
+
if (!tasks.length) {
|
|
27115
|
+
const empty = document.createElement("div");
|
|
27116
|
+
empty.className = "journal-empty";
|
|
27117
|
+
empty.textContent = text2().noRelatedTasks;
|
|
27118
|
+
panel.appendChild(empty);
|
|
27119
|
+
return panel;
|
|
27120
|
+
}
|
|
27121
|
+
for (const task of tasks)
|
|
27122
|
+
panel.appendChild(renderTaskCard(task, { compact: true }));
|
|
27123
|
+
return panel;
|
|
27124
|
+
}
|
|
27125
|
+
function renderJournalTab() {
|
|
27126
|
+
const body = document.createElement("div");
|
|
27127
|
+
body.className = "journal-daily-layout";
|
|
27128
|
+
body.append(renderCalendar(), renderJournalEditor(), renderRelatedTasks());
|
|
27129
|
+
return body;
|
|
27130
|
+
}
|
|
27131
|
+
function taskVisible(task) {
|
|
27132
|
+
return !labelFilter || task.labels.includes(labelFilter);
|
|
27133
|
+
}
|
|
27134
|
+
function renderTaskCard(task, options = {}) {
|
|
27135
|
+
const card = document.createElement("button");
|
|
27136
|
+
card.type = "button";
|
|
27137
|
+
card.className = "journal-task-card";
|
|
27138
|
+
card.dataset.taskId = task.id;
|
|
27139
|
+
card.classList.toggle("selected", task.id === selectedTaskId);
|
|
27140
|
+
card.classList.toggle("compact", !!options.compact);
|
|
27141
|
+
card.addEventListener("click", (event) => {
|
|
27142
|
+
if (draggingTaskId || suppressTaskClick || taskDragHandleFromTarget(card, event.target))
|
|
27143
|
+
return;
|
|
27144
|
+
selectTask(task.id, { revealEditor: true });
|
|
27145
|
+
});
|
|
27146
|
+
card.addEventListener("dragstart", (event) => {
|
|
27147
|
+
if (!taskDragHandleFromTarget(card, event.target)) {
|
|
27148
|
+
event.preventDefault();
|
|
27149
|
+
return;
|
|
27150
|
+
}
|
|
27151
|
+
draggingTaskId = task.id;
|
|
27152
|
+
nativeTaskDragActive = true;
|
|
27153
|
+
lastTaskDropTarget = null;
|
|
27154
|
+
card.classList.add("dragging");
|
|
27155
|
+
if (event.dataTransfer) {
|
|
27156
|
+
event.dataTransfer.effectAllowed = "move";
|
|
27157
|
+
event.dataTransfer.setData("text/plain", task.id);
|
|
27158
|
+
const rect = card.getBoundingClientRect();
|
|
27159
|
+
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)));
|
|
27160
|
+
}
|
|
27161
|
+
});
|
|
27162
|
+
card.addEventListener("dragenter", (event) => {
|
|
27163
|
+
if (!draggingTaskId && !draggingGithubIssue() || draggingTaskId === task.id)
|
|
27164
|
+
return;
|
|
27165
|
+
event.preventDefault();
|
|
27166
|
+
const after = isTaskDropAfter(card, event);
|
|
27167
|
+
lastTaskDropTarget = after ? { after_id: task.id } : { before_id: task.id };
|
|
27168
|
+
markTaskDropTarget(card, after);
|
|
27169
|
+
});
|
|
27170
|
+
card.addEventListener("dragover", (event) => {
|
|
27171
|
+
const issue = draggingGithubIssue();
|
|
27172
|
+
if (!draggingTaskId && !issue || draggingTaskId === task.id)
|
|
27173
|
+
return;
|
|
27174
|
+
event.preventDefault();
|
|
27175
|
+
if (event.dataTransfer)
|
|
27176
|
+
event.dataTransfer.dropEffect = issue ? "copy" : "move";
|
|
27177
|
+
scheduleTaskDragAutoScroll(event);
|
|
27178
|
+
const after = isTaskDropAfter(card, event);
|
|
27179
|
+
lastTaskDropTarget = after ? { after_id: task.id } : { before_id: task.id };
|
|
27180
|
+
markTaskDropTarget(card, after);
|
|
27181
|
+
});
|
|
27182
|
+
card.addEventListener("dragleave", (event) => {
|
|
27183
|
+
const related = event.relatedTarget;
|
|
27184
|
+
if (related && card.contains(related))
|
|
27185
|
+
return;
|
|
27186
|
+
lastTaskDropTarget = null;
|
|
27187
|
+
card.classList.remove("drop-target", "drop-after");
|
|
27188
|
+
});
|
|
27189
|
+
card.addEventListener("drop", (event) => {
|
|
27190
|
+
const issue = draggingGithubIssue() || githubIssueFromDataTransfer(event.dataTransfer);
|
|
27191
|
+
if (!draggingTaskId && !issue || draggingTaskId === task.id)
|
|
27192
|
+
return;
|
|
27193
|
+
event.preventDefault();
|
|
27194
|
+
event.stopPropagation();
|
|
27195
|
+
const after = isTaskDropAfter(card, event);
|
|
27196
|
+
const target = after ? { after_id: task.id } : { before_id: task.id };
|
|
27197
|
+
lastTaskDropTarget = null;
|
|
27198
|
+
if (issue)
|
|
27199
|
+
linkGithubIssueToTask(issue, target);
|
|
27200
|
+
else
|
|
27201
|
+
moveDraggedTask(target);
|
|
27202
|
+
});
|
|
27203
|
+
card.addEventListener("dragend", (event) => finishNativeTaskDrag(event));
|
|
27204
|
+
card.addEventListener("pointerdown", (event) => {
|
|
27205
|
+
beginPointerTaskDrag(task.id, event);
|
|
27206
|
+
});
|
|
27207
|
+
const top = document.createElement("span");
|
|
27208
|
+
top.className = "journal-task-card-top";
|
|
27209
|
+
const priority = document.createElement("span");
|
|
27210
|
+
priority.className = `journal-priority journal-priority-${task.priority}`;
|
|
27211
|
+
priority.textContent = text2().priorityLabels[task.priority];
|
|
27212
|
+
const status = document.createElement("span");
|
|
27213
|
+
status.className = "journal-task-status";
|
|
27214
|
+
status.textContent = text2().statusLabels[task.status];
|
|
27215
|
+
top.append(priority, status);
|
|
27216
|
+
const dragHandle = document.createElement("span");
|
|
27217
|
+
dragHandle.className = "journal-task-drag-handle";
|
|
27218
|
+
dragHandle.draggable = true;
|
|
27219
|
+
dragHandle.title = text2().dragTask;
|
|
27220
|
+
dragHandle.innerHTML = iconSvg("journal-task-drag-icon", GRABBER_16_PATH);
|
|
27221
|
+
dragHandle.setAttribute("aria-hidden", "true");
|
|
27222
|
+
top.appendChild(dragHandle);
|
|
27223
|
+
const title = document.createElement("strong");
|
|
27224
|
+
title.textContent = task.title;
|
|
27225
|
+
const labels = document.createElement("span");
|
|
27226
|
+
labels.className = "journal-task-labels";
|
|
27227
|
+
for (const label of task.labels)
|
|
27228
|
+
labels.appendChild(labelChip(label));
|
|
27229
|
+
card.append(top, title, labels);
|
|
27230
|
+
if (task.due_date) {
|
|
27231
|
+
const due = document.createElement("span");
|
|
27232
|
+
due.className = "journal-task-due";
|
|
27233
|
+
due.textContent = `${text2().duePrefix} ${task.due_date}`;
|
|
27234
|
+
card.appendChild(due);
|
|
27235
|
+
}
|
|
27236
|
+
if (task.claim) {
|
|
27237
|
+
const claim = document.createElement("span");
|
|
27238
|
+
claim.className = "journal-task-claim";
|
|
27239
|
+
claim.textContent = text2().claimedBy(task.claim.by);
|
|
27240
|
+
card.appendChild(claim);
|
|
27241
|
+
}
|
|
27242
|
+
return card;
|
|
27243
|
+
}
|
|
27244
|
+
function taskDragHandleFromTarget(card, target) {
|
|
27245
|
+
const handle = target?.closest(".journal-task-drag-handle");
|
|
27246
|
+
return handle && card.contains(handle) ? handle : null;
|
|
27247
|
+
}
|
|
27248
|
+
function isTaskDropAfter(card, event) {
|
|
27249
|
+
const rect = card.getBoundingClientRect();
|
|
27250
|
+
return event.clientY > rect.top + rect.height / 2;
|
|
27251
|
+
}
|
|
27252
|
+
function clearTaskDropTargets() {
|
|
27253
|
+
root.querySelectorAll(".journal-task-card.drop-target, .journal-task-column.drop-target").forEach((el) => {
|
|
27254
|
+
el.classList.remove("drop-target", "drop-after");
|
|
27255
|
+
});
|
|
27256
|
+
}
|
|
27257
|
+
function clearTaskDragState() {
|
|
27258
|
+
root.querySelectorAll(".journal-task-card.dragging").forEach((el) => {
|
|
27259
|
+
el.classList.remove("dragging");
|
|
27260
|
+
});
|
|
27261
|
+
stopTaskDragAutoScroll();
|
|
27262
|
+
clearTaskDropTargets();
|
|
27263
|
+
lastTaskDropTarget = null;
|
|
27264
|
+
nativeTaskDragActive = false;
|
|
27265
|
+
draggingTaskId = "";
|
|
27266
|
+
suppressTaskClick = true;
|
|
27267
|
+
window.setTimeout(() => {
|
|
27268
|
+
suppressTaskClick = false;
|
|
27269
|
+
}, 50);
|
|
27270
|
+
}
|
|
27271
|
+
function clearGithubIssueDragState() {
|
|
27272
|
+
draggingGithubIssueNumber = null;
|
|
27273
|
+
lastTaskDropTarget = null;
|
|
27274
|
+
stopTaskDragAutoScroll();
|
|
27275
|
+
clearTaskDropTargets();
|
|
27276
|
+
}
|
|
27277
|
+
function taskCardById(taskId) {
|
|
27278
|
+
return root.querySelector(`.journal-task-card[data-task-id="${CSS.escape(taskId)}"]`);
|
|
27279
|
+
}
|
|
27280
|
+
function taskDragScrollTargetAtPoint(clientX, clientY) {
|
|
27281
|
+
const element = document.elementFromPoint(clientX, clientY);
|
|
27282
|
+
let cursor = element?.closest(".journal-github-issues, .journal-github-panel, .journal-task-board-wrap, .journal-tasks-layout") || root.querySelector(".journal-task-board-wrap");
|
|
27283
|
+
while (cursor && cursor !== root) {
|
|
27284
|
+
const style = window.getComputedStyle(cursor);
|
|
27285
|
+
if (/(auto|scroll)/.test(style.overflowY) && cursor.scrollHeight > cursor.clientHeight + 1) {
|
|
27286
|
+
return cursor;
|
|
27287
|
+
}
|
|
27288
|
+
cursor = cursor.parentElement;
|
|
27289
|
+
}
|
|
27290
|
+
const layout = root.querySelector(".journal-tasks-layout");
|
|
27291
|
+
return layout && layout.scrollHeight > layout.clientHeight + 1 ? layout : null;
|
|
27292
|
+
}
|
|
27293
|
+
function taskDragAutoScrollDelta(target, clientY) {
|
|
27294
|
+
const rect = target.getBoundingClientRect();
|
|
27295
|
+
const canScrollUp = target.scrollTop > 0;
|
|
27296
|
+
const canScrollDown = target.scrollTop + target.clientHeight < target.scrollHeight - 1;
|
|
27297
|
+
const topDistance = clientY - rect.top;
|
|
27298
|
+
if (canScrollUp && topDistance < TASK_DRAG_AUTOSCROLL_EDGE_PX) {
|
|
27299
|
+
const intensity = (TASK_DRAG_AUTOSCROLL_EDGE_PX - Math.max(0, topDistance)) / TASK_DRAG_AUTOSCROLL_EDGE_PX;
|
|
27300
|
+
return -Math.max(4, Math.round(TASK_DRAG_AUTOSCROLL_MAX_STEP_PX * intensity));
|
|
27301
|
+
}
|
|
27302
|
+
const bottomDistance = rect.bottom - clientY;
|
|
27303
|
+
if (canScrollDown && bottomDistance < TASK_DRAG_AUTOSCROLL_EDGE_PX) {
|
|
27304
|
+
const intensity = (TASK_DRAG_AUTOSCROLL_EDGE_PX - Math.max(0, bottomDistance)) / TASK_DRAG_AUTOSCROLL_EDGE_PX;
|
|
27305
|
+
return Math.max(4, Math.round(TASK_DRAG_AUTOSCROLL_MAX_STEP_PX * intensity));
|
|
27306
|
+
}
|
|
27307
|
+
return 0;
|
|
27308
|
+
}
|
|
27309
|
+
function stopTaskDragAutoScroll() {
|
|
27310
|
+
if (!taskDragAutoScrollFrame)
|
|
27311
|
+
return;
|
|
27312
|
+
window.cancelAnimationFrame(taskDragAutoScrollFrame);
|
|
27313
|
+
taskDragAutoScrollFrame = 0;
|
|
27314
|
+
}
|
|
27315
|
+
function runTaskDragAutoScroll() {
|
|
27316
|
+
taskDragAutoScrollFrame = 0;
|
|
27317
|
+
if (!draggingTaskId && !draggingGithubIssue())
|
|
27318
|
+
return;
|
|
27319
|
+
const target = taskDragScrollTargetAtPoint(taskDragAutoScrollX, taskDragAutoScrollY);
|
|
27320
|
+
if (!target)
|
|
27321
|
+
return;
|
|
27322
|
+
const delta = taskDragAutoScrollDelta(target, taskDragAutoScrollY);
|
|
27323
|
+
if (!delta)
|
|
27324
|
+
return;
|
|
27325
|
+
target.scrollBy({ top: delta, behavior: "auto" });
|
|
27326
|
+
markPointerDropTarget({
|
|
27327
|
+
clientX: taskDragAutoScrollX,
|
|
27328
|
+
clientY: taskDragAutoScrollY
|
|
27329
|
+
});
|
|
27330
|
+
taskDragAutoScrollFrame = window.requestAnimationFrame(runTaskDragAutoScroll);
|
|
27331
|
+
}
|
|
27332
|
+
function scheduleTaskDragAutoScroll(event) {
|
|
27333
|
+
taskDragAutoScrollX = event.clientX;
|
|
27334
|
+
taskDragAutoScrollY = event.clientY;
|
|
27335
|
+
if (taskDragAutoScrollFrame)
|
|
27336
|
+
return;
|
|
27337
|
+
taskDragAutoScrollFrame = window.requestAnimationFrame(runTaskDragAutoScroll);
|
|
27338
|
+
}
|
|
27339
|
+
function markTaskDropTarget(card, after) {
|
|
27340
|
+
clearTaskDropTargets();
|
|
27341
|
+
card.classList.add("drop-target");
|
|
27342
|
+
card.classList.toggle("drop-after", after);
|
|
27343
|
+
}
|
|
27344
|
+
function markColumnDropTarget(column) {
|
|
27345
|
+
clearTaskDropTargets();
|
|
27346
|
+
column.classList.add("drop-target");
|
|
27347
|
+
}
|
|
27348
|
+
function taskDropTargetFromPoint(event) {
|
|
27349
|
+
const element = document.elementFromPoint(event.clientX, event.clientY);
|
|
27350
|
+
if (!element)
|
|
27351
|
+
return null;
|
|
27352
|
+
const card = element.closest(".journal-task-card");
|
|
27353
|
+
if (card?.dataset.taskId && card.dataset.taskId !== draggingTaskId) {
|
|
27354
|
+
const after = isTaskDropAfter(card, event);
|
|
27355
|
+
return after ? { after_id: card.dataset.taskId } : { before_id: card.dataset.taskId };
|
|
27356
|
+
}
|
|
27357
|
+
const column = element.closest(".journal-task-column");
|
|
27358
|
+
const status = column?.dataset.status;
|
|
27359
|
+
return isJournalTaskStatus(status) ? { status } : null;
|
|
27360
|
+
}
|
|
27361
|
+
function finishNativeTaskDrag(event) {
|
|
27362
|
+
const issue = draggingGithubIssue();
|
|
27363
|
+
if (!draggingTaskId && !issue) {
|
|
27364
|
+
clearTaskDropTargets();
|
|
27365
|
+
stopTaskDragAutoScroll();
|
|
27366
|
+
return;
|
|
27367
|
+
}
|
|
27368
|
+
const target = taskDropTargetFromPoint(event) || lastTaskDropTarget;
|
|
27369
|
+
if (!target) {
|
|
27370
|
+
if (issue)
|
|
27371
|
+
clearGithubIssueDragState();
|
|
27372
|
+
else
|
|
27373
|
+
clearTaskDragState();
|
|
27374
|
+
return;
|
|
27375
|
+
}
|
|
27376
|
+
lastTaskDropTarget = null;
|
|
27377
|
+
if (issue)
|
|
27378
|
+
linkGithubIssueToTask(issue, target);
|
|
27379
|
+
else
|
|
27380
|
+
moveDraggedTask(target);
|
|
27381
|
+
}
|
|
27382
|
+
function markPointerDropTarget(event) {
|
|
27383
|
+
const element = document.elementFromPoint(event.clientX, event.clientY);
|
|
27384
|
+
const card = element?.closest(".journal-task-card");
|
|
27385
|
+
if (card?.dataset.taskId && card.dataset.taskId !== draggingTaskId) {
|
|
27386
|
+
markTaskDropTarget(card, isTaskDropAfter(card, event));
|
|
27387
|
+
return;
|
|
27388
|
+
}
|
|
27389
|
+
const column = element?.closest(".journal-task-column");
|
|
27390
|
+
if (column) {
|
|
27391
|
+
markColumnDropTarget(column);
|
|
27392
|
+
return;
|
|
27393
|
+
}
|
|
27394
|
+
clearTaskDropTargets();
|
|
27395
|
+
}
|
|
27396
|
+
function beginPointerTaskDrag(taskId, event) {
|
|
27397
|
+
if (event.button !== 0)
|
|
27398
|
+
return;
|
|
27399
|
+
const source = event.currentTarget;
|
|
27400
|
+
if (!source)
|
|
27401
|
+
return;
|
|
27402
|
+
if (!taskDragHandleFromTarget(source, event.target))
|
|
27403
|
+
return;
|
|
27404
|
+
pointerDrag = {
|
|
27405
|
+
taskId,
|
|
27406
|
+
pointerId: event.pointerId,
|
|
27407
|
+
source,
|
|
27408
|
+
startX: event.clientX,
|
|
27409
|
+
startY: event.clientY,
|
|
27410
|
+
active: false
|
|
27411
|
+
};
|
|
27412
|
+
source.setPointerCapture?.(event.pointerId);
|
|
27413
|
+
document.addEventListener("pointermove", handlePointerTaskDragMove);
|
|
27414
|
+
document.addEventListener("pointerup", handlePointerTaskDragEnd);
|
|
27415
|
+
document.addEventListener("pointercancel", handlePointerTaskDragCancel);
|
|
27416
|
+
}
|
|
27417
|
+
function stopPointerTaskDragListeners() {
|
|
27418
|
+
document.removeEventListener("pointermove", handlePointerTaskDragMove);
|
|
27419
|
+
document.removeEventListener("pointerup", handlePointerTaskDragEnd);
|
|
27420
|
+
document.removeEventListener("pointercancel", handlePointerTaskDragCancel);
|
|
27421
|
+
}
|
|
27422
|
+
function handlePointerTaskDragMove(event) {
|
|
27423
|
+
if (!pointerDrag)
|
|
27424
|
+
return;
|
|
27425
|
+
if (event.pointerId !== pointerDrag.pointerId)
|
|
27426
|
+
return;
|
|
27427
|
+
const distance = Math.hypot(event.clientX - pointerDrag.startX, event.clientY - pointerDrag.startY);
|
|
27428
|
+
if (!pointerDrag.active) {
|
|
27429
|
+
if (distance < 5)
|
|
27430
|
+
return;
|
|
27431
|
+
pointerDrag.active = true;
|
|
27432
|
+
draggingTaskId = pointerDrag.taskId;
|
|
27433
|
+
taskCardById(pointerDrag.taskId)?.classList.add("dragging");
|
|
27434
|
+
}
|
|
27435
|
+
event.preventDefault();
|
|
27436
|
+
scheduleTaskDragAutoScroll(event);
|
|
27437
|
+
markPointerDropTarget(event);
|
|
27438
|
+
}
|
|
27439
|
+
function handlePointerTaskDragEnd(event) {
|
|
27440
|
+
const state = pointerDrag;
|
|
27441
|
+
if (state && event.pointerId !== state.pointerId)
|
|
27442
|
+
return;
|
|
27443
|
+
pointerDrag = null;
|
|
27444
|
+
stopPointerTaskDragListeners();
|
|
27445
|
+
state?.source.releasePointerCapture?.(event.pointerId);
|
|
27446
|
+
if (!state?.active)
|
|
27447
|
+
return;
|
|
27448
|
+
const target = taskDropTargetFromPoint(event);
|
|
27449
|
+
if (target)
|
|
27450
|
+
moveDraggedTask(target);
|
|
27451
|
+
else
|
|
27452
|
+
clearTaskDragState();
|
|
27453
|
+
}
|
|
27454
|
+
function handlePointerTaskDragCancel(event) {
|
|
27455
|
+
const state = pointerDrag;
|
|
27456
|
+
if (state && event.pointerId !== state.pointerId)
|
|
27457
|
+
return;
|
|
27458
|
+
pointerDrag = null;
|
|
27459
|
+
stopPointerTaskDragListeners();
|
|
27460
|
+
state?.source.releasePointerCapture?.(event.pointerId);
|
|
27461
|
+
if (nativeTaskDragActive)
|
|
27462
|
+
return;
|
|
27463
|
+
clearTaskDragState();
|
|
27464
|
+
}
|
|
27465
|
+
async function moveDraggedTask(target) {
|
|
27466
|
+
const id = draggingTaskId;
|
|
27467
|
+
clearTaskDragState();
|
|
27468
|
+
if (!id)
|
|
27469
|
+
return;
|
|
27470
|
+
try {
|
|
27471
|
+
await requestJournal("move-task", { id, ...target }, () => {
|
|
27472
|
+
selectedTaskId = id;
|
|
27473
|
+
});
|
|
27474
|
+
} catch (error2) {
|
|
27475
|
+
message = error2 instanceof Error ? error2.message : text2().moveTaskFailed;
|
|
27476
|
+
deps.setStatus("error");
|
|
27477
|
+
render();
|
|
27478
|
+
}
|
|
27479
|
+
}
|
|
27480
|
+
function renderQueuePreview() {
|
|
27481
|
+
const queue = document.createElement("section");
|
|
27482
|
+
queue.className = "journal-command-preview journal-queue";
|
|
27483
|
+
const title = document.createElement("strong");
|
|
27484
|
+
title.textContent = text2().aiQueue;
|
|
27485
|
+
const command = document.createElement("code");
|
|
27486
|
+
command.textContent = labelFilter ? `code-viewer journal task-next --label ${labelFilter} --limit 5` : "code-viewer journal task-next --limit 5";
|
|
27487
|
+
queue.append(title, command);
|
|
27488
|
+
const next = data ? selectNextJournalTasks(data.tasks, { labels: labelFilter ? [labelFilter] : [] }, 5) : [];
|
|
27489
|
+
if (next.length) {
|
|
27490
|
+
const list2 = document.createElement("ol");
|
|
27491
|
+
for (const task of next) {
|
|
27492
|
+
const item = document.createElement("li");
|
|
27493
|
+
item.textContent = `${text2().priorityLabels[task.priority]} ${task.title}`;
|
|
27494
|
+
list2.appendChild(item);
|
|
27495
|
+
}
|
|
27496
|
+
queue.appendChild(list2);
|
|
27497
|
+
}
|
|
27498
|
+
return queue;
|
|
27499
|
+
}
|
|
27500
|
+
function renderGithubIssueToggle() {
|
|
27501
|
+
const button = document.createElement("button");
|
|
27502
|
+
button.type = "button";
|
|
27503
|
+
button.className = "journal-github-toggle";
|
|
27504
|
+
button.classList.toggle("active", githubInboxOpen);
|
|
27505
|
+
button.setAttribute("aria-expanded", githubInboxOpen ? "true" : "false");
|
|
27506
|
+
button.innerHTML = iconSvg("journal-github-toggle-icon", COMMENT_DISCUSSION_16_PATH);
|
|
27507
|
+
const label = document.createElement("span");
|
|
27508
|
+
label.textContent = text2().githubIssues;
|
|
27509
|
+
button.appendChild(label);
|
|
27510
|
+
const count = document.createElement("span");
|
|
27511
|
+
count.className = "journal-github-toggle-count";
|
|
27512
|
+
count.textContent = githubIssuesLoading ? "..." : githubIssuesLoaded ? String(githubIssues.length) : "0";
|
|
27513
|
+
button.appendChild(count);
|
|
27514
|
+
button.addEventListener("click", () => {
|
|
27515
|
+
githubInboxOpen = !githubInboxOpen;
|
|
27516
|
+
render();
|
|
27517
|
+
if (githubInboxOpen && !githubIssuesAutoRequested && !githubIssuesLoading) {
|
|
27518
|
+
loadGithubIssues();
|
|
27519
|
+
}
|
|
27520
|
+
});
|
|
27521
|
+
return button;
|
|
27522
|
+
}
|
|
27523
|
+
function issueLabel(issueNumber) {
|
|
27524
|
+
return journalIssueLabel(issueNumber);
|
|
27525
|
+
}
|
|
27526
|
+
function linkedTaskForIssue(issueNumber) {
|
|
27527
|
+
const label = issueLabel(issueNumber);
|
|
27528
|
+
const repoLabel = journalIssueRepoLabel(githubRepoFilter || undefined);
|
|
27529
|
+
return data?.tasks.tasks.find((task) => {
|
|
27530
|
+
if (!task.labels.includes("github") || !task.labels.includes(label)) {
|
|
27531
|
+
return false;
|
|
27532
|
+
}
|
|
27533
|
+
if (repoLabel)
|
|
27534
|
+
return task.labels.includes(repoLabel);
|
|
27535
|
+
return !task.labels.some((taskLabel) => taskLabel.startsWith("repo-"));
|
|
27536
|
+
}) || null;
|
|
27537
|
+
}
|
|
27538
|
+
function githubIssueStateLabel(state) {
|
|
27539
|
+
if (state === "open" || state === "closed" || state === "all") {
|
|
27540
|
+
return text2().githubStateLabels[state];
|
|
27541
|
+
}
|
|
27542
|
+
return state;
|
|
27543
|
+
}
|
|
27544
|
+
function closeGithubInbox() {
|
|
27545
|
+
if (!githubInboxOpen)
|
|
27546
|
+
return;
|
|
27547
|
+
githubInboxOpen = false;
|
|
27548
|
+
render();
|
|
27549
|
+
}
|
|
27550
|
+
function handleGithubInboxKeydown(event) {
|
|
27551
|
+
if (event.key !== "Escape" || !githubInboxOpen)
|
|
27552
|
+
return;
|
|
27553
|
+
closeGithubInbox();
|
|
27554
|
+
}
|
|
27555
|
+
function handleGithubInboxOutsidePointerDown(event) {
|
|
27556
|
+
if (!githubInboxOpen)
|
|
27557
|
+
return;
|
|
27558
|
+
const target = event.target;
|
|
27559
|
+
const panel = root.querySelector(".journal-github-panel");
|
|
27560
|
+
const toggle = root.querySelector(".journal-github-toggle");
|
|
27561
|
+
if (target && (panel?.contains(target) || toggle?.contains(target)))
|
|
27562
|
+
return;
|
|
27563
|
+
closeGithubInbox();
|
|
27564
|
+
}
|
|
27565
|
+
function resetGithubIssueFilters(shouldRender = true) {
|
|
27566
|
+
githubIssueGeneration++;
|
|
27567
|
+
githubIssueLimit = GITHUB_ISSUE_INITIAL_LIMIT;
|
|
27568
|
+
githubIssues = [];
|
|
27569
|
+
githubIssuesLoaded = false;
|
|
27570
|
+
githubIssuesLoading = false;
|
|
27571
|
+
githubIssuesAutoRequested = false;
|
|
27572
|
+
githubIssuesError = githubRetrySeconds() > 0 ? text2().githubRateLimited(githubRetrySeconds()) : "";
|
|
27573
|
+
if (shouldRender)
|
|
27574
|
+
render();
|
|
27575
|
+
}
|
|
27576
|
+
function stableGithubIssues(issues) {
|
|
27577
|
+
const byNumber = new Map;
|
|
27578
|
+
for (const issue of issues)
|
|
27579
|
+
byNumber.set(issue.number, issue);
|
|
27580
|
+
return [...byNumber.values()].sort((a2, b2) => b2.number - a2.number);
|
|
27581
|
+
}
|
|
27582
|
+
function githubRetrySeconds() {
|
|
27583
|
+
return Math.max(0, Math.ceil((githubIssuesRetryAt - Date.now()) / 1000));
|
|
27584
|
+
}
|
|
27585
|
+
function githubCanRequest() {
|
|
27586
|
+
return !githubIssuesLoading && githubRetrySeconds() === 0;
|
|
27587
|
+
}
|
|
27588
|
+
async function loadGithubIssues(limit = githubIssueLimit) {
|
|
27589
|
+
if (!githubCanRequest()) {
|
|
27590
|
+
render();
|
|
27591
|
+
return;
|
|
27592
|
+
}
|
|
27593
|
+
const seq = ++githubIssueGeneration;
|
|
27594
|
+
githubIssuesAutoRequested = true;
|
|
27595
|
+
githubIssuesLoading = true;
|
|
27596
|
+
githubIssueLimit = Math.min(Math.max(GITHUB_ISSUE_INITIAL_LIMIT, limit), GITHUB_ISSUE_LIMIT_MAX);
|
|
27597
|
+
githubIssuesError = "";
|
|
27598
|
+
render();
|
|
27599
|
+
deps.setStatus("refreshing");
|
|
27600
|
+
try {
|
|
27601
|
+
const res = await deps.trackLoad(fetch("/_journal", {
|
|
27602
|
+
method: "POST",
|
|
27603
|
+
headers: {
|
|
27604
|
+
"Content-Type": "application/json",
|
|
27605
|
+
"X-Code-Viewer-Action": "1"
|
|
27606
|
+
},
|
|
27607
|
+
body: JSON.stringify({
|
|
27608
|
+
action: "list-github-issues",
|
|
27609
|
+
repo: githubRepoFilter || undefined,
|
|
27610
|
+
labels: parseGithubLabels(githubLabelFilter),
|
|
27611
|
+
search: githubSearchFilter || undefined,
|
|
27612
|
+
state: githubStateFilter,
|
|
27613
|
+
limit: githubIssueLimit
|
|
27614
|
+
})
|
|
27615
|
+
}));
|
|
27616
|
+
if (!res.ok)
|
|
27617
|
+
throw new Error(await res.text());
|
|
27618
|
+
const response = await res.json();
|
|
27619
|
+
if (seq !== githubIssueGeneration)
|
|
27620
|
+
return;
|
|
27621
|
+
githubIssues = stableGithubIssues(response.issues || []);
|
|
27622
|
+
githubIssuesLoaded = true;
|
|
27623
|
+
deps.setStatus("live");
|
|
27624
|
+
} catch (error2) {
|
|
27625
|
+
if (seq !== githubIssueGeneration)
|
|
27626
|
+
return;
|
|
27627
|
+
if (error2 instanceof Error && /rate limit|secondary rate limit/i.test(error2.message)) {
|
|
27628
|
+
githubIssuesRetryAt = Date.now() + GITHUB_ISSUE_RATE_LIMIT_RETRY_MS;
|
|
27629
|
+
githubIssuesError = text2().githubRateLimited(githubRetrySeconds());
|
|
27630
|
+
window.setTimeout(() => {
|
|
27631
|
+
if (githubRetrySeconds() === 0) {
|
|
27632
|
+
githubIssuesError = "";
|
|
27633
|
+
render();
|
|
27634
|
+
}
|
|
27635
|
+
}, GITHUB_ISSUE_RATE_LIMIT_RETRY_MS + 50);
|
|
27636
|
+
} else {
|
|
27637
|
+
githubIssuesError = error2 instanceof Error ? error2.message : text2().githubLoadFailed;
|
|
27638
|
+
}
|
|
27639
|
+
githubIssuesLoaded = false;
|
|
27640
|
+
deps.setStatus("error");
|
|
27641
|
+
} finally {
|
|
27642
|
+
if (seq === githubIssueGeneration) {
|
|
27643
|
+
githubIssuesLoading = false;
|
|
27644
|
+
render();
|
|
27645
|
+
}
|
|
27646
|
+
}
|
|
27647
|
+
}
|
|
27648
|
+
async function linkGithubIssueToTask(issue, target) {
|
|
27649
|
+
try {
|
|
27650
|
+
const response = await requestJournal("link-github-issue", {
|
|
27651
|
+
issue_number: issue.number,
|
|
27652
|
+
repo: githubRepoFilter || undefined,
|
|
27653
|
+
title: issue.title,
|
|
27654
|
+
url: issue.url,
|
|
27655
|
+
memo_label: text2().githubMemoLabel,
|
|
27656
|
+
status: target.status,
|
|
27657
|
+
before_id: target.before_id,
|
|
27658
|
+
after_id: target.after_id,
|
|
27659
|
+
priority: "p2",
|
|
27660
|
+
labels: normalizeJournalLabels([labelFilter])
|
|
27661
|
+
});
|
|
27662
|
+
if (response.task) {
|
|
27663
|
+
selectedTaskId = response.task.id;
|
|
27664
|
+
githubInboxOpen = false;
|
|
27665
|
+
render();
|
|
27666
|
+
return response.task.id;
|
|
27667
|
+
}
|
|
27668
|
+
} catch (error2) {
|
|
27669
|
+
message = error2 instanceof Error ? error2.message : text2().githubLinkTaskFailed;
|
|
27670
|
+
deps.setStatus("error");
|
|
27671
|
+
render();
|
|
27672
|
+
} finally {
|
|
27673
|
+
stopTaskDragAutoScroll();
|
|
27674
|
+
draggingGithubIssueNumber = null;
|
|
27675
|
+
}
|
|
27676
|
+
return null;
|
|
27677
|
+
}
|
|
27678
|
+
function draggingGithubIssue() {
|
|
27679
|
+
if (draggingGithubIssueNumber === null)
|
|
27680
|
+
return null;
|
|
27681
|
+
return githubIssues.find((issue) => issue.number === draggingGithubIssueNumber) || null;
|
|
27682
|
+
}
|
|
27683
|
+
function githubIssueFromDataTransfer(dataTransfer) {
|
|
27684
|
+
const raw = dataTransfer?.getData("text/plain") || "";
|
|
27685
|
+
const match2 = raw.trim().match(/^#?(\d+)$/);
|
|
27686
|
+
if (!match2)
|
|
27687
|
+
return null;
|
|
27688
|
+
const issueNumber = Number(match2[1]);
|
|
27689
|
+
if (!Number.isInteger(issueNumber))
|
|
27690
|
+
return null;
|
|
27691
|
+
return githubIssues.find((issue) => issue.number === issueNumber) || null;
|
|
27692
|
+
}
|
|
27693
|
+
function renderGithubIssueCard(issue) {
|
|
27694
|
+
const card = document.createElement("article");
|
|
27695
|
+
card.className = "journal-github-issue-card";
|
|
27696
|
+
card.dataset.issueNumber = String(issue.number);
|
|
27697
|
+
const linked = linkedTaskForIssue(issue.number);
|
|
27698
|
+
card.draggable = !linked;
|
|
27699
|
+
if (linked)
|
|
27700
|
+
card.classList.add("linked");
|
|
27701
|
+
if (!linked) {
|
|
27702
|
+
card.addEventListener("dragstart", (event) => {
|
|
27703
|
+
draggingGithubIssueNumber = issue.number;
|
|
27704
|
+
lastTaskDropTarget = null;
|
|
27705
|
+
event.dataTransfer?.setData("text/plain", `#${issue.number}`);
|
|
27706
|
+
if (event.dataTransfer)
|
|
27707
|
+
event.dataTransfer.effectAllowed = "copyMove";
|
|
27708
|
+
});
|
|
27709
|
+
card.addEventListener("dragend", (event) => finishNativeTaskDrag(event));
|
|
27710
|
+
}
|
|
27711
|
+
const head = document.createElement("div");
|
|
27712
|
+
head.className = "journal-github-issue-head";
|
|
27713
|
+
const number = document.createElement("span");
|
|
27714
|
+
number.textContent = `#${issue.number}`;
|
|
27715
|
+
const state = document.createElement("span");
|
|
27716
|
+
state.textContent = githubIssueStateLabel(issue.state);
|
|
27717
|
+
head.append(number, state);
|
|
27718
|
+
if (linked) {
|
|
27719
|
+
const linkedBadge = document.createElement("span");
|
|
27720
|
+
linkedBadge.className = "journal-github-linked";
|
|
27721
|
+
linkedBadge.textContent = text2().githubLinked;
|
|
27722
|
+
head.appendChild(linkedBadge);
|
|
27723
|
+
}
|
|
27724
|
+
const title = document.createElement("strong");
|
|
27725
|
+
title.textContent = issue.title;
|
|
27726
|
+
const labels = document.createElement("div");
|
|
27727
|
+
labels.className = "journal-github-issue-labels";
|
|
27728
|
+
for (const label of issue.labels.slice(0, 4)) {
|
|
27729
|
+
labels.appendChild(labelChip(label));
|
|
27730
|
+
}
|
|
27731
|
+
const action = document.createElement("button");
|
|
27732
|
+
action.type = "button";
|
|
27733
|
+
action.draggable = false;
|
|
27734
|
+
action.className = "journal-github-issue-action";
|
|
27735
|
+
action.textContent = linked ? text2().githubOpenTask : text2().githubAddToBoard;
|
|
27736
|
+
action.addEventListener("click", async (event) => {
|
|
27737
|
+
event.preventDefault();
|
|
27738
|
+
event.stopPropagation();
|
|
27739
|
+
if (linked) {
|
|
27740
|
+
githubInboxOpen = false;
|
|
27741
|
+
selectTask(linked.id, { focusEditor: true, revealEditor: true });
|
|
27742
|
+
return;
|
|
27743
|
+
}
|
|
27744
|
+
action.disabled = true;
|
|
27745
|
+
const taskId = await linkGithubIssueToTask(issue, { status: "draft" });
|
|
27746
|
+
if (taskId)
|
|
27747
|
+
selectTask(taskId, { focusEditor: true, revealEditor: true });
|
|
27748
|
+
});
|
|
27749
|
+
card.append(head, title, labels, action);
|
|
27750
|
+
return card;
|
|
27751
|
+
}
|
|
27752
|
+
function renderGithubIssuesPanel() {
|
|
27753
|
+
const panel = document.createElement("section");
|
|
27754
|
+
panel.className = "journal-github-panel";
|
|
27755
|
+
const title = document.createElement("strong");
|
|
27756
|
+
title.textContent = text2().githubIssues;
|
|
27757
|
+
const controls = document.createElement("div");
|
|
27758
|
+
controls.className = "journal-github-controls";
|
|
27759
|
+
const repo = document.createElement("input");
|
|
27760
|
+
repo.type = "text";
|
|
27761
|
+
repo.placeholder = text2().githubRepoPlaceholder;
|
|
27762
|
+
repo.value = githubRepoFilter;
|
|
27763
|
+
const ghLabel = document.createElement("input");
|
|
27764
|
+
ghLabel.type = "text";
|
|
27765
|
+
ghLabel.placeholder = text2().githubLabelPlaceholder;
|
|
27766
|
+
ghLabel.value = githubLabelFilter;
|
|
27767
|
+
const search = document.createElement("input");
|
|
27768
|
+
search.type = "search";
|
|
27769
|
+
search.placeholder = text2().githubSearchPlaceholder;
|
|
27770
|
+
search.value = githubSearchFilter;
|
|
27771
|
+
const state = document.createElement("select");
|
|
27772
|
+
for (const value of ["open", "closed", "all"]) {
|
|
27773
|
+
const option = document.createElement("option");
|
|
27774
|
+
option.value = value;
|
|
27775
|
+
option.textContent = text2().githubStateLabels[value];
|
|
27776
|
+
option.selected = value === githubStateFilter;
|
|
27777
|
+
state.appendChild(option);
|
|
27778
|
+
}
|
|
27779
|
+
const syncControls = (shouldRender = true, shouldLoad = shouldRender) => {
|
|
27780
|
+
const nextState = state.value === "closed" || state.value === "all" ? state.value : "open";
|
|
27781
|
+
const nextRepo = repo.value.trim();
|
|
27782
|
+
const nextLabel = ghLabel.value.trim();
|
|
27783
|
+
const nextSearch = search.value.trim();
|
|
27784
|
+
const changed = nextRepo !== githubRepoFilter || nextLabel !== githubLabelFilter || nextSearch !== githubSearchFilter || nextState !== githubStateFilter;
|
|
27785
|
+
githubRepoFilter = nextRepo;
|
|
27786
|
+
githubLabelFilter = nextLabel;
|
|
27787
|
+
githubSearchFilter = nextSearch;
|
|
27788
|
+
githubStateFilter = nextState;
|
|
27789
|
+
if (!changed)
|
|
27790
|
+
return;
|
|
27791
|
+
resetGithubIssueFilters(shouldRender);
|
|
27792
|
+
if (shouldLoad && githubInboxOpen)
|
|
27793
|
+
loadGithubIssues();
|
|
27794
|
+
};
|
|
27795
|
+
const loadOnEnter = (event) => {
|
|
27796
|
+
if (event.key !== "Enter")
|
|
27797
|
+
return;
|
|
27798
|
+
event.preventDefault();
|
|
27799
|
+
syncControls(false, false);
|
|
27800
|
+
loadGithubIssues();
|
|
27801
|
+
};
|
|
27802
|
+
repo.addEventListener("change", () => syncControls());
|
|
27803
|
+
repo.addEventListener("keydown", loadOnEnter);
|
|
27804
|
+
ghLabel.addEventListener("change", () => syncControls());
|
|
27805
|
+
ghLabel.addEventListener("keydown", loadOnEnter);
|
|
27806
|
+
search.addEventListener("change", () => syncControls());
|
|
27807
|
+
search.addEventListener("keydown", loadOnEnter);
|
|
27808
|
+
state.addEventListener("change", () => syncControls());
|
|
27809
|
+
const load = document.createElement("button");
|
|
27810
|
+
load.type = "button";
|
|
27811
|
+
load.textContent = githubIssuesLoading ? text2().githubLoading : text2().githubLoad;
|
|
27812
|
+
load.disabled = !githubCanRequest();
|
|
27813
|
+
load.addEventListener("click", () => {
|
|
27814
|
+
syncControls(false, false);
|
|
27815
|
+
loadGithubIssues();
|
|
27816
|
+
});
|
|
27817
|
+
controls.append(repo, ghLabel, search, state, load);
|
|
27818
|
+
const hint = document.createElement("p");
|
|
27819
|
+
hint.className = "journal-github-hint";
|
|
27820
|
+
const showing = githubIssuesLoaded && !githubIssuesError ? ` ${text2().githubShowing(githubIssues.length, githubIssueLimit)}` : "";
|
|
27821
|
+
hint.textContent = githubIssuesError || `${text2().githubDragHint}${showing}`;
|
|
27822
|
+
const list2 = document.createElement("div");
|
|
27823
|
+
list2.className = "journal-github-issues";
|
|
27824
|
+
list2.addEventListener("dragover", (event) => {
|
|
27825
|
+
if (!draggingGithubIssue())
|
|
27826
|
+
return;
|
|
27827
|
+
event.preventDefault();
|
|
27828
|
+
if (event.dataTransfer)
|
|
27829
|
+
event.dataTransfer.dropEffect = "copy";
|
|
27830
|
+
scheduleTaskDragAutoScroll(event);
|
|
27831
|
+
});
|
|
27832
|
+
if (githubIssues.length) {
|
|
27833
|
+
for (const issue of githubIssues) {
|
|
27834
|
+
list2.appendChild(renderGithubIssueCard(issue));
|
|
27835
|
+
}
|
|
27836
|
+
} else {
|
|
27837
|
+
const empty = document.createElement("div");
|
|
27838
|
+
empty.className = "journal-empty";
|
|
27839
|
+
empty.textContent = githubIssuesLoading ? text2().githubLoading : githubIssuesLoaded ? text2().githubNoIssues : text2().githubNotLoaded;
|
|
27840
|
+
list2.appendChild(empty);
|
|
27841
|
+
}
|
|
27842
|
+
const head = document.createElement("div");
|
|
27843
|
+
head.className = "journal-github-panel-head";
|
|
27844
|
+
const close = document.createElement("button");
|
|
27845
|
+
close.type = "button";
|
|
27846
|
+
close.className = "journal-github-close";
|
|
27847
|
+
close.title = text2().githubClose;
|
|
27848
|
+
close.setAttribute("aria-label", text2().githubClose);
|
|
27849
|
+
close.innerHTML = iconSvg("journal-github-close-icon", X_16_PATH);
|
|
27850
|
+
close.addEventListener("click", () => {
|
|
27851
|
+
closeGithubInbox();
|
|
27852
|
+
});
|
|
27853
|
+
head.append(title, close);
|
|
27854
|
+
panel.append(head, controls, hint, list2);
|
|
27855
|
+
if (githubIssuesLoaded && githubIssues.length >= githubIssueLimit && githubIssueLimit < GITHUB_ISSUE_LIMIT_MAX) {
|
|
27856
|
+
const more = document.createElement("button");
|
|
27857
|
+
more.type = "button";
|
|
27858
|
+
more.className = "journal-github-load-more";
|
|
27859
|
+
more.textContent = text2().githubLoadMore;
|
|
27860
|
+
more.disabled = !githubCanRequest();
|
|
27861
|
+
more.addEventListener("click", () => loadGithubIssues(githubIssueLimit + GITHUB_ISSUE_LIMIT_STEP));
|
|
27862
|
+
panel.appendChild(more);
|
|
27863
|
+
}
|
|
27864
|
+
return panel;
|
|
27865
|
+
}
|
|
27866
|
+
function taskLabelCounts() {
|
|
27867
|
+
const counts = new Map;
|
|
27868
|
+
for (const task of data?.tasks.tasks || []) {
|
|
27869
|
+
for (const label of task.labels) {
|
|
27870
|
+
counts.set(label, (counts.get(label) || 0) + 1);
|
|
27871
|
+
}
|
|
27872
|
+
}
|
|
27873
|
+
return counts;
|
|
27874
|
+
}
|
|
27875
|
+
function renderLabelFilterButton(label, count, active, value = label) {
|
|
27876
|
+
const button = document.createElement("button");
|
|
27877
|
+
button.type = "button";
|
|
27878
|
+
button.className = "journal-label-filter";
|
|
27879
|
+
button.classList.toggle("active", active);
|
|
27880
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
27881
|
+
const name = document.createElement("span");
|
|
27882
|
+
name.className = "journal-label-filter-name";
|
|
27883
|
+
name.textContent = label;
|
|
27884
|
+
const badge = document.createElement("span");
|
|
27885
|
+
badge.className = "journal-label-filter-count";
|
|
27886
|
+
badge.textContent = String(count);
|
|
27887
|
+
button.append(name, badge);
|
|
27888
|
+
button.addEventListener("click", () => {
|
|
27889
|
+
labelFilter = active ? "" : value;
|
|
27890
|
+
setRoute({ tab: "tasks", label: labelFilter || undefined });
|
|
27891
|
+
});
|
|
27892
|
+
return button;
|
|
27893
|
+
}
|
|
27894
|
+
function renderLabelFilters() {
|
|
27895
|
+
const wrap = document.createElement("div");
|
|
27896
|
+
wrap.className = "journal-label-filters";
|
|
27897
|
+
wrap.setAttribute("aria-label", text2().labelFilters);
|
|
27898
|
+
const counts = taskLabelCounts();
|
|
27899
|
+
const total = data?.tasks.tasks.length || 0;
|
|
27900
|
+
wrap.appendChild(renderLabelFilterButton(text2().allLabels, total, !labelFilter, ""));
|
|
27901
|
+
for (const [label, count] of [...counts.entries()].sort((a2, b2) => a2[0].localeCompare(b2[0]))) {
|
|
27902
|
+
wrap.appendChild(renderLabelFilterButton(label, count, label === labelFilter));
|
|
27903
|
+
}
|
|
27904
|
+
return wrap;
|
|
27905
|
+
}
|
|
27906
|
+
function renderTaskBoard() {
|
|
27907
|
+
const board = document.createElement("div");
|
|
27908
|
+
board.className = "journal-task-board";
|
|
27909
|
+
for (const status of JOURNAL_TASK_STATUSES) {
|
|
27910
|
+
const column = document.createElement("section");
|
|
27911
|
+
column.className = `journal-task-column journal-task-column-${status}`;
|
|
27912
|
+
column.dataset.status = status;
|
|
27913
|
+
column.addEventListener("dragover", (event) => {
|
|
27914
|
+
const issue = draggingGithubIssue() || githubIssueFromDataTransfer(event.dataTransfer);
|
|
27915
|
+
if (!draggingTaskId && !issue)
|
|
27916
|
+
return;
|
|
27917
|
+
const targetCard = event.target?.closest(".journal-task-card");
|
|
27918
|
+
if (targetCard && column.contains(targetCard))
|
|
27919
|
+
return;
|
|
27920
|
+
event.preventDefault();
|
|
27921
|
+
if (event.dataTransfer)
|
|
27922
|
+
event.dataTransfer.dropEffect = issue ? "copy" : "move";
|
|
27923
|
+
scheduleTaskDragAutoScroll(event);
|
|
27924
|
+
lastTaskDropTarget = { status };
|
|
27925
|
+
markColumnDropTarget(column);
|
|
27926
|
+
});
|
|
27927
|
+
column.addEventListener("dragleave", (event) => {
|
|
27928
|
+
const related = event.relatedTarget;
|
|
27929
|
+
if (related && column.contains(related))
|
|
27930
|
+
return;
|
|
27931
|
+
lastTaskDropTarget = null;
|
|
27932
|
+
column.classList.remove("drop-target");
|
|
27933
|
+
});
|
|
27934
|
+
column.addEventListener("drop", (event) => {
|
|
27935
|
+
const issue = draggingGithubIssue();
|
|
27936
|
+
if (!draggingTaskId && !issue)
|
|
27937
|
+
return;
|
|
27938
|
+
const targetCard = event.target?.closest(".journal-task-card");
|
|
27939
|
+
if (targetCard && column.contains(targetCard))
|
|
27940
|
+
return;
|
|
27941
|
+
event.preventDefault();
|
|
27942
|
+
lastTaskDropTarget = null;
|
|
27943
|
+
if (issue)
|
|
27944
|
+
linkGithubIssueToTask(issue, { status });
|
|
27945
|
+
else
|
|
27946
|
+
moveDraggedTask({ status });
|
|
27947
|
+
});
|
|
27948
|
+
const head = document.createElement("div");
|
|
27949
|
+
head.className = "journal-task-column-head";
|
|
27950
|
+
const title = document.createElement("strong");
|
|
27951
|
+
title.textContent = text2().statusLabels[status];
|
|
27952
|
+
const count = document.createElement("span");
|
|
27953
|
+
const tasks = (data?.tasks.tasks || []).filter((task) => task.status === status && taskVisible(task));
|
|
27954
|
+
count.textContent = String(tasks.length);
|
|
27955
|
+
head.append(title, count);
|
|
27956
|
+
column.appendChild(head);
|
|
27957
|
+
if (!tasks.length) {
|
|
27958
|
+
const empty = document.createElement("div");
|
|
27959
|
+
empty.className = "journal-column-empty";
|
|
27960
|
+
empty.textContent = text2().empty;
|
|
27961
|
+
column.appendChild(empty);
|
|
27962
|
+
}
|
|
27963
|
+
for (const task of tasks)
|
|
27964
|
+
column.appendChild(renderTaskCard(task));
|
|
27965
|
+
board.appendChild(column);
|
|
27966
|
+
}
|
|
27967
|
+
return board;
|
|
27968
|
+
}
|
|
27969
|
+
function insertMarkdown(textarea, before, after = "") {
|
|
27970
|
+
const start = textarea.selectionStart;
|
|
27971
|
+
const end = textarea.selectionEnd;
|
|
27972
|
+
const selected = textarea.value.slice(start, end);
|
|
27973
|
+
textarea.value = textarea.value.slice(0, start) + before + selected + after + textarea.value.slice(end);
|
|
27974
|
+
textarea.focus();
|
|
27975
|
+
textarea.selectionStart = start + before.length;
|
|
27976
|
+
textarea.selectionEnd = end + before.length;
|
|
27977
|
+
}
|
|
27978
|
+
async function renderTaskPreview(host, body) {
|
|
27979
|
+
host.replaceChildren();
|
|
27980
|
+
const rendered = await renderMarkdownPreview(body || `_${text2().noBody}_`, { path: "journal-task.md", ref: "worktree" }, { syntaxHighlight: false });
|
|
27981
|
+
host.appendChild(rendered);
|
|
27982
|
+
}
|
|
27983
|
+
function renderTaskEditor() {
|
|
27984
|
+
const task = selectedTask();
|
|
27985
|
+
const panel = document.createElement("aside");
|
|
27986
|
+
panel.className = "journal-task-editor";
|
|
27987
|
+
const resizer = document.createElement("div");
|
|
27988
|
+
resizer.className = "journal-task-editor-resizer";
|
|
27989
|
+
resizer.role = "separator";
|
|
27990
|
+
resizer.tabIndex = 0;
|
|
27991
|
+
resizer.setAttribute("aria-orientation", "vertical");
|
|
27992
|
+
resizer.setAttribute("aria-label", text2().resizeTaskPanel);
|
|
27993
|
+
resizer.addEventListener("pointerdown", (event) => startTaskEditorResize(event, panel));
|
|
27994
|
+
const heading2 = document.createElement("div");
|
|
27995
|
+
heading2.className = "journal-section-head";
|
|
27996
|
+
const titleText = document.createElement("strong");
|
|
27997
|
+
titleText.textContent = task ? text2().taskHeading : text2().newTaskHeading;
|
|
27998
|
+
const newTask = document.createElement("button");
|
|
27999
|
+
newTask.type = "button";
|
|
28000
|
+
newTask.innerHTML = `${iconSvg("octicon-plus", PLUS_16_PATH)}<span>${text2().new}</span>`;
|
|
28001
|
+
newTask.addEventListener("click", () => {
|
|
28002
|
+
selectedTaskId = "";
|
|
28003
|
+
setRoute({ tab: "tasks", task: undefined });
|
|
28004
|
+
});
|
|
28005
|
+
heading2.append(titleText, newTask);
|
|
28006
|
+
const title = document.createElement("input");
|
|
28007
|
+
title.className = "journal-title-input";
|
|
28008
|
+
title.placeholder = text2().titlePlaceholder;
|
|
28009
|
+
title.value = task?.title || "";
|
|
28010
|
+
const row = document.createElement("div");
|
|
28011
|
+
row.className = "journal-task-fields";
|
|
28012
|
+
const status = document.createElement("select");
|
|
28013
|
+
for (const value of JOURNAL_TASK_STATUSES) {
|
|
28014
|
+
const option = document.createElement("option");
|
|
28015
|
+
option.value = value;
|
|
28016
|
+
option.textContent = text2().statusLabels[value];
|
|
28017
|
+
option.selected = (task?.status || "todo") === value;
|
|
28018
|
+
status.appendChild(option);
|
|
28019
|
+
}
|
|
28020
|
+
const priority = document.createElement("select");
|
|
28021
|
+
for (const value of JOURNAL_TASK_PRIORITIES) {
|
|
28022
|
+
const option = document.createElement("option");
|
|
28023
|
+
option.value = value;
|
|
28024
|
+
option.textContent = text2().priorityLabels[value];
|
|
28025
|
+
option.selected = (task?.priority || "p2") === value;
|
|
28026
|
+
priority.appendChild(option);
|
|
28027
|
+
}
|
|
28028
|
+
const due = document.createElement("input");
|
|
28029
|
+
due.type = "date";
|
|
28030
|
+
due.title = text2().endDate;
|
|
28031
|
+
due.setAttribute("aria-label", text2().endDate);
|
|
28032
|
+
due.value = task?.due_date || "";
|
|
28033
|
+
const sourceDate = document.createElement("input");
|
|
28034
|
+
sourceDate.type = "date";
|
|
28035
|
+
sourceDate.title = text2().startDate;
|
|
28036
|
+
sourceDate.setAttribute("aria-label", text2().startDate);
|
|
28037
|
+
sourceDate.value = task?.source_date || selectedDate;
|
|
28038
|
+
row.append(taskField(text2().statusField, status), taskField(text2().priorityField, priority), taskField(text2().startDate, sourceDate), taskField(text2().endDate, due));
|
|
28039
|
+
const labels = createLabelEditor(task ? task.labels : normalizeJournalLabels([labelFilter]), text2().labelPlaceholder, labelSuggestions(), text2().removeLabel);
|
|
28040
|
+
const toolbar = document.createElement("div");
|
|
28041
|
+
toolbar.className = "journal-markdown-toolbar";
|
|
28042
|
+
const body = document.createElement("textarea");
|
|
28043
|
+
body.className = "journal-body-input journal-task-body-input";
|
|
28044
|
+
body.placeholder = text2().taskBodyPlaceholder;
|
|
28045
|
+
body.value = task?.body || "";
|
|
28046
|
+
const preview = document.createElement("div");
|
|
28047
|
+
preview.className = "journal-markdown-preview";
|
|
28048
|
+
const syncMode = () => {
|
|
28049
|
+
body.hidden = editorMode === "preview";
|
|
28050
|
+
preview.hidden = editorMode === "write";
|
|
28051
|
+
renderTaskPreview(preview, body.value);
|
|
28052
|
+
};
|
|
28053
|
+
for (const spec of [
|
|
28054
|
+
["B", "**", "**"],
|
|
28055
|
+
["`", "`", "`"],
|
|
28056
|
+
["[]", "- [ ] ", ""],
|
|
28057
|
+
[">", "> ", ""]
|
|
28058
|
+
]) {
|
|
28059
|
+
const button = document.createElement("button");
|
|
28060
|
+
button.type = "button";
|
|
28061
|
+
button.textContent = spec[0];
|
|
28062
|
+
button.addEventListener("click", () => insertMarkdown(body, spec[1], spec[2]));
|
|
28063
|
+
toolbar.appendChild(button);
|
|
28064
|
+
}
|
|
28065
|
+
const modes = document.createElement("div");
|
|
28066
|
+
modes.className = "seg journal-editor-mode";
|
|
28067
|
+
for (const mode of ["write", "preview", "split"]) {
|
|
28068
|
+
const button = document.createElement("button");
|
|
28069
|
+
button.type = "button";
|
|
28070
|
+
button.classList.toggle("active", editorMode === mode);
|
|
28071
|
+
button.textContent = text2().editorModes[mode];
|
|
28072
|
+
button.addEventListener("click", () => {
|
|
28073
|
+
editorMode = mode;
|
|
28074
|
+
render();
|
|
28075
|
+
});
|
|
28076
|
+
modes.appendChild(button);
|
|
28077
|
+
}
|
|
28078
|
+
toolbar.appendChild(modes);
|
|
28079
|
+
body.addEventListener("input", () => {
|
|
28080
|
+
if (editorMode !== "write")
|
|
28081
|
+
renderTaskPreview(preview, body.value);
|
|
28082
|
+
});
|
|
28083
|
+
syncMode();
|
|
28084
|
+
const actions = document.createElement("div");
|
|
28085
|
+
actions.className = "journal-editor-actions";
|
|
28086
|
+
const save = document.createElement("button");
|
|
28087
|
+
save.type = "button";
|
|
28088
|
+
save.className = "journal-primary-action";
|
|
28089
|
+
save.textContent = task ? text2().saveTask : text2().addTask;
|
|
28090
|
+
save.addEventListener("click", async () => {
|
|
28091
|
+
setButtonBusy(save, true);
|
|
28092
|
+
const payload = {
|
|
28093
|
+
title: title.value,
|
|
28094
|
+
status: status.value,
|
|
28095
|
+
priority: priority.value,
|
|
28096
|
+
labels: labels.getLabels(),
|
|
28097
|
+
due_date: due.value || null,
|
|
28098
|
+
source_date: sourceDate.value || null,
|
|
28099
|
+
body: body.value
|
|
28100
|
+
};
|
|
28101
|
+
try {
|
|
28102
|
+
if (task)
|
|
28103
|
+
await requestJournal("update-task", { id: task.id, ...payload });
|
|
28104
|
+
else {
|
|
28105
|
+
const response = await requestJournal("add-task", payload, (next) => {
|
|
28106
|
+
if (next.task)
|
|
28107
|
+
selectedTaskId = next.task.id;
|
|
28108
|
+
});
|
|
28109
|
+
if (response.task)
|
|
28110
|
+
setRoute({ tab: "tasks", task: response.task.id });
|
|
28111
|
+
}
|
|
28112
|
+
} catch (error2) {
|
|
28113
|
+
message = error2 instanceof Error ? error2.message : text2().saveTaskFailed;
|
|
28114
|
+
deps.setStatus("error");
|
|
28115
|
+
render();
|
|
28116
|
+
} finally {
|
|
28117
|
+
setButtonBusy(save, false);
|
|
28118
|
+
}
|
|
28119
|
+
});
|
|
28120
|
+
actions.appendChild(save);
|
|
28121
|
+
if (task) {
|
|
28122
|
+
const claim = document.createElement("button");
|
|
28123
|
+
claim.type = "button";
|
|
28124
|
+
claim.textContent = text2().claim;
|
|
28125
|
+
claim.addEventListener("click", async () => {
|
|
28126
|
+
setButtonBusy(claim, true);
|
|
28127
|
+
try {
|
|
28128
|
+
await requestJournal("claim-task", { id: task.id, by: "user" });
|
|
28129
|
+
} catch (error2) {
|
|
28130
|
+
message = error2 instanceof Error ? error2.message : text2().claimTaskFailed;
|
|
28131
|
+
deps.setStatus("error");
|
|
28132
|
+
render();
|
|
28133
|
+
} finally {
|
|
28134
|
+
setButtonBusy(claim, false);
|
|
28135
|
+
}
|
|
28136
|
+
});
|
|
28137
|
+
const done = document.createElement("button");
|
|
28138
|
+
done.type = "button";
|
|
28139
|
+
done.textContent = text2().done;
|
|
28140
|
+
done.disabled = task.status !== "doing" || task.claim?.by !== "user";
|
|
28141
|
+
done.addEventListener("click", async () => {
|
|
28142
|
+
setButtonBusy(done, true);
|
|
28143
|
+
try {
|
|
28144
|
+
await requestJournal("complete-task", {
|
|
28145
|
+
id: task.id,
|
|
28146
|
+
by: "user",
|
|
28147
|
+
source: "user"
|
|
28148
|
+
});
|
|
28149
|
+
} catch (error2) {
|
|
28150
|
+
message = error2 instanceof Error ? error2.message : text2().doneTaskFailed;
|
|
28151
|
+
deps.setStatus("error");
|
|
28152
|
+
render();
|
|
28153
|
+
} finally {
|
|
28154
|
+
setButtonBusy(done, false);
|
|
28155
|
+
}
|
|
28156
|
+
});
|
|
28157
|
+
const del = document.createElement("button");
|
|
28158
|
+
del.type = "button";
|
|
28159
|
+
del.className = "journal-danger-action";
|
|
28160
|
+
del.innerHTML = `${iconSvg("octicon-trash", TRASH_16_PATH)}<span>${text2().delete}</span>`;
|
|
28161
|
+
del.addEventListener("click", async () => {
|
|
28162
|
+
const ok = await showConfirmDialog({
|
|
28163
|
+
title: `${text2().delete}?`,
|
|
28164
|
+
body: task.title,
|
|
28165
|
+
confirmLabel: text2().delete,
|
|
28166
|
+
danger: true,
|
|
28167
|
+
focusReturnTarget: del
|
|
28168
|
+
});
|
|
28169
|
+
if (!ok)
|
|
28170
|
+
return;
|
|
28171
|
+
setButtonBusy(del, true);
|
|
28172
|
+
try {
|
|
28173
|
+
const result = await requestJournal("delete-task", { id: task.id });
|
|
28174
|
+
if (!result.removed)
|
|
28175
|
+
throw new Error(text2().deleteTaskFailed);
|
|
28176
|
+
selectedTaskId = "";
|
|
28177
|
+
setRoute({ tab: "tasks", task: undefined });
|
|
28178
|
+
} catch (error2) {
|
|
28179
|
+
message = error2 instanceof Error ? error2.message : text2().deleteTaskFailed;
|
|
28180
|
+
deps.setStatus("error");
|
|
28181
|
+
render();
|
|
28182
|
+
} finally {
|
|
28183
|
+
setButtonBusy(del, false);
|
|
28184
|
+
}
|
|
28185
|
+
});
|
|
28186
|
+
actions.append(claim, done, del);
|
|
28187
|
+
}
|
|
28188
|
+
panel.append(resizer, heading2, title, row, labels.element, toolbar, body, preview, actions);
|
|
28189
|
+
return panel;
|
|
28190
|
+
}
|
|
28191
|
+
function renderTasksTab() {
|
|
28192
|
+
const wrap = document.createElement("div");
|
|
28193
|
+
wrap.className = "journal-tasks-layout";
|
|
28194
|
+
const toolbar = document.createElement("div");
|
|
28195
|
+
toolbar.className = "journal-task-toolbar";
|
|
28196
|
+
const labelInput = document.createElement("input");
|
|
28197
|
+
labelInput.type = "search";
|
|
28198
|
+
labelInput.placeholder = text2().labelFilterPlaceholder;
|
|
28199
|
+
labelInput.value = labelFilter;
|
|
28200
|
+
labelInput.addEventListener("change", () => {
|
|
28201
|
+
labelFilter = normalizeJournalLabel(labelInput.value) || "";
|
|
28202
|
+
setRoute({ tab: "tasks", label: labelFilter || undefined });
|
|
28203
|
+
});
|
|
28204
|
+
toolbar.append(labelInput, renderQueuePreview(), renderGithubIssueToggle(), renderLabelFilters());
|
|
28205
|
+
if (githubInboxOpen)
|
|
28206
|
+
toolbar.appendChild(renderGithubIssuesPanel());
|
|
28207
|
+
const boardWrap = document.createElement("div");
|
|
28208
|
+
boardWrap.className = "journal-task-board-wrap";
|
|
28209
|
+
boardWrap.appendChild(renderTaskBoard());
|
|
28210
|
+
wrap.append(toolbar, boardWrap, renderTaskEditor());
|
|
28211
|
+
return wrap;
|
|
28212
|
+
}
|
|
28213
|
+
function render() {
|
|
28214
|
+
root.setAttribute("aria-label", text2().ariaLabel);
|
|
28215
|
+
root.replaceChildren(renderHeader());
|
|
28216
|
+
const content = document.createElement("div");
|
|
28217
|
+
content.className = "journal-content";
|
|
28218
|
+
content.appendChild(activeTab === "journal" ? renderJournalTab() : renderTasksTab());
|
|
28219
|
+
root.appendChild(content);
|
|
28220
|
+
if (pendingTaskEditorFocus || pendingTaskEditorReveal)
|
|
28221
|
+
scheduleTaskEditorInteraction();
|
|
28222
|
+
}
|
|
28223
|
+
async function enter() {
|
|
28224
|
+
applyRoute();
|
|
28225
|
+
mount();
|
|
28226
|
+
render();
|
|
28227
|
+
await refresh();
|
|
28228
|
+
}
|
|
28229
|
+
function handleSse() {
|
|
28230
|
+
if (!mounted)
|
|
28231
|
+
return;
|
|
28232
|
+
if (draggingTaskId || draggingGithubIssueNumber !== null || pointerDrag || journalEditorHasFocus())
|
|
28233
|
+
return;
|
|
28234
|
+
refresh();
|
|
28235
|
+
}
|
|
28236
|
+
function localize() {
|
|
28237
|
+
if (mounted)
|
|
28238
|
+
render();
|
|
28239
|
+
}
|
|
28240
|
+
restoreTaskEditorWidth();
|
|
28241
|
+
document.addEventListener("keydown", handleGithubInboxKeydown);
|
|
28242
|
+
document.addEventListener("pointerdown", handleGithubInboxOutsidePointerDown);
|
|
28243
|
+
window.addEventListener("pointermove", handleTaskEditorResizeMove);
|
|
28244
|
+
window.addEventListener("pointerup", finishTaskEditorResize);
|
|
28245
|
+
window.addEventListener("pointercancel", finishTaskEditorResize);
|
|
28246
|
+
window.addEventListener("resize", () => {
|
|
28247
|
+
const layout = root.querySelector(".journal-tasks-layout");
|
|
28248
|
+
if (layout && layout.clientWidth <= 1100)
|
|
28249
|
+
return;
|
|
28250
|
+
const panel = root.querySelector(".journal-task-editor");
|
|
28251
|
+
if (panel)
|
|
28252
|
+
applyTaskEditorWidth(panel.getBoundingClientRect().width, false);
|
|
28253
|
+
});
|
|
28254
|
+
return { enter, suspend, handleSse, localize };
|
|
28255
|
+
}
|
|
28256
|
+
|
|
26180
28257
|
// web-src/core/html-escape.ts
|
|
26181
28258
|
function escapeHtml2(value) {
|
|
26182
28259
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -27700,7 +29777,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
27700
29777
|
} else {
|
|
27701
29778
|
renderFlat(files, ul, onFileClick);
|
|
27702
29779
|
}
|
|
27703
|
-
$("#totals").textContent = files.length ? `${files.length} file${files.length === 1 ? "" : "s"}` : "";
|
|
29780
|
+
$("#totals").textContent = !repoSidebar && files.length ? `${files.length} file${files.length === 1 ? "" : "s"}` : "";
|
|
27704
29781
|
const effectiveView = treeMode ? "tree" : STATE.sbView;
|
|
27705
29782
|
$$(".sb-view-seg button").forEach((b2) => {
|
|
27706
29783
|
b2.classList.toggle("active", b2.dataset.view === effectiveView);
|
|
@@ -30062,6 +32139,8 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
30062
32139
|
return true;
|
|
30063
32140
|
}
|
|
30064
32141
|
const SOURCE_SHIKI_LANGS = Array.from(new Set([
|
|
32142
|
+
"asciidoc",
|
|
32143
|
+
"astro",
|
|
30065
32144
|
"bash",
|
|
30066
32145
|
"bibtex",
|
|
30067
32146
|
"c",
|
|
@@ -30073,33 +32152,46 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
30073
32152
|
"dart",
|
|
30074
32153
|
"diff",
|
|
30075
32154
|
"dockerfile",
|
|
32155
|
+
"dotenv",
|
|
30076
32156
|
"elixir",
|
|
32157
|
+
"erb",
|
|
30077
32158
|
"erlang",
|
|
30078
32159
|
"fortran",
|
|
30079
32160
|
"go",
|
|
30080
32161
|
"gradle",
|
|
30081
32162
|
"graphql",
|
|
32163
|
+
"haml",
|
|
32164
|
+
"handlebars",
|
|
30082
32165
|
"haskell",
|
|
30083
32166
|
"html",
|
|
32167
|
+
"ini",
|
|
30084
32168
|
"java",
|
|
30085
32169
|
"javascript",
|
|
30086
32170
|
"json",
|
|
32171
|
+
"jsonc",
|
|
30087
32172
|
"julia",
|
|
30088
32173
|
"kotlin",
|
|
32174
|
+
"less",
|
|
32175
|
+
"liquid",
|
|
30089
32176
|
"lua",
|
|
30090
32177
|
"make",
|
|
30091
32178
|
"markdown",
|
|
30092
32179
|
"nix",
|
|
30093
32180
|
"ocaml",
|
|
32181
|
+
"pascal",
|
|
30094
32182
|
"perl",
|
|
30095
32183
|
"php",
|
|
32184
|
+
"powershell",
|
|
32185
|
+
"prisma",
|
|
30096
32186
|
"properties",
|
|
30097
32187
|
"protobuf",
|
|
32188
|
+
"pug",
|
|
30098
32189
|
"python",
|
|
30099
32190
|
"r",
|
|
30100
32191
|
"rst",
|
|
30101
32192
|
"ruby",
|
|
30102
32193
|
"rust",
|
|
32194
|
+
"sass",
|
|
30103
32195
|
"scala",
|
|
30104
32196
|
"scss",
|
|
30105
32197
|
"sql",
|
|
@@ -30107,6 +32199,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
30107
32199
|
"terraform",
|
|
30108
32200
|
"tex",
|
|
30109
32201
|
"toml",
|
|
32202
|
+
"twig",
|
|
30110
32203
|
"typescript",
|
|
30111
32204
|
"vim",
|
|
30112
32205
|
"vue",
|
|
@@ -32284,6 +34377,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32284
34377
|
repo: "Repository",
|
|
32285
34378
|
diff: "Diff Viewer",
|
|
32286
34379
|
history: "History",
|
|
34380
|
+
journal: "Work Log",
|
|
32287
34381
|
database: "Datastores",
|
|
32288
34382
|
help: "Help"
|
|
32289
34383
|
},
|
|
@@ -32421,6 +34515,103 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32421
34515
|
filterTitle: "Filter commits by message, SHA, author:name, or path:file.",
|
|
32422
34516
|
refreshTitle: "Refresh commit history"
|
|
32423
34517
|
},
|
|
34518
|
+
journal: {
|
|
34519
|
+
locale: "en",
|
|
34520
|
+
ariaLabel: "Work log and tasks",
|
|
34521
|
+
title: "Work Log",
|
|
34522
|
+
tabs: {
|
|
34523
|
+
journal: "Log",
|
|
34524
|
+
tasks: "Tasks"
|
|
34525
|
+
},
|
|
34526
|
+
refresh: "Refresh work log",
|
|
34527
|
+
loading: "loading...",
|
|
34528
|
+
loadFailed: "failed to load work log",
|
|
34529
|
+
statusLabels: {
|
|
34530
|
+
draft: "Draft",
|
|
34531
|
+
todo: "Todo",
|
|
34532
|
+
doing: "Doing",
|
|
34533
|
+
blocked: "Blocked",
|
|
34534
|
+
done: "Done"
|
|
34535
|
+
},
|
|
34536
|
+
priorityLabels: {
|
|
34537
|
+
p0: "P0",
|
|
34538
|
+
p1: "P1",
|
|
34539
|
+
p2: "P2",
|
|
34540
|
+
p3: "P3"
|
|
34541
|
+
},
|
|
34542
|
+
statusField: "Status",
|
|
34543
|
+
priorityField: "Priority",
|
|
34544
|
+
previousMonth: "Previous month",
|
|
34545
|
+
nextMonth: "Next month",
|
|
34546
|
+
weekDays: ["S", "M", "T", "W", "T", "F", "S"],
|
|
34547
|
+
noEntries: "No logs",
|
|
34548
|
+
noRelatedTasks: "No related tasks",
|
|
34549
|
+
noBody: "No body",
|
|
34550
|
+
relatedTasks: "Tasks",
|
|
34551
|
+
new: "New",
|
|
34552
|
+
titlePlaceholder: "Title",
|
|
34553
|
+
labelPlaceholder: "labels",
|
|
34554
|
+
entryBodyPlaceholder: "What did you work on today?",
|
|
34555
|
+
addEntry: "Add log",
|
|
34556
|
+
saveEntry: "Save log",
|
|
34557
|
+
delete: "Delete",
|
|
34558
|
+
deleteEntryFailed: "failed to delete log",
|
|
34559
|
+
saveEntryFailed: "failed to save log",
|
|
34560
|
+
moveTaskFailed: "failed to move task",
|
|
34561
|
+
aiQueue: "AI queue",
|
|
34562
|
+
empty: "Empty",
|
|
34563
|
+
duePrefix: "due",
|
|
34564
|
+
startDate: "Start date",
|
|
34565
|
+
endDate: "End date",
|
|
34566
|
+
removeLabel: (label) => `Remove ${label}`,
|
|
34567
|
+
claimedBy: (name) => `claimed by ${name}`,
|
|
34568
|
+
taskHeading: "Task",
|
|
34569
|
+
newTaskHeading: "New task",
|
|
34570
|
+
taskBodyPlaceholder: "Task details, acceptance checklist, notes",
|
|
34571
|
+
addTask: "Add task",
|
|
34572
|
+
saveTask: "Save task",
|
|
34573
|
+
saveTaskFailed: "failed to save task",
|
|
34574
|
+
claim: "Claim",
|
|
34575
|
+
claimTaskFailed: "failed to claim task",
|
|
34576
|
+
done: "Done",
|
|
34577
|
+
doneTaskFailed: "failed to complete task",
|
|
34578
|
+
deleteTaskFailed: "failed to delete task",
|
|
34579
|
+
labelFilterPlaceholder: "label",
|
|
34580
|
+
allLabels: "All",
|
|
34581
|
+
labelFilters: "Label filters",
|
|
34582
|
+
githubIssues: "GitHub Issues",
|
|
34583
|
+
githubRepoPlaceholder: "repo (optional)",
|
|
34584
|
+
githubLabelPlaceholder: "GitHub labels, comma separated",
|
|
34585
|
+
githubSearchPlaceholder: "search issues",
|
|
34586
|
+
githubStateLabels: {
|
|
34587
|
+
open: "Open",
|
|
34588
|
+
closed: "Closed",
|
|
34589
|
+
all: "All"
|
|
34590
|
+
},
|
|
34591
|
+
githubLoad: "Load issues",
|
|
34592
|
+
githubLoadMore: "Load more",
|
|
34593
|
+
githubLoading: "loading issues...",
|
|
34594
|
+
githubLoadFailed: "failed to load GitHub issues",
|
|
34595
|
+
githubShowing: (count, limit) => `Showing ${count} of ${limit}.`,
|
|
34596
|
+
githubRateLimited: (seconds) => `GitHub rate limit hit. Try again in ${seconds}s.`,
|
|
34597
|
+
githubNotLoaded: "GitHub issues are not loaded",
|
|
34598
|
+
githubNoIssues: "No GitHub issues",
|
|
34599
|
+
githubClose: "Close GitHub issues",
|
|
34600
|
+
githubLinked: "linked",
|
|
34601
|
+
githubAddToBoard: "Add to board",
|
|
34602
|
+
githubOpenTask: "Open task",
|
|
34603
|
+
githubDragHint: "Add or drag an issue to link it to a local task.",
|
|
34604
|
+
githubLinkTaskFailed: "failed to link GitHub issue",
|
|
34605
|
+
githubMemoLabel: "Memo:",
|
|
34606
|
+
moreTasks: (count) => `${count} more`,
|
|
34607
|
+
resizeTaskPanel: "Resize task panel",
|
|
34608
|
+
dragTask: "Drag task",
|
|
34609
|
+
editorModes: {
|
|
34610
|
+
write: "write",
|
|
34611
|
+
preview: "preview",
|
|
34612
|
+
split: "split"
|
|
34613
|
+
}
|
|
34614
|
+
},
|
|
32424
34615
|
quickHelp: {
|
|
32425
34616
|
buttonTitle: "quick help (shortcuts)",
|
|
32426
34617
|
panelTitle: "Quick Help",
|
|
@@ -32476,6 +34667,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32476
34667
|
repo: "リポジトリ",
|
|
32477
34668
|
diff: "Diff ビューア",
|
|
32478
34669
|
history: "履歴",
|
|
34670
|
+
journal: "ワークログ",
|
|
32479
34671
|
database: "データストア",
|
|
32480
34672
|
help: "ヘルプ"
|
|
32481
34673
|
},
|
|
@@ -32613,6 +34805,103 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32613
34805
|
filterTitle: "メッセージ、SHA、author:name、path:file でコミットを絞り込みます。",
|
|
32614
34806
|
refreshTitle: "コミット履歴を更新"
|
|
32615
34807
|
},
|
|
34808
|
+
journal: {
|
|
34809
|
+
locale: "ja",
|
|
34810
|
+
ariaLabel: "ワークログとタスク",
|
|
34811
|
+
title: "ワークログ",
|
|
34812
|
+
tabs: {
|
|
34813
|
+
journal: "ログ",
|
|
34814
|
+
tasks: "タスク"
|
|
34815
|
+
},
|
|
34816
|
+
refresh: "ワークログを更新",
|
|
34817
|
+
loading: "読み込み中...",
|
|
34818
|
+
loadFailed: "ワークログの読み込みに失敗しました",
|
|
34819
|
+
statusLabels: {
|
|
34820
|
+
draft: "下書き",
|
|
34821
|
+
todo: "未着手",
|
|
34822
|
+
doing: "進行中",
|
|
34823
|
+
blocked: "ブロック",
|
|
34824
|
+
done: "完了"
|
|
34825
|
+
},
|
|
34826
|
+
priorityLabels: {
|
|
34827
|
+
p0: "P0",
|
|
34828
|
+
p1: "P1",
|
|
34829
|
+
p2: "P2",
|
|
34830
|
+
p3: "P3"
|
|
34831
|
+
},
|
|
34832
|
+
statusField: "ステータス",
|
|
34833
|
+
priorityField: "優先度",
|
|
34834
|
+
previousMonth: "前の月",
|
|
34835
|
+
nextMonth: "次の月",
|
|
34836
|
+
weekDays: ["日", "月", "火", "水", "木", "金", "土"],
|
|
34837
|
+
noEntries: "ログはありません",
|
|
34838
|
+
noRelatedTasks: "関連タスクはありません",
|
|
34839
|
+
noBody: "本文はありません",
|
|
34840
|
+
relatedTasks: "タスク",
|
|
34841
|
+
new: "新規",
|
|
34842
|
+
titlePlaceholder: "タイトル",
|
|
34843
|
+
labelPlaceholder: "ラベル",
|
|
34844
|
+
entryBodyPlaceholder: "今日の作業を記録",
|
|
34845
|
+
addEntry: "ログを追加",
|
|
34846
|
+
saveEntry: "ログを保存",
|
|
34847
|
+
delete: "削除",
|
|
34848
|
+
deleteEntryFailed: "ログの削除に失敗しました",
|
|
34849
|
+
saveEntryFailed: "ログの保存に失敗しました",
|
|
34850
|
+
moveTaskFailed: "タスクの移動に失敗しました",
|
|
34851
|
+
aiQueue: "AIキュー",
|
|
34852
|
+
empty: "空",
|
|
34853
|
+
duePrefix: "期限",
|
|
34854
|
+
startDate: "開始日",
|
|
34855
|
+
endDate: "終了日",
|
|
34856
|
+
removeLabel: (label) => `${label} を削除`,
|
|
34857
|
+
claimedBy: (name) => `${name} が確保中`,
|
|
34858
|
+
taskHeading: "タスク",
|
|
34859
|
+
newTaskHeading: "新規タスク",
|
|
34860
|
+
taskBodyPlaceholder: "詳細、受け入れ条件、メモ",
|
|
34861
|
+
addTask: "タスクを追加",
|
|
34862
|
+
saveTask: "タスクを保存",
|
|
34863
|
+
saveTaskFailed: "タスクの保存に失敗しました",
|
|
34864
|
+
claim: "確保",
|
|
34865
|
+
claimTaskFailed: "タスクの確保に失敗しました",
|
|
34866
|
+
done: "完了",
|
|
34867
|
+
doneTaskFailed: "タスクの完了に失敗しました",
|
|
34868
|
+
deleteTaskFailed: "タスクの削除に失敗しました",
|
|
34869
|
+
labelFilterPlaceholder: "ラベル",
|
|
34870
|
+
allLabels: "すべて",
|
|
34871
|
+
labelFilters: "ラベルフィルタ",
|
|
34872
|
+
githubIssues: "GitHub Issue",
|
|
34873
|
+
githubRepoPlaceholder: "リポジトリ(任意)",
|
|
34874
|
+
githubLabelPlaceholder: "GitHubラベル(カンマ区切り)",
|
|
34875
|
+
githubSearchPlaceholder: "Issue検索",
|
|
34876
|
+
githubStateLabels: {
|
|
34877
|
+
open: "未解決",
|
|
34878
|
+
closed: "解決済み",
|
|
34879
|
+
all: "すべて"
|
|
34880
|
+
},
|
|
34881
|
+
githubLoad: "Issueを表示",
|
|
34882
|
+
githubLoadMore: "さらに読む",
|
|
34883
|
+
githubLoading: "Issueを読み込み中...",
|
|
34884
|
+
githubLoadFailed: "GitHub Issueの読み込みに失敗しました",
|
|
34885
|
+
githubShowing: (count, limit) => `${limit}件中${count}件を表示中。`,
|
|
34886
|
+
githubRateLimited: (seconds) => `GitHubの制限に達しました。${seconds}秒後に再試行してください。`,
|
|
34887
|
+
githubNotLoaded: "GitHub Issueは未読み込みです",
|
|
34888
|
+
githubNoIssues: "GitHub Issueは0件です",
|
|
34889
|
+
githubClose: "GitHub Issueを閉じる",
|
|
34890
|
+
githubLinked: "紐づき済み",
|
|
34891
|
+
githubAddToBoard: "看板へ追加",
|
|
34892
|
+
githubOpenTask: "タスクを開く",
|
|
34893
|
+
githubDragHint: "Issueを追加またはドラッグすると、自分用タスクとして紐づけます。",
|
|
34894
|
+
githubLinkTaskFailed: "GitHub Issueの紐づけに失敗しました",
|
|
34895
|
+
githubMemoLabel: "メモ:",
|
|
34896
|
+
moreTasks: (count) => `他${count}件`,
|
|
34897
|
+
resizeTaskPanel: "タスクパネル幅を変更",
|
|
34898
|
+
dragTask: "タスクをドラッグ",
|
|
34899
|
+
editorModes: {
|
|
34900
|
+
write: "編集",
|
|
34901
|
+
preview: "プレビュー",
|
|
34902
|
+
split: "分割"
|
|
34903
|
+
}
|
|
34904
|
+
},
|
|
32616
34905
|
quickHelp: {
|
|
32617
34906
|
buttonTitle: "クイックヘルプ(ショートカット)",
|
|
32618
34907
|
panelTitle: "クイックヘルプ",
|
|
@@ -32816,6 +35105,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32816
35105
|
button.setAttribute("aria-label", text2.history.refreshTitle);
|
|
32817
35106
|
});
|
|
32818
35107
|
relocalizeHistory?.();
|
|
35108
|
+
relocalizeJournal?.();
|
|
32819
35109
|
setElementText(".scope-settings-head strong", text2.settings.title);
|
|
32820
35110
|
const settingsClose = document.querySelector("#scope-settings-close");
|
|
32821
35111
|
settingsClose?.setAttribute("aria-label", text2.settings.close);
|
|
@@ -32885,6 +35175,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
32885
35175
|
relocalizeDatabase?.();
|
|
32886
35176
|
}
|
|
32887
35177
|
let relocalizeHistory = null;
|
|
35178
|
+
let relocalizeJournal = null;
|
|
32888
35179
|
let relocalizeDatabase = null;
|
|
32889
35180
|
let QUICK_HELP = null;
|
|
32890
35181
|
function setViewerLanguage(language, persist = true) {
|
|
@@ -33336,6 +35627,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33336
35627
|
return mount;
|
|
33337
35628
|
}
|
|
33338
35629
|
let ANNOTATIONS_UI = null;
|
|
35630
|
+
let JOURNAL_VIEW = null;
|
|
33339
35631
|
function applyInlineAnnotations() {
|
|
33340
35632
|
ANNOTATIONS_UI?.applyInlineAnnotations();
|
|
33341
35633
|
}
|
|
@@ -33413,6 +35705,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33413
35705
|
preHistoryRange = null;
|
|
33414
35706
|
removeFileHistoryShell2();
|
|
33415
35707
|
}
|
|
35708
|
+
if (previousRoute.screen === "journal" && nextRoute.screen !== "journal") {
|
|
35709
|
+
JOURNAL_VIEW?.suspend();
|
|
35710
|
+
}
|
|
33416
35711
|
STATE.route = nextRoute;
|
|
33417
35712
|
STATE.from = nextRoute.range.from;
|
|
33418
35713
|
STATE.to = nextRoute.range.to;
|
|
@@ -33435,6 +35730,12 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33435
35730
|
if (shouldDispatchFileRouteAfterSetRoute(previousRoute, nextRoute)) {
|
|
33436
35731
|
dispatchFileRoute(nextRoute);
|
|
33437
35732
|
}
|
|
35733
|
+
if (nextRoute.screen === "journal") {
|
|
35734
|
+
cancelActiveSourceLoad("navigation");
|
|
35735
|
+
setPageMode();
|
|
35736
|
+
removeStandaloneSource();
|
|
35737
|
+
JOURNAL_VIEW?.enter();
|
|
35738
|
+
}
|
|
33438
35739
|
}
|
|
33439
35740
|
function setQueryHistoryPanelOpen(open) {
|
|
33440
35741
|
const panel = document.getElementById("query-history-panel");
|
|
@@ -33459,6 +35760,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33459
35760
|
document.body.classList.toggle("gdp-history-page", historyPanelRoute);
|
|
33460
35761
|
document.body.classList.toggle("gdp-file-history-page", fileHistoryRoute);
|
|
33461
35762
|
document.body.classList.toggle("gdp-database-page", STATE.route.screen === "database");
|
|
35763
|
+
document.body.classList.toggle("gdp-journal-page", STATE.route.screen === "journal");
|
|
33462
35764
|
const repoTargetWrap = document.querySelector("#repo-target-wrap");
|
|
33463
35765
|
if (!repoSidebarRoute && repoTargetWrap) {
|
|
33464
35766
|
repoTargetWrap.hidden = true;
|
|
@@ -33509,6 +35811,12 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33509
35811
|
range: currentRange()
|
|
33510
35812
|
});
|
|
33511
35813
|
}
|
|
35814
|
+
if (link2.dataset.route === "journal") {
|
|
35815
|
+
link2.href = buildRoute({
|
|
35816
|
+
screen: "journal",
|
|
35817
|
+
range: currentRange()
|
|
35818
|
+
});
|
|
35819
|
+
}
|
|
33512
35820
|
if (link2.dataset.route === "database") {
|
|
33513
35821
|
link2.href = buildRoute({
|
|
33514
35822
|
screen: "database",
|
|
@@ -33682,6 +35990,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
33682
35990
|
focusDiffLine,
|
|
33683
35991
|
scrollDiffElementIntoView,
|
|
33684
35992
|
expandAllFileContext,
|
|
35993
|
+
loadDiffFile,
|
|
33685
35994
|
applyViewedState,
|
|
33686
35995
|
enqueueInitialLoads
|
|
33687
35996
|
} = DIFF_VIEW;
|
|
@@ -34323,6 +36632,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34323
36632
|
function reloadDiffFromUi(trigger) {
|
|
34324
36633
|
const topbarButton = $("#reload-prom");
|
|
34325
36634
|
topbarButton.classList.add("spinning");
|
|
36635
|
+
topbarButton.setAttribute("aria-busy", "true");
|
|
34326
36636
|
if (trigger && trigger !== topbarButton) {
|
|
34327
36637
|
trigger.classList.add("spinning");
|
|
34328
36638
|
trigger.setAttribute("aria-busy", "true");
|
|
@@ -34330,6 +36640,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34330
36640
|
load().finally(() => {
|
|
34331
36641
|
setTimeout(() => {
|
|
34332
36642
|
topbarButton.classList.remove("spinning");
|
|
36643
|
+
topbarButton.setAttribute("aria-busy", "false");
|
|
34333
36644
|
if (trigger && trigger !== topbarButton) {
|
|
34334
36645
|
trigger.classList.remove("spinning");
|
|
34335
36646
|
trigger.setAttribute("aria-busy", "false");
|
|
@@ -34349,6 +36660,11 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34349
36660
|
setStatus("live");
|
|
34350
36661
|
return Promise.resolve(null);
|
|
34351
36662
|
}
|
|
36663
|
+
if (STATE.route.screen === "journal") {
|
|
36664
|
+
JOURNAL_VIEW?.enter();
|
|
36665
|
+
setStatus("live");
|
|
36666
|
+
return Promise.resolve(null);
|
|
36667
|
+
}
|
|
34352
36668
|
if (STATE.route.screen === "file" && !(isFileHistoryRoute(STATE.route) && activeHistoryPathFilter) && dispatchFileRoute(STATE.route)) {
|
|
34353
36669
|
return Promise.resolve({
|
|
34354
36670
|
structureChanged: false,
|
|
@@ -34410,6 +36726,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34410
36726
|
} else if (STATE.route.screen === "database") {
|
|
34411
36727
|
setStatus("live");
|
|
34412
36728
|
DATABASE_VIEW.enter(STATE.route.db, STATE.route.schema, STATE.route.table, STATE.route.tab).then(() => ANNOTATIONS_UI?.applyInlineAnnotations());
|
|
36729
|
+
} else if (STATE.route.screen === "journal") {
|
|
36730
|
+
setStatus("live");
|
|
36731
|
+
JOURNAL_VIEW?.enter();
|
|
34413
36732
|
} else
|
|
34414
36733
|
load();
|
|
34415
36734
|
syncLineRefPill();
|
|
@@ -34589,6 +36908,17 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34589
36908
|
event.preventDefault();
|
|
34590
36909
|
closeDoctorSheet();
|
|
34591
36910
|
});
|
|
36911
|
+
JOURNAL_VIEW = createJournalView({
|
|
36912
|
+
getRoute: () => STATE.route,
|
|
36913
|
+
setRoute,
|
|
36914
|
+
currentRange,
|
|
36915
|
+
trackLoad,
|
|
36916
|
+
getText: () => uiText().journal,
|
|
36917
|
+
setPageMode,
|
|
36918
|
+
syncHeaderMenu,
|
|
36919
|
+
setStatus
|
|
36920
|
+
});
|
|
36921
|
+
relocalizeJournal = () => JOURNAL_VIEW?.localize();
|
|
34592
36922
|
const DATABASE_VIEW = createDatabaseView({
|
|
34593
36923
|
setRoute,
|
|
34594
36924
|
setPageMode,
|
|
@@ -34637,6 +36967,8 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34637
36967
|
nextRoute = normalizeInternalFileRoute(nextRoute);
|
|
34638
36968
|
if (previousRoute.screen === "database" && nextRoute.screen !== "database")
|
|
34639
36969
|
DATABASE_VIEW.suspend();
|
|
36970
|
+
if (previousRoute.screen === "journal" && nextRoute.screen !== "journal")
|
|
36971
|
+
JOURNAL_VIEW?.suspend();
|
|
34640
36972
|
if (isHistoryPanelRoute(previousRoute) && !isHistoryPanelRoute(nextRoute))
|
|
34641
36973
|
HISTORY_VIEW.leaveHistory();
|
|
34642
36974
|
if (isHistoryPanelRoute(previousRoute) && !isHistoryPanelRoute(nextRoute))
|
|
@@ -34691,6 +37023,14 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34691
37023
|
setStatus("live");
|
|
34692
37024
|
return;
|
|
34693
37025
|
}
|
|
37026
|
+
if (STATE.route.screen === "journal") {
|
|
37027
|
+
cancelActiveSourceLoad("navigation");
|
|
37028
|
+
setPageMode();
|
|
37029
|
+
removeStandaloneSource();
|
|
37030
|
+
JOURNAL_VIEW?.enter();
|
|
37031
|
+
setStatus("live");
|
|
37032
|
+
return;
|
|
37033
|
+
}
|
|
34694
37034
|
if (STATE.route.screen !== "file") {
|
|
34695
37035
|
cancelActiveSourceLoad("navigation");
|
|
34696
37036
|
setPageMode();
|
|
@@ -34811,6 +37151,7 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
34811
37151
|
focusDiffLine,
|
|
34812
37152
|
scrollDiffElementIntoView,
|
|
34813
37153
|
expandAllFileContext,
|
|
37154
|
+
loadDiffFile,
|
|
34814
37155
|
scrollToFile,
|
|
34815
37156
|
renderStandaloneSource,
|
|
34816
37157
|
removeStandaloneSource,
|
|
@@ -35158,6 +37499,9 @@ code-viewer query snapshot delete --id snap-abc123`
|
|
|
35158
37499
|
es.addEventListener("annotation", (event) => {
|
|
35159
37500
|
ANNOTATIONS_UI?.handleSse(event.data);
|
|
35160
37501
|
});
|
|
37502
|
+
es.addEventListener("journal", () => {
|
|
37503
|
+
JOURNAL_VIEW?.handleSse();
|
|
37504
|
+
});
|
|
35161
37505
|
es.addEventListener("db-query", (event) => {
|
|
35162
37506
|
DATABASE_VIEW.handleSse("db-query", event.data);
|
|
35163
37507
|
});
|