@youtyan/code-viewer 0.8.2 → 0.8.3
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 +6 -0
- package/dist/code-viewer.js +313 -168
- package/package.json +1 -1
- package/web/app.js +107 -15
- package/web/style.css +46 -0
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -26055,6 +26055,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
|
|
|
26055
26055
|
{
|
|
26056
26056
|
kind: "paragraph",
|
|
26057
26057
|
text: "In large repositories the sidebar loads folder children on demand. Folders you open are remembered and automatically re-expanded after a reload."
|
|
26058
|
+
},
|
|
26059
|
+
{
|
|
26060
|
+
kind: "paragraph",
|
|
26061
|
+
text: 'Symlinks show a distinct icon and a "→ target" label so they are never mistaken for a regular file or folder, and clicking one navigates to its resolved target. A broken symlink is visually flagged and disabled. Files with pending git changes (new, modified, renamed, deleted) show a status badge in the tree instead of the regular type icon.'
|
|
26058
26062
|
}
|
|
26059
26063
|
]
|
|
26060
26064
|
},
|
|
@@ -26722,6 +26726,10 @@ code-viewer query agent-help`
|
|
|
26722
26726
|
{
|
|
26723
26727
|
kind: "paragraph",
|
|
26724
26728
|
text: "大きいリポジトリではサイドバーがフォルダの中身を必要に応じて読み込みます。開いたフォルダは記憶され、次回のリロード時に同じ状態で展開し直されます。"
|
|
26729
|
+
},
|
|
26730
|
+
{
|
|
26731
|
+
kind: "paragraph",
|
|
26732
|
+
text: "シンボリックリンクは専用アイコンと「→ リンク先」ラベルで表示されるため通常のファイル/フォルダと区別でき、クリックするとリンク先に遷移します。リンク切れのシンボリックリンクは無効化されたことが分かる表示になります。未コミットの git 変更(新規・変更・リネーム・削除)があるファイルは、通常の種類アイコンの代わりにステータスバッジがツリーに表示されます。"
|
|
26725
26733
|
}
|
|
26726
26734
|
]
|
|
26727
26735
|
},
|
|
@@ -30719,6 +30727,11 @@ code-viewer query agent-help`
|
|
|
30719
30727
|
node.children_omitted = true;
|
|
30720
30728
|
node.children_omitted_reason = f2.children_omitted_reason;
|
|
30721
30729
|
}
|
|
30730
|
+
if (f2.is_symlink) {
|
|
30731
|
+
node.is_symlink = true;
|
|
30732
|
+
node.symlink_target = f2.symlink_target;
|
|
30733
|
+
node.resolved_path = f2.resolved_path;
|
|
30734
|
+
}
|
|
30722
30735
|
continue;
|
|
30723
30736
|
}
|
|
30724
30737
|
node.files.push(f2);
|
|
@@ -30768,6 +30781,8 @@ code-viewer query agent-help`
|
|
|
30768
30781
|
li.dataset.childrenOmittedReason = dir.children_omitted_reason;
|
|
30769
30782
|
if (dir.explicit)
|
|
30770
30783
|
li.dataset.explicit = "true";
|
|
30784
|
+
if (dir.is_symlink)
|
|
30785
|
+
li.classList.add("symlink-row");
|
|
30771
30786
|
if (dir.children_omitted) {
|
|
30772
30787
|
li.classList.add("children-omitted");
|
|
30773
30788
|
li.classList.add(dir.children_omitted_reason === "heavy" ? "children-omitted-heavy" : "children-omitted-internal");
|
|
@@ -30801,6 +30816,9 @@ code-viewer query agent-help`
|
|
|
30801
30816
|
omitted.title = badge.title;
|
|
30802
30817
|
label.appendChild(omitted);
|
|
30803
30818
|
}
|
|
30819
|
+
const dirSymlinkLabel = symlinkTargetLabel(dir);
|
|
30820
|
+
if (dirSymlinkLabel)
|
|
30821
|
+
label.appendChild(dirSymlinkLabel);
|
|
30804
30822
|
li.appendChild(label);
|
|
30805
30823
|
li.appendChild(createOpenPathButton(dir.path, "directory", openDirectoryInOsTitle()));
|
|
30806
30824
|
const collapsed = STATE.collapsedDirs.has(dir.path);
|
|
@@ -30839,7 +30857,8 @@ code-viewer query agent-help`
|
|
|
30839
30857
|
display_path: dir.path,
|
|
30840
30858
|
type: "tree",
|
|
30841
30859
|
children_omitted: dir.children_omitted,
|
|
30842
|
-
children_omitted_reason: dir.children_omitted_reason
|
|
30860
|
+
children_omitted_reason: dir.children_omitted_reason,
|
|
30861
|
+
resolved_path: dir.resolved_path
|
|
30843
30862
|
});
|
|
30844
30863
|
scheduleMainSurfaceFocus();
|
|
30845
30864
|
});
|
|
@@ -30902,6 +30921,11 @@ code-viewer query agent-help`
|
|
|
30902
30921
|
node.children_omitted = true;
|
|
30903
30922
|
node.children_omitted_reason = entry.children_omitted_reason;
|
|
30904
30923
|
}
|
|
30924
|
+
if (entry.is_symlink) {
|
|
30925
|
+
node.is_symlink = true;
|
|
30926
|
+
node.symlink_target = entry.symlink_target;
|
|
30927
|
+
node.resolved_path = entry.resolved_path;
|
|
30928
|
+
}
|
|
30905
30929
|
return;
|
|
30906
30930
|
}
|
|
30907
30931
|
if (!node.files.some((file) => file.path === entry.path))
|
|
@@ -30937,7 +30961,12 @@ code-viewer query agent-help`
|
|
|
30937
30961
|
type: meta.ref === "worktree" && entry.type === "commit" && !entry.submodule ? "tree" : entry.type,
|
|
30938
30962
|
submodule: entry.submodule,
|
|
30939
30963
|
children_omitted: entry.children_omitted,
|
|
30940
|
-
children_omitted_reason: entry.children_omitted_reason
|
|
30964
|
+
children_omitted_reason: entry.children_omitted_reason,
|
|
30965
|
+
is_symlink: entry.is_symlink,
|
|
30966
|
+
symlink_target: entry.symlink_target,
|
|
30967
|
+
symlink_target_type: entry.symlink_target_type,
|
|
30968
|
+
resolved_path: entry.resolved_path,
|
|
30969
|
+
status: entry.status
|
|
30941
30970
|
}));
|
|
30942
30971
|
mergeSidebarTreeEntries(entries);
|
|
30943
30972
|
SIDEBAR_LAZY_LOADED_DIRS.add(dir.path);
|
|
@@ -30957,6 +30986,8 @@ code-viewer query agent-help`
|
|
|
30957
30986
|
li.dataset.childrenOmittedReason = dir.children_omitted_reason;
|
|
30958
30987
|
if (dir.explicit)
|
|
30959
30988
|
li.dataset.explicit = "true";
|
|
30989
|
+
if (dir.is_symlink)
|
|
30990
|
+
li.classList.add("symlink-row");
|
|
30960
30991
|
if (dir.children_omitted) {
|
|
30961
30992
|
li.classList.add("children-omitted");
|
|
30962
30993
|
li.classList.add(dir.children_omitted_reason === "heavy" ? "children-omitted-heavy" : "children-omitted-internal");
|
|
@@ -30990,6 +31021,9 @@ code-viewer query agent-help`
|
|
|
30990
31021
|
omitted.title = badge.title;
|
|
30991
31022
|
label.appendChild(omitted);
|
|
30992
31023
|
}
|
|
31024
|
+
const dirSymlinkLabel = symlinkTargetLabel(dir);
|
|
31025
|
+
if (dirSymlinkLabel)
|
|
31026
|
+
label.appendChild(dirSymlinkLabel);
|
|
30993
31027
|
li.appendChild(label);
|
|
30994
31028
|
li.appendChild(createOpenPathButton(dir.path, "directory", openDirectoryInOsTitle()));
|
|
30995
31029
|
const updateIcon = () => {
|
|
@@ -31077,16 +31111,38 @@ code-viewer query agent-help`
|
|
|
31077
31111
|
return tag;
|
|
31078
31112
|
}
|
|
31079
31113
|
function sidebarEntryIcon(f2) {
|
|
31114
|
+
if (f2.is_symlink)
|
|
31115
|
+
return iconSvg("octicon-link", LINK_16_PATH);
|
|
31080
31116
|
return f2.type === "commit" ? iconSvg("octicon-git-branch", GIT_BRANCH_16_PATH) : fileEntryIcon();
|
|
31081
31117
|
}
|
|
31118
|
+
function symlinkTargetLabel(f2) {
|
|
31119
|
+
if (!f2.is_symlink)
|
|
31120
|
+
return null;
|
|
31121
|
+
const broken = f2.symlink_target_type === "missing";
|
|
31122
|
+
const label = document.createElement("span");
|
|
31123
|
+
label.className = broken ? "symlink-target broken" : "symlink-target";
|
|
31124
|
+
label.textContent = `→ ${f2.symlink_target || "?"}`;
|
|
31125
|
+
label.title = broken ? `Broken symlink → ${f2.symlink_target || ""}` : `Symlink → ${f2.symlink_target || ""}`;
|
|
31126
|
+
return label;
|
|
31127
|
+
}
|
|
31082
31128
|
function createTreeFileRow(f2, depth, onFileClick) {
|
|
31083
31129
|
const li = document.createElement("li");
|
|
31084
31130
|
li.className = "tree-file";
|
|
31085
31131
|
li.tabIndex = -1;
|
|
31086
31132
|
li.dataset.path = f2.path;
|
|
31087
31133
|
li.dataset.type = f2.type || "blob";
|
|
31134
|
+
const brokenSymlink = f2.is_symlink && f2.symlink_target_type === "missing";
|
|
31135
|
+
const deletedEntry = !!onFileClick && f2.status === "D";
|
|
31136
|
+
if (f2.is_symlink)
|
|
31137
|
+
li.classList.add("symlink-row");
|
|
31088
31138
|
if (f2.type === "commit") {
|
|
31089
31139
|
li.title = commitEntryBadge(f2.submodule).title;
|
|
31140
|
+
} else if (brokenSymlink) {
|
|
31141
|
+
li.classList.add("symlink-broken-row", "gdp-row-disabled");
|
|
31142
|
+
li.setAttribute("aria-disabled", "true");
|
|
31143
|
+
} else if (deletedEntry) {
|
|
31144
|
+
li.classList.add("gdp-row-disabled");
|
|
31145
|
+
li.setAttribute("aria-disabled", "true");
|
|
31090
31146
|
}
|
|
31091
31147
|
li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
|
|
31092
31148
|
li.classList.toggle("hidden-by-tests", STATE.hideTests && !isRepositorySidebarMode() && isTestPath(f2.path || ""));
|
|
@@ -31107,17 +31163,22 @@ code-viewer query agent-help`
|
|
|
31107
31163
|
name.textContent = f2.path.split("/").pop();
|
|
31108
31164
|
name.title = f2.path;
|
|
31109
31165
|
li.appendChild(name);
|
|
31166
|
+
const symlinkLabel = symlinkTargetLabel(f2);
|
|
31167
|
+
if (symlinkLabel)
|
|
31168
|
+
li.appendChild(symlinkLabel);
|
|
31110
31169
|
const kindTag = fileKindTag(f2);
|
|
31111
31170
|
if (kindTag)
|
|
31112
31171
|
li.appendChild(kindTag);
|
|
31113
31172
|
li.addEventListener("click", () => {
|
|
31173
|
+
if (brokenSymlink || deletedEntry)
|
|
31174
|
+
return;
|
|
31114
31175
|
if (onFileClick)
|
|
31115
31176
|
onFileClick(f2);
|
|
31116
31177
|
else
|
|
31117
31178
|
scrollToFile(f2.path);
|
|
31118
31179
|
scheduleMainSurfaceFocus();
|
|
31119
31180
|
});
|
|
31120
|
-
if (!onFileClick)
|
|
31181
|
+
if (!onFileClick && !brokenSymlink)
|
|
31121
31182
|
li.addEventListener("mouseenter", () => prefetchByPath(f2.path), {
|
|
31122
31183
|
passive: true
|
|
31123
31184
|
});
|
|
@@ -32022,7 +32083,8 @@ code-viewer query agent-help`
|
|
|
32022
32083
|
sortColumnLabels,
|
|
32023
32084
|
repositoryFallback,
|
|
32024
32085
|
repositoryRootFallback,
|
|
32025
|
-
commitEntryMeta
|
|
32086
|
+
commitEntryMeta,
|
|
32087
|
+
fileBadge
|
|
32026
32088
|
} = deps;
|
|
32027
32089
|
let REPO_SORT = {
|
|
32028
32090
|
key: "name",
|
|
@@ -32068,9 +32130,21 @@ code-viewer query agent-help`
|
|
|
32068
32130
|
function commitEntryIcon() {
|
|
32069
32131
|
return iconSvg("octicon-git-branch", GIT_BRANCH_16_PATH);
|
|
32070
32132
|
}
|
|
32133
|
+
function symlinkEntryIcon() {
|
|
32134
|
+
return iconSvg("octicon-link", LINK_16_PATH);
|
|
32135
|
+
}
|
|
32071
32136
|
function isWorktreeRef(ref) {
|
|
32072
32137
|
return canTrashWorktreeRef(ref);
|
|
32073
32138
|
}
|
|
32139
|
+
function repoEntryTypeIcon(entry, browsable, nonBrowsableCommit) {
|
|
32140
|
+
const icon = document.createElement("span");
|
|
32141
|
+
icon.className = browsable ? "dir-icon" : nonBrowsableCommit ? "d2h-icon-wrapper gdp-repo-row-gitlink-icon" : "d2h-icon-wrapper";
|
|
32142
|
+
if (browsable)
|
|
32143
|
+
setFolderIcon(icon, true);
|
|
32144
|
+
else
|
|
32145
|
+
icon.innerHTML = entry.is_symlink ? symlinkEntryIcon() : entry.type === "commit" ? commitEntryIcon() : fileEntryIcon();
|
|
32146
|
+
return icon;
|
|
32147
|
+
}
|
|
32074
32148
|
function canBrowseRepoEntry(entry, ref) {
|
|
32075
32149
|
return entry.type === "tree" || entry.type === "commit" && isWorktreeRef(ref) && !entry.submodule;
|
|
32076
32150
|
}
|
|
@@ -32448,28 +32522,31 @@ code-viewer query agent-help`
|
|
|
32448
32522
|
sortedRepoEntries(meta.entries, meta.ref).forEach((entry) => {
|
|
32449
32523
|
const browsable = canBrowseRepoEntry(entry, meta.ref);
|
|
32450
32524
|
const nonBrowsableCommit = entry.type === "commit" && !browsable;
|
|
32525
|
+
const brokenSymlink = !!entry.is_symlink && entry.symlink_target_type === "missing";
|
|
32526
|
+
const deletedEntry = entry.status === "D";
|
|
32451
32527
|
const row = document.createElement("button");
|
|
32452
32528
|
row.type = "button";
|
|
32453
|
-
row.className = nonBrowsableCommit ? `gdp-repo-row ${entry.type} gdp-repo-row-gitlink` : `gdp-repo-row ${entry.type}`;
|
|
32454
|
-
|
|
32455
|
-
|
|
32456
|
-
|
|
32457
|
-
setFolderIcon(icon, true);
|
|
32458
|
-
else
|
|
32459
|
-
icon.innerHTML = entry.type === "commit" ? commitEntryIcon() : fileEntryIcon();
|
|
32529
|
+
row.className = nonBrowsableCommit ? `gdp-repo-row ${entry.type} gdp-repo-row-gitlink` : entry.is_symlink ? `gdp-repo-row ${entry.type} symlink-row${brokenSymlink ? " symlink-broken-row gdp-row-disabled" : ""}` : `gdp-repo-row ${entry.type}`;
|
|
32530
|
+
if (deletedEntry)
|
|
32531
|
+
row.classList.add("gdp-row-disabled");
|
|
32532
|
+
const icon = entry.status ? fileBadge(entry.status) : repoEntryTypeIcon(entry, browsable, nonBrowsableCommit);
|
|
32460
32533
|
const name = document.createElement("span");
|
|
32461
32534
|
name.className = "name";
|
|
32462
32535
|
name.textContent = entry.name;
|
|
32463
32536
|
if (nonBrowsableCommit) {
|
|
32464
32537
|
row.title = commitEntryMeta(entry.submodule).title;
|
|
32465
32538
|
row.setAttribute("aria-disabled", "true");
|
|
32539
|
+
} else if (brokenSymlink || deletedEntry) {
|
|
32540
|
+
row.setAttribute("aria-disabled", "true");
|
|
32466
32541
|
}
|
|
32467
32542
|
const metaBlock = createRepoEntryMeta(entry, browsable);
|
|
32468
32543
|
const size = createRepoEntrySize(entry);
|
|
32469
32544
|
row.append(icon, name, metaBlock, size);
|
|
32470
32545
|
row.addEventListener("click", () => {
|
|
32546
|
+
if (brokenSymlink || deletedEntry)
|
|
32547
|
+
return;
|
|
32471
32548
|
if (browsable) {
|
|
32472
|
-
setRoute(repoRoute(meta.ref, entry.path));
|
|
32549
|
+
setRoute(repoRoute(meta.ref, entry.resolved_path ?? entry.path));
|
|
32473
32550
|
loadRepo();
|
|
32474
32551
|
} else if (entry.type === "blob") {
|
|
32475
32552
|
setRoute({
|
|
@@ -32578,12 +32655,17 @@ code-viewer query agent-help`
|
|
|
32578
32655
|
type: canBrowseRepoEntry(entry, normalizedRef) ? "tree" : entry.type,
|
|
32579
32656
|
submodule: entry.submodule,
|
|
32580
32657
|
children_omitted: entry.children_omitted,
|
|
32581
|
-
children_omitted_reason: entry.children_omitted_reason
|
|
32658
|
+
children_omitted_reason: entry.children_omitted_reason,
|
|
32659
|
+
is_symlink: entry.is_symlink,
|
|
32660
|
+
symlink_target: entry.symlink_target,
|
|
32661
|
+
symlink_target_type: entry.symlink_target_type,
|
|
32662
|
+
resolved_path: entry.resolved_path,
|
|
32663
|
+
status: entry.status
|
|
32582
32664
|
}));
|
|
32583
32665
|
setRepoSidebarRef(normalizedRef);
|
|
32584
32666
|
renderSidebar(files, (file) => {
|
|
32585
32667
|
if (file.type === "tree") {
|
|
32586
|
-
setRoute(repoRoute(normalizedRef, file.path));
|
|
32668
|
+
setRoute(repoRoute(normalizedRef, file.resolved_path ?? file.path));
|
|
32587
32669
|
loadRepo();
|
|
32588
32670
|
return;
|
|
32589
32671
|
}
|
|
@@ -32649,6 +32731,15 @@ code-viewer query agent-help`
|
|
|
32649
32731
|
meta.title = badge.title;
|
|
32650
32732
|
return meta;
|
|
32651
32733
|
}
|
|
32734
|
+
if (entry.is_symlink) {
|
|
32735
|
+
const broken = entry.symlink_target_type === "missing";
|
|
32736
|
+
meta.classList.add("symlink-target");
|
|
32737
|
+
if (broken)
|
|
32738
|
+
meta.classList.add("broken");
|
|
32739
|
+
meta.textContent = `→ ${entry.symlink_target || "?"}`;
|
|
32740
|
+
meta.title = broken ? `Broken symlink → ${entry.symlink_target || ""}` : `Symlink → ${entry.symlink_target || ""}`;
|
|
32741
|
+
return meta;
|
|
32742
|
+
}
|
|
32652
32743
|
const updated = formatFileDate(entry.updated_at || entry.commit_updated_at);
|
|
32653
32744
|
const created = formatFileDate(entry.created_at);
|
|
32654
32745
|
if (browsable && updated) {
|
|
@@ -36077,7 +36168,8 @@ code-viewer query agent-help`
|
|
|
36077
36168
|
commitEntryMeta: (submodule) => {
|
|
36078
36169
|
const text3 = uiText().repo;
|
|
36079
36170
|
return submodule ? { label: text3.submoduleLabel, title: text3.submoduleTitle } : { label: text3.gitlinkLabel, title: text3.gitlinkTitle };
|
|
36080
|
-
}
|
|
36171
|
+
},
|
|
36172
|
+
fileBadge: (status) => DIFF_VIEW.fileBadge(status)
|
|
36081
36173
|
});
|
|
36082
36174
|
const {
|
|
36083
36175
|
loadRepo,
|
package/web/style.css
CHANGED
|
@@ -2646,6 +2646,45 @@ body.gdp-help-page #content {
|
|
|
2646
2646
|
color: var(--accent-emph);
|
|
2647
2647
|
}
|
|
2648
2648
|
|
|
2649
|
+
/* "→ target" sublabel next to a symlink name in the sidebar tree, and the
|
|
2650
|
+
same marker's meta-column form in the repo listing (.gdp-repo-row
|
|
2651
|
+
.meta.symlink-target below). Uses --done (violet) rather than the usual
|
|
2652
|
+
muted meta color so a symlink row reads as distinct at a glance, not
|
|
2653
|
+
just on hover/title. Broken links (target missing/outside the repo)
|
|
2654
|
+
switch to the danger color instead. */
|
|
2655
|
+
.symlink-target {
|
|
2656
|
+
overflow: hidden;
|
|
2657
|
+
text-overflow: ellipsis;
|
|
2658
|
+
white-space: nowrap;
|
|
2659
|
+
color: var(--done);
|
|
2660
|
+
font-weight: 500;
|
|
2661
|
+
font-size: 12px;
|
|
2662
|
+
margin-left: 6px;
|
|
2663
|
+
}
|
|
2664
|
+
.symlink-target.broken { color: var(--danger); }
|
|
2665
|
+
/* Recolor the row's own type icon (file icon, or the shared blue dir-icon)
|
|
2666
|
+
to match the target label, so the symlink signal isn't limited to the
|
|
2667
|
+
sublabel text. */
|
|
2668
|
+
#filelist.tree .tree-file.symlink-row .d2h-icon-wrapper,
|
|
2669
|
+
#filelist.tree .tree-dir.symlink-row .dir-icon,
|
|
2670
|
+
.gdp-repo-row.symlink-row .d2h-icon-wrapper,
|
|
2671
|
+
.gdp-repo-row.symlink-row .dir-icon {
|
|
2672
|
+
color: var(--done);
|
|
2673
|
+
}
|
|
2674
|
+
.tree-file.symlink-broken-row .d2h-icon-wrapper,
|
|
2675
|
+
.gdp-repo-row.symlink-broken-row .d2h-icon-wrapper {
|
|
2676
|
+
color: var(--danger);
|
|
2677
|
+
}
|
|
2678
|
+
/* Shared "disabled row" treatment: a broken symlink or a synthesized
|
|
2679
|
+
deleted-file row (see deletedTreeEntriesForPath in preview.ts) - both are
|
|
2680
|
+
badged but cannot actually be opened. */
|
|
2681
|
+
.tree-file.gdp-row-disabled,
|
|
2682
|
+
.tree-dir.gdp-row-disabled,
|
|
2683
|
+
.gdp-repo-row.gdp-row-disabled {
|
|
2684
|
+
cursor: default;
|
|
2685
|
+
opacity: 0.7;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2649
2688
|
/* ===== Main content area ===== */
|
|
2650
2689
|
#content {
|
|
2651
2690
|
margin-left: var(--sidebar-w);
|
|
@@ -4595,6 +4634,13 @@ body.gdp-file-detail-page #empty {
|
|
|
4595
4634
|
font-size: 11px;
|
|
4596
4635
|
line-height: 1.4;
|
|
4597
4636
|
}
|
|
4637
|
+
.gdp-repo-row .meta.symlink-target.broken { color: var(--danger); }
|
|
4638
|
+
/* Disabled row (broken symlink or synthesized deleted-file entry): opening
|
|
4639
|
+
it would just 404, so no hover affordance - same treatment as
|
|
4640
|
+
.gdp-repo-row-gitlink above. */
|
|
4641
|
+
.gdp-repo-row.gdp-row-disabled:hover {
|
|
4642
|
+
background: var(--bg);
|
|
4643
|
+
}
|
|
4598
4644
|
.gdp-context-menu {
|
|
4599
4645
|
position: fixed;
|
|
4600
4646
|
z-index: 1000;
|