@young1lin/dsh-ui-gitworkbench 0.1.5 → 0.1.6
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/CHANGELOG.md +33 -0
- package/CHANGELOG_EN.md +33 -0
- package/lib/apply-blocks.js +159 -0
- package/lib/atomic-json.js +23 -5
- package/lib/blame.js +83 -0
- package/lib/client.js +24360 -1436
- package/lib/git-ops.js +25 -0
- package/lib/image-sniff.js +197 -0
- package/lib/index.js +401 -7
- package/lib/patch-model.js +223 -0
- package/lib/side-guard.js +55 -0
- package/lib/write-checked.js +164 -0
- package/package.json +7 -1
- package/src/apply-blocks.ts +215 -0
- package/src/atomic-json.ts +29 -5
- package/src/blame.ts +94 -0
- package/src/client/CodeEditor.tsx +317 -0
- package/src/client/FileBrowser.tsx +657 -0
- package/src/client/GitWorkbenchPanel.module.css +453 -7
- package/src/client/GitWorkbenchPanel.tsx +1436 -143
- package/src/client/ImageView.tsx +120 -0
- package/src/client/blame-gutter.ts +108 -0
- package/src/client/blame-view.ts +104 -0
- package/src/client/cm-diff.ts +108 -0
- package/src/client/cm-tokens.ts +79 -0
- package/src/client/diff-nav.ts +198 -0
- package/src/client/file-icon.ts +190 -0
- package/src/client/file-rows.ts +184 -0
- package/src/client/files-place.ts +178 -0
- package/src/client/glyphs.tsx +86 -0
- package/src/client/highlight.ts +25 -0
- package/src/client/idle-value.ts +53 -0
- package/src/client/image-view.ts +106 -0
- package/src/client/indent.ts +74 -0
- package/src/client/index.ts +59 -0
- package/src/client/locales.ts +171 -4
- package/src/client/pane-size.ts +71 -0
- package/src/client/side-edit.ts +244 -0
- package/src/client/side-rows.ts +258 -0
- package/src/client/stable-list.ts +31 -0
- package/src/client/use-change-nav.ts +83 -0
- package/src/client/worktree-view.ts +11 -1
- package/src/git-ops.ts +36 -1
- package/src/image-sniff.ts +204 -0
- package/src/index.ts +447 -7
- package/src/patch-model.ts +267 -0
- package/src/side-guard.ts +58 -0
- package/src/write-checked.ts +223 -0
|
@@ -501,6 +501,16 @@
|
|
|
501
501
|
}
|
|
502
502
|
.paneDivider:hover::after { opacity: 0.6; }
|
|
503
503
|
.paneDividerActive::after { opacity: 0.9; height: 48px; }
|
|
504
|
+
/* The same handle turned through a right angle. Both modifier rules below beat
|
|
505
|
+
the shared ones on specificity, so their order relative to them does not
|
|
506
|
+
matter — the stylesheet's usual "same value loses" trap does not apply. */
|
|
507
|
+
.paneDividerY {
|
|
508
|
+
width: auto; height: 7px;
|
|
509
|
+
cursor: row-resize;
|
|
510
|
+
}
|
|
511
|
+
.paneDividerY::before { top: 3px; bottom: auto; left: 0; right: 0; width: auto; height: 1px; }
|
|
512
|
+
.paneDividerY::after { width: 28px; height: 3px; transition: opacity 140ms ease, width 140ms ease; }
|
|
513
|
+
.paneDividerY.paneDividerActive::after { width: 48px; height: 3px; }
|
|
504
514
|
|
|
505
515
|
.header {
|
|
506
516
|
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
@@ -1310,12 +1320,15 @@
|
|
|
1310
1320
|
.commitLine {
|
|
1311
1321
|
display: flex; align-items: stretch;
|
|
1312
1322
|
flex: none;
|
|
1313
|
-
|
|
1323
|
+
/* Must equal GRAPH_ROW_H in GitWorkbenchPanel.tsx, or the lanes drawn per
|
|
1324
|
+
row stop meeting across the seam between rows. Guarded by
|
|
1325
|
+
tests/commit-row-height.test.ts. */
|
|
1326
|
+
height: 28px;
|
|
1314
1327
|
}
|
|
1315
1328
|
.graphCell { flex: none; display: block; }
|
|
1316
1329
|
/* An inset row, like dsh's own session rows: 12px radius, filled when active. */
|
|
1317
1330
|
.commit {
|
|
1318
|
-
display: flex;
|
|
1331
|
+
display: flex; align-items: center; gap: 10px;
|
|
1319
1332
|
flex: 1 1 auto; min-width: 0; box-sizing: border-box;
|
|
1320
1333
|
height: 100%;
|
|
1321
1334
|
margin: 1px 0;
|
|
@@ -1332,14 +1345,24 @@
|
|
|
1332
1345
|
}
|
|
1333
1346
|
.commit:hover { background: var(--gs-raise); }
|
|
1334
1347
|
.commitActive { border-color: var(--gs-accent-border); background: var(--gs-accent-bg); }
|
|
1335
|
-
|
|
1336
|
-
|
|
1348
|
+
/* Pushed to the right end of the row, and never squeezed: the subject is what
|
|
1349
|
+
gives way when the pane narrows, because it is the part with an ellipsis to
|
|
1350
|
+
give. */
|
|
1351
|
+
.commitMeta { flex: none; display: flex; align-items: baseline; gap: 8px; }
|
|
1352
|
+
.commitHash { flex: none; color: var(--gs-info); }
|
|
1337
1353
|
.commitSubjectRow {
|
|
1338
1354
|
display: flex; align-items: baseline; gap: 4px;
|
|
1339
|
-
min-width: 0;
|
|
1355
|
+
flex: 1 1 auto; min-width: 0;
|
|
1340
1356
|
}
|
|
1357
|
+
/* Shrinks, never grows. Growing was invisible while the list was a 26%%
|
|
1358
|
+
column — the subject filled the row either way — and became a defect the
|
|
1359
|
+
moment the list spanned the drawer: a subject that takes ALL the free space
|
|
1360
|
+
pushes the has-a-body marker a thousand pixels right, where it reads as an
|
|
1361
|
+
unexplained ellipsis next to the author rather than as "there is more to
|
|
1362
|
+
this message". The row still pushes author and date to the right end;
|
|
1363
|
+
.commitSubjectRow is what absorbs the slack. */
|
|
1341
1364
|
.commitSubject {
|
|
1342
|
-
flex: 1; min-width: 0;
|
|
1365
|
+
flex: 0 1 auto; min-width: 0;
|
|
1343
1366
|
overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
|
|
1344
1367
|
color: var(--gs-fg-muted);
|
|
1345
1368
|
}
|
|
@@ -1366,6 +1389,27 @@
|
|
|
1366
1389
|
}
|
|
1367
1390
|
|
|
1368
1391
|
.body { display: flex; flex: 1; min-height: 0; z-index: 1; }
|
|
1392
|
+
/* History stacks: the commit list over the tree-and-diff row. `.bodyRow` is
|
|
1393
|
+
rendered on every tab, so the unstacked tabs are a row inside a row — which
|
|
1394
|
+
costs nothing and keeps one JSX shape instead of two. */
|
|
1395
|
+
.body[data-stacked] { flex-direction: column; }
|
|
1396
|
+
.bodyRow { display: flex; flex: 1 1 0; min-width: 0; min-height: 0; }
|
|
1397
|
+
/* Stacked, the list is sized by HEIGHT and spans the drawer; the width rules
|
|
1398
|
+
it carries for the column layout would otherwise pin it to 26%. */
|
|
1399
|
+
.body[data-stacked] .commitsPane {
|
|
1400
|
+
width: auto; max-width: none; min-width: 0;
|
|
1401
|
+
height: 34%; flex: none;
|
|
1402
|
+
/* The drag's clamp, restated where a RESIZE can also reach it. An inline
|
|
1403
|
+
`height` in pixels survives the window getting shorter, and then the list
|
|
1404
|
+
is taller than the body: the lower half goes to zero and the handle ends
|
|
1405
|
+
up below the bottom edge with nothing to grab. Percentages re-resolve on
|
|
1406
|
+
every layout, which is exactly what the stored pixel height cannot do.
|
|
1407
|
+
Both numbers come from the panel's own constants, so there is still one
|
|
1408
|
+
home for them. `max()` keeps the range from inverting in a drawer too
|
|
1409
|
+
short to hold both floors — the list then simply takes its minimum. */
|
|
1410
|
+
min-height: var(--gs-min-commits-tall);
|
|
1411
|
+
max-height: max(var(--gs-min-commits-tall), calc(100% - var(--gs-min-stacked-lower)));
|
|
1412
|
+
}
|
|
1369
1413
|
|
|
1370
1414
|
/* File tree column. A peer of the commit pane and the diff, never their parent:
|
|
1371
1415
|
each of the three scrolls on its own and keeps its full height. */
|
|
@@ -1553,7 +1597,29 @@
|
|
|
1553
1597
|
.fileCountDel { color: var(--gs-del); }
|
|
1554
1598
|
|
|
1555
1599
|
/* diff pane */
|
|
1556
|
-
|
|
1600
|
+
/* A column, so a header can sit above the scrolled diff instead of inside it.
|
|
1601
|
+
`overflow: auto` stays as the outer guard — the rename line and the
|
|
1602
|
+
side-by-side pane are also children here, and one of them growing must
|
|
1603
|
+
scroll rather than be clipped. */
|
|
1604
|
+
.diffPane {
|
|
1605
|
+
flex: 1 1 0; min-width: var(--gs-min-diff);
|
|
1606
|
+
display: flex; flex-direction: column;
|
|
1607
|
+
overflow: auto; background: var(--gs-surface);
|
|
1608
|
+
}
|
|
1609
|
+
/* The unified view: its own header and its own scroller, filling whatever the
|
|
1610
|
+
pane has left after a rename line. */
|
|
1611
|
+
.diffWrap { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; width: 100%; }
|
|
1612
|
+
.diffScroll { flex: 1 1 0; min-height: 0; overflow: auto; outline: none; }
|
|
1613
|
+
/* The walk's controls. Right-aligned and quiet: they belong to the diff, and
|
|
1614
|
+
the diff is what the reader came for. Same shape as `.sideNav` in the
|
|
1615
|
+
side-by-side pane, because it is the same control. */
|
|
1616
|
+
.diffNav {
|
|
1617
|
+
flex: none;
|
|
1618
|
+
display: flex; justify-content: flex-end; align-items: center; gap: 4px;
|
|
1619
|
+
padding: 3px 12px 3px 0;
|
|
1620
|
+
border-bottom: 1px solid var(--gs-border-soft);
|
|
1621
|
+
}
|
|
1622
|
+
.diffNav .blockBtn { padding-inline: 6px; line-height: 0; }
|
|
1557
1623
|
/* The pre is as wide as its longest line (`max-content`) and at least the pane
|
|
1558
1624
|
(`min-width: 100%`). Each row then stretches to that width, so add/delete
|
|
1559
1625
|
tints survive horizontal scroll instead of stopping at the viewport edge. */
|
|
@@ -1570,6 +1636,7 @@
|
|
|
1570
1636
|
tab-size: 4;
|
|
1571
1637
|
}
|
|
1572
1638
|
.renameLine {
|
|
1639
|
+
flex: none;
|
|
1573
1640
|
padding: 8px 16px;
|
|
1574
1641
|
border-bottom: 1px solid var(--gs-border-soft);
|
|
1575
1642
|
color: var(--gs-info);
|
|
@@ -1628,6 +1695,205 @@
|
|
|
1628
1695
|
.wordAdd { background: var(--gs-add-word); border-radius: 2px; }
|
|
1629
1696
|
.wordDel { background: var(--gs-del-word); border-radius: 2px; }
|
|
1630
1697
|
|
|
1698
|
+
/* Side-by-side diff pane. One grid holds EVERY row's four cells — a per-row
|
|
1699
|
+
grid would let the column boundary drift with each row's content, and a
|
|
1700
|
+
fixed split would clip long lines. Equal 1fr code tracks keep the two
|
|
1701
|
+
columns identically wide however wide the widest line is, and the whole pane
|
|
1702
|
+
(this view's single shared vertical scroll) carries the horizontal one. */
|
|
1703
|
+
/* The pane is a HEADER plus a scroller, not one wide column: the tab row
|
|
1704
|
+
carries Save/Revert, and a row sized to the widest line of code pushes them
|
|
1705
|
+
off the right edge of a long file — reachable only by scrolling sideways,
|
|
1706
|
+
which is not where anyone looks for a toolbar. Only `.sideScroll` scrolls;
|
|
1707
|
+
the tabs and the notices above it stay put on both axes. */
|
|
1708
|
+
.sidePane { display: flex; flex-direction: column; width: 100%; min-width: 0; height: 100%; }
|
|
1709
|
+
/* Vertical scrolling belongs to the pane; horizontal belongs to each column,
|
|
1710
|
+
which is what lets the divider mean something. A single scroller for both
|
|
1711
|
+
axes would tie the two sides' horizontal position together and size the
|
|
1712
|
+
whole grid to the widest line in the file. */
|
|
1713
|
+
.sideScroll { flex: 1 1 0; min-height: 0; overflow-y: auto; overflow-x: hidden; }
|
|
1714
|
+
.sideCols { display: flex; align-items: stretch; min-height: 100%; }
|
|
1715
|
+
/* `min-width: 0` is what lets a flex column be narrower than its content —
|
|
1716
|
+
without it the column refuses to shrink and the divider stops moving. */
|
|
1717
|
+
.sideCol { flex: 0 0 auto; min-width: 0; overflow-x: auto; }
|
|
1718
|
+
.sideColRight { flex: 1 1 0; }
|
|
1719
|
+
.sideColGrid {
|
|
1720
|
+
display: grid;
|
|
1721
|
+
grid-template-columns: 3.2em 1fr;
|
|
1722
|
+
align-content: start;
|
|
1723
|
+
padding: 8px 0 16px;
|
|
1724
|
+
min-width: 100%; width: max-content;
|
|
1725
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
1726
|
+
font-size: var(--gs-t-dense); line-height: 20px; font-weight: 400;
|
|
1727
|
+
font-variant-ligatures: none;
|
|
1728
|
+
font-feature-settings: 'liga' 0, 'calt' 0;
|
|
1729
|
+
tab-size: 4;
|
|
1730
|
+
}
|
|
1731
|
+
/* The blame gutter, on the working-tree column only. Fixed width so the code
|
|
1732
|
+
beside it does not shift line to line, and clipped rather than wrapped: a
|
|
1733
|
+
gutter row that grew to two lines would break the alignment the whole
|
|
1734
|
+
side-by-side view rests on. */
|
|
1735
|
+
.sideColGridBlame { grid-template-columns: 13em 3.2em 1fr; }
|
|
1736
|
+
.blameCell {
|
|
1737
|
+
box-sizing: border-box;
|
|
1738
|
+
padding: 0 8px 0 10px;
|
|
1739
|
+
border-right: 1px solid var(--gs-border-soft);
|
|
1740
|
+
color: var(--gs-fg-faint);
|
|
1741
|
+
font-size: var(--gs-t-meta);
|
|
1742
|
+
white-space: pre; overflow: hidden; text-overflow: ellipsis;
|
|
1743
|
+
user-select: none;
|
|
1744
|
+
}
|
|
1745
|
+
.sideTabs {
|
|
1746
|
+
z-index: 5;
|
|
1747
|
+
display: flex; gap: 16px;
|
|
1748
|
+
flex: none;
|
|
1749
|
+
padding: 6px 16px 0;
|
|
1750
|
+
border-bottom: 1px solid var(--gs-border-soft);
|
|
1751
|
+
background: var(--gs-surface-2);
|
|
1752
|
+
}
|
|
1753
|
+
.sideTab {
|
|
1754
|
+
position: relative;
|
|
1755
|
+
padding: 2px 0 5px;
|
|
1756
|
+
border: none;
|
|
1757
|
+
background: transparent;
|
|
1758
|
+
font-family: inherit;
|
|
1759
|
+
font-size: var(--gs-t-meta); line-height: 16px; font-weight: 500;
|
|
1760
|
+
color: var(--gs-fg-dim);
|
|
1761
|
+
cursor: pointer;
|
|
1762
|
+
}
|
|
1763
|
+
.sideTab::after {
|
|
1764
|
+
content: '';
|
|
1765
|
+
position: absolute; right: 0; bottom: 1px; left: 0;
|
|
1766
|
+
height: 2px; border-radius: 2px;
|
|
1767
|
+
background: transparent;
|
|
1768
|
+
}
|
|
1769
|
+
.sideTabActive { color: var(--gs-accent); }
|
|
1770
|
+
.sideTabActive::after { background: var(--gs-accent); }
|
|
1771
|
+
.sideNotice {
|
|
1772
|
+
flex: none;
|
|
1773
|
+
padding: 8px 16px;
|
|
1774
|
+
border-bottom: 1px solid var(--gs-border-soft);
|
|
1775
|
+
color: var(--gs-info);
|
|
1776
|
+
font-size: var(--gs-t-dense);
|
|
1777
|
+
}
|
|
1778
|
+
.sideNum {
|
|
1779
|
+
box-sizing: border-box;
|
|
1780
|
+
padding: 0 8px;
|
|
1781
|
+
text-align: right;
|
|
1782
|
+
color: var(--gs-fg-fainter);
|
|
1783
|
+
user-select: none;
|
|
1784
|
+
font-variant-numeric: tabular-nums;
|
|
1785
|
+
}
|
|
1786
|
+
.sideNumAdd { background: var(--gs-add-num, var(--gs-add-line)); }
|
|
1787
|
+
.sideNumDel { background: var(--gs-del-num, var(--gs-del-line)); }
|
|
1788
|
+
.sideCode { white-space: pre; padding: 0 16px 0 10px; min-height: 20px; }
|
|
1789
|
+
.sideCodeSame { color: var(--gs-fg-muted); }
|
|
1790
|
+
.sideCodeAdd { background: var(--gs-add-line); }
|
|
1791
|
+
.sideCodeDel { background: var(--gs-del-line); }
|
|
1792
|
+
/* The block affordance: a changed row's cells carry the block tint above, and
|
|
1793
|
+
the hovered block's every cell — code and gutter — takes the hot outline,
|
|
1794
|
+
so the reader sees the unit the buttons act on rather than the one cell the
|
|
1795
|
+
pointer happens to be over. `.sideCellBlock` also positions the action bar,
|
|
1796
|
+
which a cell of the block's first row carries. */
|
|
1797
|
+
.sideCellBlock { position: relative; }
|
|
1798
|
+
.sideBlockHot { outline: 1px solid var(--gs-accent); outline-offset: -1px; }
|
|
1799
|
+
|
|
1800
|
+
/* The hovered block's actions, floating on the row above the block. It lives
|
|
1801
|
+
INSIDE the block's first code cell, so it travels with the column and the
|
|
1802
|
+
scroll, and it only ever covers context — never the change it acts on. The
|
|
1803
|
+
surface and shadow are what keep it readable over code. */
|
|
1804
|
+
.blockBar {
|
|
1805
|
+
position: absolute; top: -21px; left: 0; z-index: 4;
|
|
1806
|
+
display: flex; gap: 4px;
|
|
1807
|
+
padding: 2px;
|
|
1808
|
+
border: 1px solid var(--gs-border);
|
|
1809
|
+
border-radius: var(--gs-r-control);
|
|
1810
|
+
background: var(--gs-surface-2, var(--gs-bg));
|
|
1811
|
+
box-shadow: 0 4px 14px var(--gs-shadow);
|
|
1812
|
+
white-space: nowrap;
|
|
1813
|
+
}
|
|
1814
|
+
.blockBtn {
|
|
1815
|
+
padding: 0 8px;
|
|
1816
|
+
border: 1px solid var(--gs-border); border-radius: var(--gs-r-pill);
|
|
1817
|
+
background: var(--gs-bg); color: var(--gs-fg-dim);
|
|
1818
|
+
font-family: inherit; font-size: var(--gs-t-meta); line-height: 16px;
|
|
1819
|
+
cursor: pointer;
|
|
1820
|
+
}
|
|
1821
|
+
.blockBtn:hover:not(:disabled) { color: var(--gs-fg); border-color: var(--gs-accent); }
|
|
1822
|
+
.blockBtn:disabled { opacity: 0.45; cursor: default; }
|
|
1823
|
+
/* The roll-back button stays red at rest, not only on hover: it is the one
|
|
1824
|
+
action in the bar that cannot be undone, and arriving under the pointer is
|
|
1825
|
+
not what should make that visible. */
|
|
1826
|
+
.blockBtnDanger { color: var(--gs-del); border-color: var(--gs-del-bg, var(--gs-border)); }
|
|
1827
|
+
.blockBtnDanger:hover:not(:disabled) { color: var(--gs-del); border-color: var(--gs-del); }
|
|
1828
|
+
|
|
1829
|
+
/* The editable right column. The working-tree column is readable but visibly
|
|
1830
|
+
a text surface before editing is armed; the tab row carries the arm/save
|
|
1831
|
+
cluster at its right end. */
|
|
1832
|
+
.sideArmable { cursor: text; }
|
|
1833
|
+
|
|
1834
|
+
/* An editable buffer says so — the one thing a rendered code pane cannot say
|
|
1835
|
+
by itself.
|
|
1836
|
+
|
|
1837
|
+
Nothing about monospaced text on a background looks like a field. In the
|
|
1838
|
+
Files tab the editor is live the moment the file opens, so without a mark
|
|
1839
|
+
the reader finds out whether they may type by typing — and finds out they
|
|
1840
|
+
may not the same way, on a pane that silently swallows it. The rule runs
|
|
1841
|
+
the full height of the editor's leading edge: dim while nothing is focused
|
|
1842
|
+
(present, not loud — it sits beside code someone came to read), full accent
|
|
1843
|
+
once the caret is actually inside, which doubles as "this pane has the
|
|
1844
|
+
keyboard".
|
|
1845
|
+
|
|
1846
|
+
Driven by an attribute the component sets from the same boolean that
|
|
1847
|
+
configures `EditorState.readOnly`, so the rule cannot appear on a pane that
|
|
1848
|
+
would refuse the keystroke. `pointer-events: none` because the bar overlays
|
|
1849
|
+
the gutter's first two pixels, and a click there must still reach the
|
|
1850
|
+
view. */
|
|
1851
|
+
.cmHost { position: relative; }
|
|
1852
|
+
.cmHost[data-editable]::before {
|
|
1853
|
+
content: '';
|
|
1854
|
+
position: absolute;
|
|
1855
|
+
top: 0; bottom: 0; left: 0;
|
|
1856
|
+
width: 2px;
|
|
1857
|
+
border-radius: 1px;
|
|
1858
|
+
background: var(--gs-accent);
|
|
1859
|
+
opacity: 0.32;
|
|
1860
|
+
transition: opacity 140ms ease;
|
|
1861
|
+
pointer-events: none;
|
|
1862
|
+
}
|
|
1863
|
+
.cmHost[data-editable]:focus-within::before { opacity: 1; }
|
|
1864
|
+
/* The change nav takes the free space, so it and `.sideActions` after it sit
|
|
1865
|
+
together at the right end. Only the FIRST auto margin absorbs anything —
|
|
1866
|
+
the one on `.sideActions` then has nothing left to take, which is what
|
|
1867
|
+
keeps the two groups adjacent rather than pushed to opposite ends. */
|
|
1868
|
+
.sideNav { margin-left: auto; display: flex; gap: 4px; align-items: center; padding: 2px 0 5px; }
|
|
1869
|
+
.sideNav .blockBtn { padding-inline: 6px; line-height: 0; }
|
|
1870
|
+
.sideNavCount { color: var(--gs-fg-faint); font-size: var(--gs-t-meta); white-space: nowrap; }
|
|
1871
|
+
.sideActions { margin-left: auto; display: flex; gap: 4px; align-items: flex-start; padding: 2px 0 5px; }
|
|
1872
|
+
/* Save gains the accent only once there is something to save: the button's
|
|
1873
|
+
whole enabled state is the dirty flag, and colour says it sooner than the
|
|
1874
|
+
pointer does. */
|
|
1875
|
+
.sideSaveReady { color: var(--gs-accent); border-color: var(--gs-accent); }
|
|
1876
|
+
|
|
1877
|
+
/* The reload-or-overwrite banner (§4) and the failed-save banner: a warning
|
|
1878
|
+
strip under the tabs carrying its own actions, so the decision it asks for
|
|
1879
|
+
is one click away rather than a sentence away. */
|
|
1880
|
+
.sideBanner {
|
|
1881
|
+
flex: none;
|
|
1882
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
1883
|
+
padding: 8px 16px;
|
|
1884
|
+
border-bottom: 1px solid var(--gs-border-soft);
|
|
1885
|
+
background: var(--gs-del-bg, transparent);
|
|
1886
|
+
color: var(--gs-del);
|
|
1887
|
+
font-size: var(--gs-t-dense);
|
|
1888
|
+
}
|
|
1889
|
+
.sideBannerTitle { font-weight: 600; }
|
|
1890
|
+
.sideBannerActs { display: flex; gap: 4px; }
|
|
1891
|
+
|
|
1892
|
+
/* The editor grid item that positions the textarea. It spans exactly the
|
|
1893
|
+
buffer's rows of the fourth column; the textarea fills it absolutely. */
|
|
1894
|
+
.sideEditCell { position: relative; }
|
|
1895
|
+
|
|
1896
|
+
|
|
1631
1897
|
/* No syntax-tint classes here: Shiki resolves a real TextMate theme and emits a
|
|
1632
1898
|
hex per token, so the panel writes colour inline. A four-class approximation
|
|
1633
1899
|
painted identifiers, types and punctuation all one colour, which is what made
|
|
@@ -2027,3 +2293,183 @@
|
|
|
2027
2293
|
font-weight: 600;
|
|
2028
2294
|
}
|
|
2029
2295
|
.btn.btnDanger:hover:not(:disabled) { border-color: var(--gs-del); background: var(--gs-del); color: var(--gs-on-accent, #fff); }
|
|
2296
|
+
|
|
2297
|
+
/* Files tab: the repository browser's tree, and the pane that opens a file.
|
|
2298
|
+
Modifier rules stay BELOW the base rule they narrow — a modifier declared
|
|
2299
|
+
first loses the cascade, which the drawer-chrome test checks for. */
|
|
2300
|
+
.fbTree {
|
|
2301
|
+
flex: none;
|
|
2302
|
+
min-width: var(--gs-min-tree);
|
|
2303
|
+
display: flex;
|
|
2304
|
+
flex-direction: column;
|
|
2305
|
+
gap: 4px;
|
|
2306
|
+
padding: 6px;
|
|
2307
|
+
overflow: auto;
|
|
2308
|
+
background: var(--gs-surface-2);
|
|
2309
|
+
}
|
|
2310
|
+
.fbSearch {
|
|
2311
|
+
flex: none;
|
|
2312
|
+
width: 100%;
|
|
2313
|
+
box-sizing: border-box;
|
|
2314
|
+
padding: 4px 6px;
|
|
2315
|
+
font: inherit;
|
|
2316
|
+
font-size: var(--gs-t-meta);
|
|
2317
|
+
color: var(--gs-fg);
|
|
2318
|
+
background: var(--gs-surface);
|
|
2319
|
+
border: 1px solid var(--gs-border);
|
|
2320
|
+
border-radius: 4px;
|
|
2321
|
+
}
|
|
2322
|
+
.fbSearch:focus { outline: none; border-color: var(--gs-accent); }
|
|
2323
|
+
.fbNote { flex: none; font-size: var(--gs-t-meta); color: var(--gs-fg-dim); padding: 2px 4px; }
|
|
2324
|
+
.fbList { list-style: none; margin: 0; padding: 0; }
|
|
2325
|
+
.fbRow {
|
|
2326
|
+
display: flex;
|
|
2327
|
+
align-items: center;
|
|
2328
|
+
gap: 4px;
|
|
2329
|
+
width: 100%;
|
|
2330
|
+
padding: 2px 6px 2px 4px;
|
|
2331
|
+
font: inherit;
|
|
2332
|
+
font-size: var(--gs-t-dense);
|
|
2333
|
+
color: var(--gs-fg-dim);
|
|
2334
|
+
text-align: left;
|
|
2335
|
+
background: none;
|
|
2336
|
+
border: 0;
|
|
2337
|
+
border-radius: 3px;
|
|
2338
|
+
cursor: pointer;
|
|
2339
|
+
white-space: nowrap;
|
|
2340
|
+
overflow: hidden;
|
|
2341
|
+
text-overflow: ellipsis;
|
|
2342
|
+
}
|
|
2343
|
+
.fbRow:hover { background: var(--gs-hover); }
|
|
2344
|
+
.fbRowActive { background: var(--gs-selected); color: var(--gs-fg); }
|
|
2345
|
+
.fbDirName { overflow: hidden; text-overflow: ellipsis; }
|
|
2346
|
+
.fbFileName { overflow: hidden; text-overflow: ellipsis; }
|
|
2347
|
+
.fbMain {
|
|
2348
|
+
flex: 1 1 0;
|
|
2349
|
+
min-width: var(--gs-min-diff);
|
|
2350
|
+
display: flex;
|
|
2351
|
+
flex-direction: column;
|
|
2352
|
+
min-height: 0;
|
|
2353
|
+
background: var(--gs-surface);
|
|
2354
|
+
}
|
|
2355
|
+
.fbHeader {
|
|
2356
|
+
flex: none;
|
|
2357
|
+
display: flex;
|
|
2358
|
+
align-items: flex-start;
|
|
2359
|
+
gap: 8px;
|
|
2360
|
+
padding: 4px 8px 0;
|
|
2361
|
+
border-bottom: 1px solid var(--gs-border);
|
|
2362
|
+
}
|
|
2363
|
+
.fbPath {
|
|
2364
|
+
font-size: var(--gs-t-meta);
|
|
2365
|
+
color: var(--gs-fg-dim);
|
|
2366
|
+
overflow: hidden;
|
|
2367
|
+
text-overflow: ellipsis;
|
|
2368
|
+
white-space: nowrap;
|
|
2369
|
+
padding: 4px 0;
|
|
2370
|
+
}
|
|
2371
|
+
/* The editor scrolls inside this, so the header and the notices above stay
|
|
2372
|
+
put — the same reason the side pane's toolbar is pinned. */
|
|
2373
|
+
.fbBody { flex: 1 1 0; min-height: 0; overflow: auto; padding: 4px 8px; }
|
|
2374
|
+
|
|
2375
|
+
/* The commit behind the picked blame line. A strip rather than a floating
|
|
2376
|
+
popup: it never covers the code it is about, and it survives a scroll. */
|
|
2377
|
+
.fbCommit {
|
|
2378
|
+
flex: none;
|
|
2379
|
+
display: flex;
|
|
2380
|
+
align-items: baseline;
|
|
2381
|
+
gap: 8px;
|
|
2382
|
+
padding: 4px 8px;
|
|
2383
|
+
font-size: var(--gs-t-meta);
|
|
2384
|
+
color: var(--gs-fg-dim);
|
|
2385
|
+
background: var(--gs-surface-2);
|
|
2386
|
+
border-bottom: 1px solid var(--gs-border);
|
|
2387
|
+
}
|
|
2388
|
+
.fbCommitLine { flex: none; color: var(--gs-fg-faint); font-variant-numeric: tabular-nums; }
|
|
2389
|
+
.fbCommitWho { flex: none; color: var(--gs-fg); font-weight: 600; }
|
|
2390
|
+
.fbCommitWhen { flex: none; font-variant-numeric: tabular-nums; }
|
|
2391
|
+
.fbCommitHash { flex: none; font-family: inherit; color: var(--gs-fg-faint); }
|
|
2392
|
+
.fbCommitWhat {
|
|
2393
|
+
flex: 1 1 auto;
|
|
2394
|
+
min-width: 0;
|
|
2395
|
+
overflow: hidden;
|
|
2396
|
+
text-overflow: ellipsis;
|
|
2397
|
+
white-space: nowrap;
|
|
2398
|
+
color: var(--gs-fg);
|
|
2399
|
+
}
|
|
2400
|
+
.fbCommitHint { color: var(--gs-fg-faint); }
|
|
2401
|
+
.fbMore {
|
|
2402
|
+
display: block;
|
|
2403
|
+
padding: 2px 6px 4px 4px;
|
|
2404
|
+
font-size: var(--gs-t-meta);
|
|
2405
|
+
color: var(--gs-fg-faint);
|
|
2406
|
+
font-style: italic;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
/* ============================ image preview ============================= */
|
|
2410
|
+
|
|
2411
|
+
/* A picture fills the pane and its facts sit under it, because the facts are
|
|
2412
|
+
the reason this is a viewer rather than an image tag: a screenshot in a
|
|
2413
|
+
repository is usually opened to check a size or a crop. */
|
|
2414
|
+
.imgPane {
|
|
2415
|
+
display: flex;
|
|
2416
|
+
flex-direction: column;
|
|
2417
|
+
min-height: 0;
|
|
2418
|
+
height: 100%;
|
|
2419
|
+
gap: 8px;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/* The checkerboard is not decoration. A transparent PNG on a flat background
|
|
2423
|
+
is indistinguishable from an opaque one of that colour, and "is this
|
|
2424
|
+
transparent" is the question the file was opened to answer. Two overlaid
|
|
2425
|
+
gradients rather than an asset: no request, and it takes the theme. */
|
|
2426
|
+
.imgStage {
|
|
2427
|
+
flex: 1 1 auto;
|
|
2428
|
+
min-height: 0;
|
|
2429
|
+
display: flex;
|
|
2430
|
+
align-items: center;
|
|
2431
|
+
justify-content: center;
|
|
2432
|
+
overflow: auto;
|
|
2433
|
+
padding: 12px;
|
|
2434
|
+
border-radius: var(--gs-r-control);
|
|
2435
|
+
background-color: var(--gs-surface-2);
|
|
2436
|
+
background-image:
|
|
2437
|
+
linear-gradient(45deg, var(--gs-border-soft) 25%, transparent 25%, transparent 75%, var(--gs-border-soft) 75%),
|
|
2438
|
+
linear-gradient(45deg, var(--gs-border-soft) 25%, transparent 25%, transparent 75%, var(--gs-border-soft) 75%);
|
|
2439
|
+
background-size: 16px 16px;
|
|
2440
|
+
background-position: 0 0, 8px 8px;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
/* Fit is the default: an 8000px render opened at full size shows its top-left
|
|
2444
|
+
corner, which looks like a broken image rather than a big one. */
|
|
2445
|
+
.imgShot {
|
|
2446
|
+
max-width: 100%;
|
|
2447
|
+
max-height: 100%;
|
|
2448
|
+
object-fit: contain;
|
|
2449
|
+
/* Nearest-neighbour would be right for icons and wrong for photographs, and
|
|
2450
|
+
the pane cannot tell them apart; smooth is the safe half of that trade. */
|
|
2451
|
+
image-rendering: auto;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
/* Actual size: the scaling comes off and the stage scrolls instead. Written
|
|
2455
|
+
as a descendant of the stage's own state so the picture keeps ONE class,
|
|
2456
|
+
and the two views cannot disagree about which is in force. */
|
|
2457
|
+
.imgStage[data-actual] {
|
|
2458
|
+
align-items: flex-start;
|
|
2459
|
+
justify-content: flex-start;
|
|
2460
|
+
}
|
|
2461
|
+
.imgStage[data-actual] .imgShot {
|
|
2462
|
+
max-width: none;
|
|
2463
|
+
max-height: none;
|
|
2464
|
+
object-fit: none;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
.imgCaption {
|
|
2468
|
+
flex: none;
|
|
2469
|
+
display: flex;
|
|
2470
|
+
align-items: center;
|
|
2471
|
+
gap: 8px;
|
|
2472
|
+
font-size: var(--gs-t-meta);
|
|
2473
|
+
color: var(--gs-fg-dim);
|
|
2474
|
+
font-variant-numeric: tabular-nums;
|
|
2475
|
+
}
|