+373
-660
api/tangled/cbor_gen.go
+373
-660
api/tangled/cbor_gen.go
···
1202
1202
1203
1203
return nil
1204
1204
}
1205
-
func (t *GitRefUpdate_Meta) MarshalCBOR(w io.Writer) error {
1206
-
if t == nil {
1207
-
_, err := w.Write(cbg.CborNull)
1208
-
return err
1209
-
}
1210
-
1211
-
cw := cbg.NewCborWriter(w)
1212
-
fieldCount := 3
1213
-
1214
-
if t.LangBreakdown == nil {
1215
-
fieldCount--
1216
-
}
1217
-
1218
-
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
1219
-
return err
1220
-
}
1221
-
1222
-
// t.CommitCount (tangled.GitRefUpdate_Meta_CommitCount) (struct)
1223
-
if len("commitCount") > 1000000 {
1224
-
return xerrors.Errorf("Value in field \"commitCount\" was too long")
1225
-
}
1226
-
1227
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("commitCount"))); err != nil {
1228
-
return err
1229
-
}
1230
-
if _, err := cw.WriteString(string("commitCount")); err != nil {
1231
-
return err
1232
-
}
1233
-
1234
-
if err := t.CommitCount.MarshalCBOR(cw); err != nil {
1235
-
return err
1236
-
}
1237
-
1238
-
// t.IsDefaultRef (bool) (bool)
1239
-
if len("isDefaultRef") > 1000000 {
1240
-
return xerrors.Errorf("Value in field \"isDefaultRef\" was too long")
1241
-
}
1242
-
1243
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("isDefaultRef"))); err != nil {
1244
-
return err
1245
-
}
1246
-
if _, err := cw.WriteString(string("isDefaultRef")); err != nil {
1247
-
return err
1248
-
}
1249
-
1250
-
if err := cbg.WriteBool(w, t.IsDefaultRef); err != nil {
1251
-
return err
1252
-
}
1253
-
1254
-
// t.LangBreakdown (tangled.GitRefUpdate_Meta_LangBreakdown) (struct)
1255
-
if t.LangBreakdown != nil {
1256
-
1257
-
if len("langBreakdown") > 1000000 {
1258
-
return xerrors.Errorf("Value in field \"langBreakdown\" was too long")
1259
-
}
1260
-
1261
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("langBreakdown"))); err != nil {
1262
-
return err
1263
-
}
1264
-
if _, err := cw.WriteString(string("langBreakdown")); err != nil {
1265
-
return err
1266
-
}
1267
-
1268
-
if err := t.LangBreakdown.MarshalCBOR(cw); err != nil {
1269
-
return err
1270
-
}
1271
-
}
1272
-
return nil
1273
-
}
1274
-
1275
-
func (t *GitRefUpdate_Meta) UnmarshalCBOR(r io.Reader) (err error) {
1276
-
*t = GitRefUpdate_Meta{}
1277
-
1278
-
cr := cbg.NewCborReader(r)
1279
-
1280
-
maj, extra, err := cr.ReadHeader()
1281
-
if err != nil {
1282
-
return err
1283
-
}
1284
-
defer func() {
1285
-
if err == io.EOF {
1286
-
err = io.ErrUnexpectedEOF
1287
-
}
1288
-
}()
1289
-
1290
-
if maj != cbg.MajMap {
1291
-
return fmt.Errorf("cbor input should be of type map")
1292
-
}
1293
-
1294
-
if extra > cbg.MaxLength {
1295
-
return fmt.Errorf("GitRefUpdate_Meta: map struct too large (%d)", extra)
1296
-
}
1297
-
1298
-
n := extra
1299
-
1300
-
nameBuf := make([]byte, 13)
1301
-
for i := uint64(0); i < n; i++ {
1302
-
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1303
-
if err != nil {
1304
-
return err
1305
-
}
1306
-
1307
-
if !ok {
1308
-
// Field doesn't exist on this type, so ignore it
1309
-
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1310
-
return err
1311
-
}
1312
-
continue
1313
-
}
1314
-
1315
-
switch string(nameBuf[:nameLen]) {
1316
-
// t.CommitCount (tangled.GitRefUpdate_Meta_CommitCount) (struct)
1317
-
case "commitCount":
1318
-
1319
-
{
1320
-
1321
-
b, err := cr.ReadByte()
1322
-
if err != nil {
1323
-
return err
1324
-
}
1325
-
if b != cbg.CborNull[0] {
1326
-
if err := cr.UnreadByte(); err != nil {
1327
-
return err
1328
-
}
1329
-
t.CommitCount = new(GitRefUpdate_Meta_CommitCount)
1330
-
if err := t.CommitCount.UnmarshalCBOR(cr); err != nil {
1331
-
return xerrors.Errorf("unmarshaling t.CommitCount pointer: %w", err)
1332
-
}
1333
-
}
1334
-
1335
-
}
1336
-
// t.IsDefaultRef (bool) (bool)
1337
-
case "isDefaultRef":
1338
-
1339
-
maj, extra, err = cr.ReadHeader()
1340
-
if err != nil {
1341
-
return err
1342
-
}
1343
-
if maj != cbg.MajOther {
1344
-
return fmt.Errorf("booleans must be major type 7")
1345
-
}
1346
-
switch extra {
1347
-
case 20:
1348
-
t.IsDefaultRef = false
1349
-
case 21:
1350
-
t.IsDefaultRef = true
1351
-
default:
1352
-
return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra)
1353
-
}
1354
-
// t.LangBreakdown (tangled.GitRefUpdate_Meta_LangBreakdown) (struct)
1355
-
case "langBreakdown":
1356
-
1357
-
{
1358
-
1359
-
b, err := cr.ReadByte()
1360
-
if err != nil {
1361
-
return err
1362
-
}
1363
-
if b != cbg.CborNull[0] {
1364
-
if err := cr.UnreadByte(); err != nil {
1365
-
return err
1366
-
}
1367
-
t.LangBreakdown = new(GitRefUpdate_Meta_LangBreakdown)
1368
-
if err := t.LangBreakdown.UnmarshalCBOR(cr); err != nil {
1369
-
return xerrors.Errorf("unmarshaling t.LangBreakdown pointer: %w", err)
1370
-
}
1371
-
}
1372
-
1373
-
}
1374
-
1375
-
default:
1376
-
// Field doesn't exist on this type, so ignore it
1377
-
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1378
-
return err
1379
-
}
1380
-
}
1381
-
}
1382
-
1383
-
return nil
1384
-
}
1385
-
func (t *GitRefUpdate_Meta_CommitCount) MarshalCBOR(w io.Writer) error {
1205
+
func (t *GitRefUpdate_CommitCountBreakdown) MarshalCBOR(w io.Writer) error {
1386
1206
if t == nil {
1387
1207
_, err := w.Write(cbg.CborNull)
1388
1208
return err
···
1399
1219
return err
1400
1220
}
1401
1221
1402
-
// t.ByEmail ([]*tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem) (slice)
1222
+
// t.ByEmail ([]*tangled.GitRefUpdate_IndividualEmailCommitCount) (slice)
1403
1223
if t.ByEmail != nil {
1404
1224
1405
1225
if len("byEmail") > 1000000 {
···
1430
1250
return nil
1431
1251
}
1432
1252
1433
-
func (t *GitRefUpdate_Meta_CommitCount) UnmarshalCBOR(r io.Reader) (err error) {
1434
-
*t = GitRefUpdate_Meta_CommitCount{}
1253
+
func (t *GitRefUpdate_CommitCountBreakdown) UnmarshalCBOR(r io.Reader) (err error) {
1254
+
*t = GitRefUpdate_CommitCountBreakdown{}
1435
1255
1436
1256
cr := cbg.NewCborReader(r)
1437
1257
···
1450
1270
}
1451
1271
1452
1272
if extra > cbg.MaxLength {
1453
-
return fmt.Errorf("GitRefUpdate_Meta_CommitCount: map struct too large (%d)", extra)
1273
+
return fmt.Errorf("GitRefUpdate_CommitCountBreakdown: map struct too large (%d)", extra)
1454
1274
}
1455
1275
1456
1276
n := extra
···
1471
1291
}
1472
1292
1473
1293
switch string(nameBuf[:nameLen]) {
1474
-
// t.ByEmail ([]*tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem) (slice)
1294
+
// t.ByEmail ([]*tangled.GitRefUpdate_IndividualEmailCommitCount) (slice)
1475
1295
case "byEmail":
1476
1296
1477
1297
maj, extra, err = cr.ReadHeader()
···
1488
1308
}
1489
1309
1490
1310
if extra > 0 {
1491
-
t.ByEmail = make([]*GitRefUpdate_Meta_CommitCount_ByEmail_Elem, extra)
1311
+
t.ByEmail = make([]*GitRefUpdate_IndividualEmailCommitCount, extra)
1492
1312
}
1493
1313
1494
1314
for i := 0; i < int(extra); i++ {
···
1510
1330
if err := cr.UnreadByte(); err != nil {
1511
1331
return err
1512
1332
}
1513
-
t.ByEmail[i] = new(GitRefUpdate_Meta_CommitCount_ByEmail_Elem)
1333
+
t.ByEmail[i] = new(GitRefUpdate_IndividualEmailCommitCount)
1514
1334
if err := t.ByEmail[i].UnmarshalCBOR(cr); err != nil {
1515
1335
return xerrors.Errorf("unmarshaling t.ByEmail[i] pointer: %w", err)
1516
1336
}
···
1531
1351
1532
1352
return nil
1533
1353
}
1534
-
func (t *GitRefUpdate_Meta_CommitCount_ByEmail_Elem) MarshalCBOR(w io.Writer) error {
1354
+
func (t *GitRefUpdate_IndividualEmailCommitCount) MarshalCBOR(w io.Writer) error {
1535
1355
if t == nil {
1536
1356
_, err := w.Write(cbg.CborNull)
1537
1357
return err
···
1590
1410
return nil
1591
1411
}
1592
1412
1593
-
func (t *GitRefUpdate_Meta_CommitCount_ByEmail_Elem) UnmarshalCBOR(r io.Reader) (err error) {
1594
-
*t = GitRefUpdate_Meta_CommitCount_ByEmail_Elem{}
1413
+
func (t *GitRefUpdate_IndividualEmailCommitCount) UnmarshalCBOR(r io.Reader) (err error) {
1414
+
*t = GitRefUpdate_IndividualEmailCommitCount{}
1595
1415
1596
1416
cr := cbg.NewCborReader(r)
1597
1417
···
1610
1430
}
1611
1431
1612
1432
if extra > cbg.MaxLength {
1613
-
return fmt.Errorf("GitRefUpdate_Meta_CommitCount_ByEmail_Elem: map struct too large (%d)", extra)
1433
+
return fmt.Errorf("GitRefUpdate_IndividualEmailCommitCount: map struct too large (%d)", extra)
1614
1434
}
1615
1435
1616
1436
n := extra
···
1679
1499
1680
1500
return nil
1681
1501
}
1682
-
func (t *GitRefUpdate_Meta_LangBreakdown) MarshalCBOR(w io.Writer) error {
1502
+
func (t *GitRefUpdate_LangBreakdown) MarshalCBOR(w io.Writer) error {
1683
1503
if t == nil {
1684
1504
_, err := w.Write(cbg.CborNull)
1685
1505
return err
···
1696
1516
return err
1697
1517
}
1698
1518
1699
-
// t.Inputs ([]*tangled.GitRefUpdate_Pair) (slice)
1519
+
// t.Inputs ([]*tangled.GitRefUpdate_IndividualLanguageSize) (slice)
1700
1520
if t.Inputs != nil {
1701
1521
1702
1522
if len("inputs") > 1000000 {
···
1727
1547
return nil
1728
1548
}
1729
1549
1730
-
func (t *GitRefUpdate_Meta_LangBreakdown) UnmarshalCBOR(r io.Reader) (err error) {
1731
-
*t = GitRefUpdate_Meta_LangBreakdown{}
1550
+
func (t *GitRefUpdate_LangBreakdown) UnmarshalCBOR(r io.Reader) (err error) {
1551
+
*t = GitRefUpdate_LangBreakdown{}
1732
1552
1733
1553
cr := cbg.NewCborReader(r)
1734
1554
···
1747
1567
}
1748
1568
1749
1569
if extra > cbg.MaxLength {
1750
-
return fmt.Errorf("GitRefUpdate_Meta_LangBreakdown: map struct too large (%d)", extra)
1570
+
return fmt.Errorf("GitRefUpdate_LangBreakdown: map struct too large (%d)", extra)
1751
1571
}
1752
1572
1753
1573
n := extra
···
1768
1588
}
1769
1589
1770
1590
switch string(nameBuf[:nameLen]) {
1771
-
// t.Inputs ([]*tangled.GitRefUpdate_Pair) (slice)
1591
+
// t.Inputs ([]*tangled.GitRefUpdate_IndividualLanguageSize) (slice)
1772
1592
case "inputs":
1773
1593
1774
1594
maj, extra, err = cr.ReadHeader()
···
1785
1605
}
1786
1606
1787
1607
if extra > 0 {
1788
-
t.Inputs = make([]*GitRefUpdate_Pair, extra)
1608
+
t.Inputs = make([]*GitRefUpdate_IndividualLanguageSize, extra)
1789
1609
}
1790
1610
1791
1611
for i := 0; i < int(extra); i++ {
···
1807
1627
if err := cr.UnreadByte(); err != nil {
1808
1628
return err
1809
1629
}
1810
-
t.Inputs[i] = new(GitRefUpdate_Pair)
1630
+
t.Inputs[i] = new(GitRefUpdate_IndividualLanguageSize)
1811
1631
if err := t.Inputs[i].UnmarshalCBOR(cr); err != nil {
1812
1632
return xerrors.Errorf("unmarshaling t.Inputs[i] pointer: %w", err)
1813
1633
}
···
1828
1648
1829
1649
return nil
1830
1650
}
1831
-
func (t *GitRefUpdate_Pair) MarshalCBOR(w io.Writer) error {
1651
+
func (t *GitRefUpdate_IndividualLanguageSize) MarshalCBOR(w io.Writer) error {
1832
1652
if t == nil {
1833
1653
_, err := w.Write(cbg.CborNull)
1834
1654
return err
···
1888
1708
return nil
1889
1709
}
1890
1710
1891
-
func (t *GitRefUpdate_Pair) UnmarshalCBOR(r io.Reader) (err error) {
1892
-
*t = GitRefUpdate_Pair{}
1711
+
func (t *GitRefUpdate_IndividualLanguageSize) UnmarshalCBOR(r io.Reader) (err error) {
1712
+
*t = GitRefUpdate_IndividualLanguageSize{}
1893
1713
1894
1714
cr := cbg.NewCborReader(r)
1895
1715
···
1908
1728
}
1909
1729
1910
1730
if extra > cbg.MaxLength {
1911
-
return fmt.Errorf("GitRefUpdate_Pair: map struct too large (%d)", extra)
1731
+
return fmt.Errorf("GitRefUpdate_IndividualLanguageSize: map struct too large (%d)", extra)
1912
1732
}
1913
1733
1914
1734
n := extra
···
1965
1785
}
1966
1786
1967
1787
t.Size = int64(extraI)
1788
+
}
1789
+
1790
+
default:
1791
+
// Field doesn't exist on this type, so ignore it
1792
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
1793
+
return err
1794
+
}
1795
+
}
1796
+
}
1797
+
1798
+
return nil
1799
+
}
1800
+
func (t *GitRefUpdate_Meta) MarshalCBOR(w io.Writer) error {
1801
+
if t == nil {
1802
+
_, err := w.Write(cbg.CborNull)
1803
+
return err
1804
+
}
1805
+
1806
+
cw := cbg.NewCborWriter(w)
1807
+
fieldCount := 3
1808
+
1809
+
if t.LangBreakdown == nil {
1810
+
fieldCount--
1811
+
}
1812
+
1813
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
1814
+
return err
1815
+
}
1816
+
1817
+
// t.CommitCount (tangled.GitRefUpdate_CommitCountBreakdown) (struct)
1818
+
if len("commitCount") > 1000000 {
1819
+
return xerrors.Errorf("Value in field \"commitCount\" was too long")
1820
+
}
1821
+
1822
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("commitCount"))); err != nil {
1823
+
return err
1824
+
}
1825
+
if _, err := cw.WriteString(string("commitCount")); err != nil {
1826
+
return err
1827
+
}
1828
+
1829
+
if err := t.CommitCount.MarshalCBOR(cw); err != nil {
1830
+
return err
1831
+
}
1832
+
1833
+
// t.IsDefaultRef (bool) (bool)
1834
+
if len("isDefaultRef") > 1000000 {
1835
+
return xerrors.Errorf("Value in field \"isDefaultRef\" was too long")
1836
+
}
1837
+
1838
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("isDefaultRef"))); err != nil {
1839
+
return err
1840
+
}
1841
+
if _, err := cw.WriteString(string("isDefaultRef")); err != nil {
1842
+
return err
1843
+
}
1844
+
1845
+
if err := cbg.WriteBool(w, t.IsDefaultRef); err != nil {
1846
+
return err
1847
+
}
1848
+
1849
+
// t.LangBreakdown (tangled.GitRefUpdate_LangBreakdown) (struct)
1850
+
if t.LangBreakdown != nil {
1851
+
1852
+
if len("langBreakdown") > 1000000 {
1853
+
return xerrors.Errorf("Value in field \"langBreakdown\" was too long")
1854
+
}
1855
+
1856
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("langBreakdown"))); err != nil {
1857
+
return err
1858
+
}
1859
+
if _, err := cw.WriteString(string("langBreakdown")); err != nil {
1860
+
return err
1861
+
}
1862
+
1863
+
if err := t.LangBreakdown.MarshalCBOR(cw); err != nil {
1864
+
return err
1865
+
}
1866
+
}
1867
+
return nil
1868
+
}
1869
+
1870
+
func (t *GitRefUpdate_Meta) UnmarshalCBOR(r io.Reader) (err error) {
1871
+
*t = GitRefUpdate_Meta{}
1872
+
1873
+
cr := cbg.NewCborReader(r)
1874
+
1875
+
maj, extra, err := cr.ReadHeader()
1876
+
if err != nil {
1877
+
return err
1878
+
}
1879
+
defer func() {
1880
+
if err == io.EOF {
1881
+
err = io.ErrUnexpectedEOF
1882
+
}
1883
+
}()
1884
+
1885
+
if maj != cbg.MajMap {
1886
+
return fmt.Errorf("cbor input should be of type map")
1887
+
}
1888
+
1889
+
if extra > cbg.MaxLength {
1890
+
return fmt.Errorf("GitRefUpdate_Meta: map struct too large (%d)", extra)
1891
+
}
1892
+
1893
+
n := extra
1894
+
1895
+
nameBuf := make([]byte, 13)
1896
+
for i := uint64(0); i < n; i++ {
1897
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
1898
+
if err != nil {
1899
+
return err
1900
+
}
1901
+
1902
+
if !ok {
1903
+
// Field doesn't exist on this type, so ignore it
1904
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
1905
+
return err
1906
+
}
1907
+
continue
1908
+
}
1909
+
1910
+
switch string(nameBuf[:nameLen]) {
1911
+
// t.CommitCount (tangled.GitRefUpdate_CommitCountBreakdown) (struct)
1912
+
case "commitCount":
1913
+
1914
+
{
1915
+
1916
+
b, err := cr.ReadByte()
1917
+
if err != nil {
1918
+
return err
1919
+
}
1920
+
if b != cbg.CborNull[0] {
1921
+
if err := cr.UnreadByte(); err != nil {
1922
+
return err
1923
+
}
1924
+
t.CommitCount = new(GitRefUpdate_CommitCountBreakdown)
1925
+
if err := t.CommitCount.UnmarshalCBOR(cr); err != nil {
1926
+
return xerrors.Errorf("unmarshaling t.CommitCount pointer: %w", err)
1927
+
}
1928
+
}
1929
+
1930
+
}
1931
+
// t.IsDefaultRef (bool) (bool)
1932
+
case "isDefaultRef":
1933
+
1934
+
maj, extra, err = cr.ReadHeader()
1935
+
if err != nil {
1936
+
return err
1937
+
}
1938
+
if maj != cbg.MajOther {
1939
+
return fmt.Errorf("booleans must be major type 7")
1940
+
}
1941
+
switch extra {
1942
+
case 20:
1943
+
t.IsDefaultRef = false
1944
+
case 21:
1945
+
t.IsDefaultRef = true
1946
+
default:
1947
+
return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra)
1948
+
}
1949
+
// t.LangBreakdown (tangled.GitRefUpdate_LangBreakdown) (struct)
1950
+
case "langBreakdown":
1951
+
1952
+
{
1953
+
1954
+
b, err := cr.ReadByte()
1955
+
if err != nil {
1956
+
return err
1957
+
}
1958
+
if b != cbg.CborNull[0] {
1959
+
if err := cr.UnreadByte(); err != nil {
1960
+
return err
1961
+
}
1962
+
t.LangBreakdown = new(GitRefUpdate_LangBreakdown)
1963
+
if err := t.LangBreakdown.UnmarshalCBOR(cr); err != nil {
1964
+
return xerrors.Errorf("unmarshaling t.LangBreakdown pointer: %w", err)
1965
+
}
1966
+
}
1967
+
1968
1968
}
1969
1969
1970
1970
default:
···
5642
5642
}
5643
5643
5644
5644
cw := cbg.NewCborWriter(w)
5645
-
fieldCount := 7
5645
+
fieldCount := 5
5646
5646
5647
5647
if t.Body == nil {
5648
5648
fieldCount--
···
5726
5726
return err
5727
5727
}
5728
5728
5729
-
// t.Owner (string) (string)
5730
-
if len("owner") > 1000000 {
5731
-
return xerrors.Errorf("Value in field \"owner\" was too long")
5732
-
}
5733
-
5734
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("owner"))); err != nil {
5735
-
return err
5736
-
}
5737
-
if _, err := cw.WriteString(string("owner")); err != nil {
5738
-
return err
5739
-
}
5740
-
5741
-
if len(t.Owner) > 1000000 {
5742
-
return xerrors.Errorf("Value in field t.Owner was too long")
5743
-
}
5744
-
5745
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Owner))); err != nil {
5746
-
return err
5747
-
}
5748
-
if _, err := cw.WriteString(string(t.Owner)); err != nil {
5749
-
return err
5750
-
}
5751
-
5752
5729
// t.Title (string) (string)
5753
5730
if len("title") > 1000000 {
5754
5731
return xerrors.Errorf("Value in field \"title\" was too long")
···
5772
5749
return err
5773
5750
}
5774
5751
5775
-
// t.IssueId (int64) (int64)
5776
-
if len("issueId") > 1000000 {
5777
-
return xerrors.Errorf("Value in field \"issueId\" was too long")
5778
-
}
5779
-
5780
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("issueId"))); err != nil {
5781
-
return err
5782
-
}
5783
-
if _, err := cw.WriteString(string("issueId")); err != nil {
5784
-
return err
5785
-
}
5786
-
5787
-
if t.IssueId >= 0 {
5788
-
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.IssueId)); err != nil {
5789
-
return err
5790
-
}
5791
-
} else {
5792
-
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.IssueId-1)); err != nil {
5793
-
return err
5794
-
}
5795
-
}
5796
-
5797
5752
// t.CreatedAt (string) (string)
5798
5753
if len("createdAt") > 1000000 {
5799
5754
return xerrors.Errorf("Value in field \"createdAt\" was too long")
···
5903
5858
5904
5859
t.LexiconTypeID = string(sval)
5905
5860
}
5906
-
// t.Owner (string) (string)
5907
-
case "owner":
5908
-
5909
-
{
5910
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
5911
-
if err != nil {
5912
-
return err
5913
-
}
5914
-
5915
-
t.Owner = string(sval)
5916
-
}
5917
5861
// t.Title (string) (string)
5918
5862
case "title":
5919
5863
···
5925
5869
5926
5870
t.Title = string(sval)
5927
5871
}
5928
-
// t.IssueId (int64) (int64)
5929
-
case "issueId":
5930
-
{
5931
-
maj, extra, err := cr.ReadHeader()
5932
-
if err != nil {
5933
-
return err
5934
-
}
5935
-
var extraI int64
5936
-
switch maj {
5937
-
case cbg.MajUnsignedInt:
5938
-
extraI = int64(extra)
5939
-
if extraI < 0 {
5940
-
return fmt.Errorf("int64 positive overflow")
5941
-
}
5942
-
case cbg.MajNegativeInt:
5943
-
extraI = int64(extra)
5944
-
if extraI < 0 {
5945
-
return fmt.Errorf("int64 negative overflow")
5946
-
}
5947
-
extraI = -1 - extraI
5948
-
default:
5949
-
return fmt.Errorf("wrong type for int64 field: %d", maj)
5950
-
}
5951
-
5952
-
t.IssueId = int64(extraI)
5953
-
}
5954
5872
// t.CreatedAt (string) (string)
5955
5873
case "createdAt":
5956
5874
···
5980
5898
}
5981
5899
5982
5900
cw := cbg.NewCborWriter(w)
5983
-
fieldCount := 7
5984
-
5985
-
if t.CommentId == nil {
5986
-
fieldCount--
5987
-
}
5901
+
fieldCount := 6
5988
5902
5989
5903
if t.Owner == nil {
5990
5904
fieldCount--
···
6127
6041
}
6128
6042
}
6129
6043
6130
-
// t.CommentId (int64) (int64)
6131
-
if t.CommentId != nil {
6132
-
6133
-
if len("commentId") > 1000000 {
6134
-
return xerrors.Errorf("Value in field \"commentId\" was too long")
6135
-
}
6136
-
6137
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("commentId"))); err != nil {
6138
-
return err
6139
-
}
6140
-
if _, err := cw.WriteString(string("commentId")); err != nil {
6141
-
return err
6142
-
}
6143
-
6144
-
if t.CommentId == nil {
6145
-
if _, err := cw.Write(cbg.CborNull); err != nil {
6146
-
return err
6147
-
}
6148
-
} else {
6149
-
if *t.CommentId >= 0 {
6150
-
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.CommentId)); err != nil {
6151
-
return err
6152
-
}
6153
-
} else {
6154
-
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.CommentId-1)); err != nil {
6155
-
return err
6156
-
}
6157
-
}
6158
-
}
6159
-
6160
-
}
6161
-
6162
6044
// t.CreatedAt (string) (string)
6163
6045
if len("createdAt") > 1000000 {
6164
6046
return xerrors.Errorf("Value in field \"createdAt\" was too long")
···
6300
6182
t.Owner = (*string)(&sval)
6301
6183
}
6302
6184
}
6303
-
// t.CommentId (int64) (int64)
6304
-
case "commentId":
6305
-
{
6306
-
6307
-
b, err := cr.ReadByte()
6308
-
if err != nil {
6309
-
return err
6310
-
}
6311
-
if b != cbg.CborNull[0] {
6312
-
if err := cr.UnreadByte(); err != nil {
6313
-
return err
6314
-
}
6315
-
maj, extra, err := cr.ReadHeader()
6316
-
if err != nil {
6317
-
return err
6318
-
}
6319
-
var extraI int64
6320
-
switch maj {
6321
-
case cbg.MajUnsignedInt:
6322
-
extraI = int64(extra)
6323
-
if extraI < 0 {
6324
-
return fmt.Errorf("int64 positive overflow")
6325
-
}
6326
-
case cbg.MajNegativeInt:
6327
-
extraI = int64(extra)
6328
-
if extraI < 0 {
6329
-
return fmt.Errorf("int64 negative overflow")
6330
-
}
6331
-
extraI = -1 - extraI
6332
-
default:
6333
-
return fmt.Errorf("wrong type for int64 field: %d", maj)
6334
-
}
6335
-
6336
-
t.CommentId = (*int64)(&extraI)
6337
-
}
6338
-
}
6339
6185
// t.CreatedAt (string) (string)
6340
6186
case "createdAt":
6341
6187
···
6529
6375
}
6530
6376
6531
6377
cw := cbg.NewCborWriter(w)
6532
-
fieldCount := 9
6378
+
fieldCount := 7
6533
6379
6534
6380
if t.Body == nil {
6535
6381
fieldCount--
···
6640
6486
return err
6641
6487
}
6642
6488
6643
-
// t.PullId (int64) (int64)
6644
-
if len("pullId") > 1000000 {
6645
-
return xerrors.Errorf("Value in field \"pullId\" was too long")
6646
-
}
6647
-
6648
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("pullId"))); err != nil {
6649
-
return err
6650
-
}
6651
-
if _, err := cw.WriteString(string("pullId")); err != nil {
6652
-
return err
6653
-
}
6654
-
6655
-
if t.PullId >= 0 {
6656
-
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.PullId)); err != nil {
6657
-
return err
6658
-
}
6659
-
} else {
6660
-
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.PullId-1)); err != nil {
6661
-
return err
6662
-
}
6663
-
}
6664
-
6665
6489
// t.Source (tangled.RepoPull_Source) (struct)
6666
6490
if t.Source != nil {
6667
6491
···
6681
6505
}
6682
6506
}
6683
6507
6684
-
// t.CreatedAt (string) (string)
6685
-
if len("createdAt") > 1000000 {
6686
-
return xerrors.Errorf("Value in field \"createdAt\" was too long")
6687
-
}
6688
-
6689
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
6690
-
return err
6691
-
}
6692
-
if _, err := cw.WriteString(string("createdAt")); err != nil {
6693
-
return err
6694
-
}
6695
-
6696
-
if len(t.CreatedAt) > 1000000 {
6697
-
return xerrors.Errorf("Value in field t.CreatedAt was too long")
6508
+
// t.Target (tangled.RepoPull_Target) (struct)
6509
+
if len("target") > 1000000 {
6510
+
return xerrors.Errorf("Value in field \"target\" was too long")
6698
6511
}
6699
6512
6700
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
6513
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("target"))); err != nil {
6701
6514
return err
6702
6515
}
6703
-
if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
6516
+
if _, err := cw.WriteString(string("target")); err != nil {
6704
6517
return err
6705
6518
}
6706
6519
6707
-
// t.TargetRepo (string) (string)
6708
-
if len("targetRepo") > 1000000 {
6709
-
return xerrors.Errorf("Value in field \"targetRepo\" was too long")
6710
-
}
6711
-
6712
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("targetRepo"))); err != nil {
6713
-
return err
6714
-
}
6715
-
if _, err := cw.WriteString(string("targetRepo")); err != nil {
6520
+
if err := t.Target.MarshalCBOR(cw); err != nil {
6716
6521
return err
6717
6522
}
6718
6523
6719
-
if len(t.TargetRepo) > 1000000 {
6720
-
return xerrors.Errorf("Value in field t.TargetRepo was too long")
6524
+
// t.CreatedAt (string) (string)
6525
+
if len("createdAt") > 1000000 {
6526
+
return xerrors.Errorf("Value in field \"createdAt\" was too long")
6721
6527
}
6722
6528
6723
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.TargetRepo))); err != nil {
6529
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
6724
6530
return err
6725
6531
}
6726
-
if _, err := cw.WriteString(string(t.TargetRepo)); err != nil {
6532
+
if _, err := cw.WriteString(string("createdAt")); err != nil {
6727
6533
return err
6728
6534
}
6729
6535
6730
-
// t.TargetBranch (string) (string)
6731
-
if len("targetBranch") > 1000000 {
6732
-
return xerrors.Errorf("Value in field \"targetBranch\" was too long")
6733
-
}
6734
-
6735
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("targetBranch"))); err != nil {
6736
-
return err
6737
-
}
6738
-
if _, err := cw.WriteString(string("targetBranch")); err != nil {
6739
-
return err
6740
-
}
6741
-
6742
-
if len(t.TargetBranch) > 1000000 {
6743
-
return xerrors.Errorf("Value in field t.TargetBranch was too long")
6536
+
if len(t.CreatedAt) > 1000000 {
6537
+
return xerrors.Errorf("Value in field t.CreatedAt was too long")
6744
6538
}
6745
6539
6746
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.TargetBranch))); err != nil {
6540
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
6747
6541
return err
6748
6542
}
6749
-
if _, err := cw.WriteString(string(t.TargetBranch)); err != nil {
6543
+
if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
6750
6544
return err
6751
6545
}
6752
6546
return nil
···
6777
6571
6778
6572
n := extra
6779
6573
6780
-
nameBuf := make([]byte, 12)
6574
+
nameBuf := make([]byte, 9)
6781
6575
for i := uint64(0); i < n; i++ {
6782
6576
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
6783
6577
if err != nil {
···
6847
6641
6848
6642
t.Title = string(sval)
6849
6643
}
6850
-
// t.PullId (int64) (int64)
6851
-
case "pullId":
6852
-
{
6853
-
maj, extra, err := cr.ReadHeader()
6854
-
if err != nil {
6855
-
return err
6856
-
}
6857
-
var extraI int64
6858
-
switch maj {
6859
-
case cbg.MajUnsignedInt:
6860
-
extraI = int64(extra)
6861
-
if extraI < 0 {
6862
-
return fmt.Errorf("int64 positive overflow")
6863
-
}
6864
-
case cbg.MajNegativeInt:
6865
-
extraI = int64(extra)
6866
-
if extraI < 0 {
6867
-
return fmt.Errorf("int64 negative overflow")
6868
-
}
6869
-
extraI = -1 - extraI
6870
-
default:
6871
-
return fmt.Errorf("wrong type for int64 field: %d", maj)
6872
-
}
6873
-
6874
-
t.PullId = int64(extraI)
6875
-
}
6876
6644
// t.Source (tangled.RepoPull_Source) (struct)
6877
6645
case "source":
6878
6646
···
6893
6661
}
6894
6662
6895
6663
}
6896
-
// t.CreatedAt (string) (string)
6897
-
case "createdAt":
6664
+
// t.Target (tangled.RepoPull_Target) (struct)
6665
+
case "target":
6898
6666
6899
6667
{
6900
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
6668
+
6669
+
b, err := cr.ReadByte()
6901
6670
if err != nil {
6902
6671
return err
6903
6672
}
6904
-
6905
-
t.CreatedAt = string(sval)
6906
-
}
6907
-
// t.TargetRepo (string) (string)
6908
-
case "targetRepo":
6909
-
6910
-
{
6911
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
6912
-
if err != nil {
6913
-
return err
6673
+
if b != cbg.CborNull[0] {
6674
+
if err := cr.UnreadByte(); err != nil {
6675
+
return err
6676
+
}
6677
+
t.Target = new(RepoPull_Target)
6678
+
if err := t.Target.UnmarshalCBOR(cr); err != nil {
6679
+
return xerrors.Errorf("unmarshaling t.Target pointer: %w", err)
6680
+
}
6914
6681
}
6915
6682
6916
-
t.TargetRepo = string(sval)
6917
6683
}
6918
-
// t.TargetBranch (string) (string)
6919
-
case "targetBranch":
6684
+
// t.CreatedAt (string) (string)
6685
+
case "createdAt":
6920
6686
6921
6687
{
6922
6688
sval, err := cbg.ReadStringWithMax(cr, 1000000)
···
6924
6690
return err
6925
6691
}
6926
6692
6927
-
t.TargetBranch = string(sval)
6693
+
t.CreatedAt = string(sval)
6928
6694
}
6929
6695
6930
6696
default:
···
6944
6710
}
6945
6711
6946
6712
cw := cbg.NewCborWriter(w)
6947
-
fieldCount := 7
6948
6713
6949
-
if t.CommentId == nil {
6950
-
fieldCount--
6951
-
}
6952
-
6953
-
if t.Owner == nil {
6954
-
fieldCount--
6955
-
}
6956
-
6957
-
if t.Repo == nil {
6958
-
fieldCount--
6959
-
}
6960
-
6961
-
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
6714
+
if _, err := cw.Write([]byte{164}); err != nil {
6962
6715
return err
6963
6716
}
6964
6717
···
7008
6761
return err
7009
6762
}
7010
6763
7011
-
// t.Repo (string) (string)
7012
-
if t.Repo != nil {
7013
-
7014
-
if len("repo") > 1000000 {
7015
-
return xerrors.Errorf("Value in field \"repo\" was too long")
7016
-
}
7017
-
7018
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repo"))); err != nil {
7019
-
return err
7020
-
}
7021
-
if _, err := cw.WriteString(string("repo")); err != nil {
7022
-
return err
7023
-
}
7024
-
7025
-
if t.Repo == nil {
7026
-
if _, err := cw.Write(cbg.CborNull); err != nil {
7027
-
return err
7028
-
}
7029
-
} else {
7030
-
if len(*t.Repo) > 1000000 {
7031
-
return xerrors.Errorf("Value in field t.Repo was too long")
7032
-
}
7033
-
7034
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Repo))); err != nil {
7035
-
return err
7036
-
}
7037
-
if _, err := cw.WriteString(string(*t.Repo)); err != nil {
7038
-
return err
7039
-
}
7040
-
}
7041
-
}
7042
-
7043
6764
// t.LexiconTypeID (string) (string)
7044
6765
if len("$type") > 1000000 {
7045
6766
return xerrors.Errorf("Value in field \"$type\" was too long")
···
7059
6780
return err
7060
6781
}
7061
6782
7062
-
// t.Owner (string) (string)
7063
-
if t.Owner != nil {
7064
-
7065
-
if len("owner") > 1000000 {
7066
-
return xerrors.Errorf("Value in field \"owner\" was too long")
7067
-
}
7068
-
7069
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("owner"))); err != nil {
7070
-
return err
7071
-
}
7072
-
if _, err := cw.WriteString(string("owner")); err != nil {
7073
-
return err
7074
-
}
7075
-
7076
-
if t.Owner == nil {
7077
-
if _, err := cw.Write(cbg.CborNull); err != nil {
7078
-
return err
7079
-
}
7080
-
} else {
7081
-
if len(*t.Owner) > 1000000 {
7082
-
return xerrors.Errorf("Value in field t.Owner was too long")
7083
-
}
7084
-
7085
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.Owner))); err != nil {
7086
-
return err
7087
-
}
7088
-
if _, err := cw.WriteString(string(*t.Owner)); err != nil {
7089
-
return err
7090
-
}
7091
-
}
7092
-
}
7093
-
7094
-
// t.CommentId (int64) (int64)
7095
-
if t.CommentId != nil {
7096
-
7097
-
if len("commentId") > 1000000 {
7098
-
return xerrors.Errorf("Value in field \"commentId\" was too long")
7099
-
}
7100
-
7101
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("commentId"))); err != nil {
7102
-
return err
7103
-
}
7104
-
if _, err := cw.WriteString(string("commentId")); err != nil {
7105
-
return err
7106
-
}
7107
-
7108
-
if t.CommentId == nil {
7109
-
if _, err := cw.Write(cbg.CborNull); err != nil {
7110
-
return err
7111
-
}
7112
-
} else {
7113
-
if *t.CommentId >= 0 {
7114
-
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(*t.CommentId)); err != nil {
7115
-
return err
7116
-
}
7117
-
} else {
7118
-
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-*t.CommentId-1)); err != nil {
7119
-
return err
7120
-
}
7121
-
}
7122
-
}
7123
-
7124
-
}
7125
-
7126
6783
// t.CreatedAt (string) (string)
7127
6784
if len("createdAt") > 1000000 {
7128
6785
return xerrors.Errorf("Value in field \"createdAt\" was too long")
···
7211
6868
7212
6869
t.Pull = string(sval)
7213
6870
}
7214
-
// t.Repo (string) (string)
7215
-
case "repo":
7216
-
7217
-
{
7218
-
b, err := cr.ReadByte()
7219
-
if err != nil {
7220
-
return err
7221
-
}
7222
-
if b != cbg.CborNull[0] {
7223
-
if err := cr.UnreadByte(); err != nil {
7224
-
return err
7225
-
}
7226
-
7227
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7228
-
if err != nil {
7229
-
return err
7230
-
}
7231
-
7232
-
t.Repo = (*string)(&sval)
7233
-
}
7234
-
}
7235
6871
// t.LexiconTypeID (string) (string)
7236
6872
case "$type":
7237
6873
···
7242
6878
}
7243
6879
7244
6880
t.LexiconTypeID = string(sval)
7245
-
}
7246
-
// t.Owner (string) (string)
7247
-
case "owner":
7248
-
7249
-
{
7250
-
b, err := cr.ReadByte()
7251
-
if err != nil {
7252
-
return err
7253
-
}
7254
-
if b != cbg.CborNull[0] {
7255
-
if err := cr.UnreadByte(); err != nil {
7256
-
return err
7257
-
}
7258
-
7259
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7260
-
if err != nil {
7261
-
return err
7262
-
}
7263
-
7264
-
t.Owner = (*string)(&sval)
7265
-
}
7266
-
}
7267
-
// t.CommentId (int64) (int64)
7268
-
case "commentId":
7269
-
{
7270
-
7271
-
b, err := cr.ReadByte()
7272
-
if err != nil {
7273
-
return err
7274
-
}
7275
-
if b != cbg.CborNull[0] {
7276
-
if err := cr.UnreadByte(); err != nil {
7277
-
return err
7278
-
}
7279
-
maj, extra, err := cr.ReadHeader()
7280
-
if err != nil {
7281
-
return err
7282
-
}
7283
-
var extraI int64
7284
-
switch maj {
7285
-
case cbg.MajUnsignedInt:
7286
-
extraI = int64(extra)
7287
-
if extraI < 0 {
7288
-
return fmt.Errorf("int64 positive overflow")
7289
-
}
7290
-
case cbg.MajNegativeInt:
7291
-
extraI = int64(extra)
7292
-
if extraI < 0 {
7293
-
return fmt.Errorf("int64 negative overflow")
7294
-
}
7295
-
extraI = -1 - extraI
7296
-
default:
7297
-
return fmt.Errorf("wrong type for int64 field: %d", maj)
7298
-
}
7299
-
7300
-
t.CommentId = (*int64)(&extraI)
7301
-
}
7302
6881
}
7303
6882
// t.CreatedAt (string) (string)
7304
6883
case "createdAt":
···
7666
7245
}
7667
7246
7668
7247
t.Status = string(sval)
7248
+
}
7249
+
7250
+
default:
7251
+
// Field doesn't exist on this type, so ignore it
7252
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
7253
+
return err
7254
+
}
7255
+
}
7256
+
}
7257
+
7258
+
return nil
7259
+
}
7260
+
func (t *RepoPull_Target) MarshalCBOR(w io.Writer) error {
7261
+
if t == nil {
7262
+
_, err := w.Write(cbg.CborNull)
7263
+
return err
7264
+
}
7265
+
7266
+
cw := cbg.NewCborWriter(w)
7267
+
7268
+
if _, err := cw.Write([]byte{162}); err != nil {
7269
+
return err
7270
+
}
7271
+
7272
+
// t.Repo (string) (string)
7273
+
if len("repo") > 1000000 {
7274
+
return xerrors.Errorf("Value in field \"repo\" was too long")
7275
+
}
7276
+
7277
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("repo"))); err != nil {
7278
+
return err
7279
+
}
7280
+
if _, err := cw.WriteString(string("repo")); err != nil {
7281
+
return err
7282
+
}
7283
+
7284
+
if len(t.Repo) > 1000000 {
7285
+
return xerrors.Errorf("Value in field t.Repo was too long")
7286
+
}
7287
+
7288
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Repo))); err != nil {
7289
+
return err
7290
+
}
7291
+
if _, err := cw.WriteString(string(t.Repo)); err != nil {
7292
+
return err
7293
+
}
7294
+
7295
+
// t.Branch (string) (string)
7296
+
if len("branch") > 1000000 {
7297
+
return xerrors.Errorf("Value in field \"branch\" was too long")
7298
+
}
7299
+
7300
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("branch"))); err != nil {
7301
+
return err
7302
+
}
7303
+
if _, err := cw.WriteString(string("branch")); err != nil {
7304
+
return err
7305
+
}
7306
+
7307
+
if len(t.Branch) > 1000000 {
7308
+
return xerrors.Errorf("Value in field t.Branch was too long")
7309
+
}
7310
+
7311
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Branch))); err != nil {
7312
+
return err
7313
+
}
7314
+
if _, err := cw.WriteString(string(t.Branch)); err != nil {
7315
+
return err
7316
+
}
7317
+
return nil
7318
+
}
7319
+
7320
+
func (t *RepoPull_Target) UnmarshalCBOR(r io.Reader) (err error) {
7321
+
*t = RepoPull_Target{}
7322
+
7323
+
cr := cbg.NewCborReader(r)
7324
+
7325
+
maj, extra, err := cr.ReadHeader()
7326
+
if err != nil {
7327
+
return err
7328
+
}
7329
+
defer func() {
7330
+
if err == io.EOF {
7331
+
err = io.ErrUnexpectedEOF
7332
+
}
7333
+
}()
7334
+
7335
+
if maj != cbg.MajMap {
7336
+
return fmt.Errorf("cbor input should be of type map")
7337
+
}
7338
+
7339
+
if extra > cbg.MaxLength {
7340
+
return fmt.Errorf("RepoPull_Target: map struct too large (%d)", extra)
7341
+
}
7342
+
7343
+
n := extra
7344
+
7345
+
nameBuf := make([]byte, 6)
7346
+
for i := uint64(0); i < n; i++ {
7347
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
7348
+
if err != nil {
7349
+
return err
7350
+
}
7351
+
7352
+
if !ok {
7353
+
// Field doesn't exist on this type, so ignore it
7354
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
7355
+
return err
7356
+
}
7357
+
continue
7358
+
}
7359
+
7360
+
switch string(nameBuf[:nameLen]) {
7361
+
// t.Repo (string) (string)
7362
+
case "repo":
7363
+
7364
+
{
7365
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7366
+
if err != nil {
7367
+
return err
7368
+
}
7369
+
7370
+
t.Repo = string(sval)
7371
+
}
7372
+
// t.Branch (string) (string)
7373
+
case "branch":
7374
+
7375
+
{
7376
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
7377
+
if err != nil {
7378
+
return err
7379
+
}
7380
+
7381
+
t.Branch = string(sval)
7669
7382
}
7670
7383
7671
7384
default:
+19
-15
api/tangled/gitrefUpdate.go
+19
-15
api/tangled/gitrefUpdate.go
···
33
33
RepoName string `json:"repoName" cborgen:"repoName"`
34
34
}
35
35
36
-
type GitRefUpdate_Meta struct {
37
-
CommitCount *GitRefUpdate_Meta_CommitCount `json:"commitCount" cborgen:"commitCount"`
38
-
IsDefaultRef bool `json:"isDefaultRef" cborgen:"isDefaultRef"`
39
-
LangBreakdown *GitRefUpdate_Meta_LangBreakdown `json:"langBreakdown,omitempty" cborgen:"langBreakdown,omitempty"`
36
+
// GitRefUpdate_CommitCountBreakdown is a "commitCountBreakdown" in the sh.tangled.git.refUpdate schema.
37
+
type GitRefUpdate_CommitCountBreakdown struct {
38
+
ByEmail []*GitRefUpdate_IndividualEmailCommitCount `json:"byEmail,omitempty" cborgen:"byEmail,omitempty"`
40
39
}
41
40
42
-
type GitRefUpdate_Meta_CommitCount struct {
43
-
ByEmail []*GitRefUpdate_Meta_CommitCount_ByEmail_Elem `json:"byEmail,omitempty" cborgen:"byEmail,omitempty"`
44
-
}
45
-
46
-
type GitRefUpdate_Meta_CommitCount_ByEmail_Elem struct {
41
+
// GitRefUpdate_IndividualEmailCommitCount is a "individualEmailCommitCount" in the sh.tangled.git.refUpdate schema.
42
+
type GitRefUpdate_IndividualEmailCommitCount struct {
47
43
Count int64 `json:"count" cborgen:"count"`
48
44
Email string `json:"email" cborgen:"email"`
49
45
}
50
46
51
-
type GitRefUpdate_Meta_LangBreakdown struct {
52
-
Inputs []*GitRefUpdate_Pair `json:"inputs,omitempty" cborgen:"inputs,omitempty"`
53
-
}
54
-
55
-
// GitRefUpdate_Pair is a "pair" in the sh.tangled.git.refUpdate schema.
56
-
type GitRefUpdate_Pair struct {
47
+
// GitRefUpdate_IndividualLanguageSize is a "individualLanguageSize" in the sh.tangled.git.refUpdate schema.
48
+
type GitRefUpdate_IndividualLanguageSize struct {
57
49
Lang string `json:"lang" cborgen:"lang"`
58
50
Size int64 `json:"size" cborgen:"size"`
59
51
}
52
+
53
+
// GitRefUpdate_LangBreakdown is a "langBreakdown" in the sh.tangled.git.refUpdate schema.
54
+
type GitRefUpdate_LangBreakdown struct {
55
+
Inputs []*GitRefUpdate_IndividualLanguageSize `json:"inputs,omitempty" cborgen:"inputs,omitempty"`
56
+
}
57
+
58
+
// GitRefUpdate_Meta is a "meta" in the sh.tangled.git.refUpdate schema.
59
+
type GitRefUpdate_Meta struct {
60
+
CommitCount *GitRefUpdate_CommitCountBreakdown `json:"commitCount" cborgen:"commitCount"`
61
+
IsDefaultRef bool `json:"isDefaultRef" cborgen:"isDefaultRef"`
62
+
LangBreakdown *GitRefUpdate_LangBreakdown `json:"langBreakdown,omitempty" cborgen:"langBreakdown,omitempty"`
63
+
}
-1
api/tangled/issuecomment.go
-1
api/tangled/issuecomment.go
···
19
19
type RepoIssueComment struct {
20
20
LexiconTypeID string `json:"$type,const=sh.tangled.repo.issue.comment" cborgen:"$type,const=sh.tangled.repo.issue.comment"`
21
21
Body string `json:"body" cborgen:"body"`
22
-
CommentId *int64 `json:"commentId,omitempty" cborgen:"commentId,omitempty"`
23
22
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
24
23
Issue string `json:"issue" cborgen:"issue"`
25
24
Owner *string `json:"owner,omitempty" cborgen:"owner,omitempty"`
+4
-7
api/tangled/pullcomment.go
+4
-7
api/tangled/pullcomment.go
···
17
17
} //
18
18
// RECORDTYPE: RepoPullComment
19
19
type RepoPullComment struct {
20
-
LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull.comment" cborgen:"$type,const=sh.tangled.repo.pull.comment"`
21
-
Body string `json:"body" cborgen:"body"`
22
-
CommentId *int64 `json:"commentId,omitempty" cborgen:"commentId,omitempty"`
23
-
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
24
-
Owner *string `json:"owner,omitempty" cborgen:"owner,omitempty"`
25
-
Pull string `json:"pull" cborgen:"pull"`
26
-
Repo *string `json:"repo,omitempty" cborgen:"repo,omitempty"`
20
+
LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull.comment" cborgen:"$type,const=sh.tangled.repo.pull.comment"`
21
+
Body string `json:"body" cborgen:"body"`
22
+
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
23
+
Pull string `json:"pull" cborgen:"pull"`
27
24
}
-2
api/tangled/repoissue.go
-2
api/tangled/repoissue.go
···
20
20
LexiconTypeID string `json:"$type,const=sh.tangled.repo.issue" cborgen:"$type,const=sh.tangled.repo.issue"`
21
21
Body *string `json:"body,omitempty" cborgen:"body,omitempty"`
22
22
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
23
-
IssueId int64 `json:"issueId" cborgen:"issueId"`
24
-
Owner string `json:"owner" cborgen:"owner"`
25
23
Repo string `json:"repo" cborgen:"repo"`
26
24
Title string `json:"title" cborgen:"title"`
27
25
}
+7
-3
api/tangled/repopull.go
+7
-3
api/tangled/repopull.go
···
21
21
Body *string `json:"body,omitempty" cborgen:"body,omitempty"`
22
22
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
23
23
Patch string `json:"patch" cborgen:"patch"`
24
-
PullId int64 `json:"pullId" cborgen:"pullId"`
25
24
Source *RepoPull_Source `json:"source,omitempty" cborgen:"source,omitempty"`
26
-
TargetBranch string `json:"targetBranch" cborgen:"targetBranch"`
27
-
TargetRepo string `json:"targetRepo" cborgen:"targetRepo"`
25
+
Target *RepoPull_Target `json:"target" cborgen:"target"`
28
26
Title string `json:"title" cborgen:"title"`
29
27
}
30
28
···
34
32
Repo *string `json:"repo,omitempty" cborgen:"repo,omitempty"`
35
33
Sha string `json:"sha" cborgen:"sha"`
36
34
}
35
+
36
+
// RepoPull_Target is a "target" in the sh.tangled.repo.pull schema.
37
+
type RepoPull_Target struct {
38
+
Branch string `json:"branch" cborgen:"branch"`
39
+
Repo string `json:"repo" cborgen:"repo"`
40
+
}
+1
-1
appview/config/config.go
+1
-1
appview/config/config.go
+4
-4
appview/db/follow.go
+4
-4
appview/db/follow.go
···
56
56
}
57
57
58
58
type FollowStats struct {
59
-
Followers int
60
-
Following int
59
+
Followers int64
60
+
Following int64
61
61
}
62
62
63
63
func GetFollowerFollowingCount(e Execer, did string) (FollowStats, error) {
64
-
followers, following := 0, 0
64
+
var followers, following int64
65
65
err := e.QueryRow(
66
66
`SELECT
67
67
COUNT(CASE WHEN subject_did = ? THEN 1 END) AS followers,
···
122
122
123
123
for rows.Next() {
124
124
var did string
125
-
var followers, following int
125
+
var followers, following int64
126
126
if err := rows.Scan(&did, &followers, &following); err != nil {
127
127
return nil, err
128
128
}
+105
appview/db/issues.go
+105
appview/db/issues.go
···
3
3
import (
4
4
"database/sql"
5
5
"fmt"
6
+
mathrand "math/rand/v2"
6
7
"strings"
7
8
"time"
8
9
···
47
48
48
49
func (i *Issue) AtUri() syntax.ATURI {
49
50
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.OwnerDid, tangled.RepoIssueNSID, i.Rkey))
51
+
}
52
+
53
+
func IssueFromRecord(did, rkey string, record tangled.RepoIssue) Issue {
54
+
created, err := time.Parse(time.RFC3339, record.CreatedAt)
55
+
if err != nil {
56
+
created = time.Now()
57
+
}
58
+
59
+
body := ""
60
+
if record.Body != nil {
61
+
body = *record.Body
62
+
}
63
+
64
+
return Issue{
65
+
RepoAt: syntax.ATURI(record.Repo),
66
+
OwnerDid: did,
67
+
Rkey: rkey,
68
+
Created: created,
69
+
Title: record.Title,
70
+
Body: body,
71
+
Open: true, // new issues are open by default
72
+
}
73
+
}
74
+
75
+
func ResolveIssueFromAtUri(e Execer, issueUri syntax.ATURI) (syntax.ATURI, int, error) {
76
+
ownerDid := issueUri.Authority().String()
77
+
issueRkey := issueUri.RecordKey().String()
78
+
79
+
var repoAt string
80
+
var issueId int
81
+
82
+
query := `select repo_at, issue_id from issues where owner_did = ? and rkey = ?`
83
+
err := e.QueryRow(query, ownerDid, issueRkey).Scan(&repoAt, &issueId)
84
+
if err != nil {
85
+
return "", 0, err
86
+
}
87
+
88
+
return syntax.ATURI(repoAt), issueId, nil
89
+
}
90
+
91
+
func IssueCommentFromRecord(e Execer, did, rkey string, record tangled.RepoIssueComment) (Comment, error) {
92
+
created, err := time.Parse(time.RFC3339, record.CreatedAt)
93
+
if err != nil {
94
+
created = time.Now()
95
+
}
96
+
97
+
ownerDid := did
98
+
if record.Owner != nil {
99
+
ownerDid = *record.Owner
100
+
}
101
+
102
+
issueUri, err := syntax.ParseATURI(record.Issue)
103
+
if err != nil {
104
+
return Comment{}, err
105
+
}
106
+
107
+
repoAt, issueId, err := ResolveIssueFromAtUri(e, issueUri)
108
+
if err != nil {
109
+
return Comment{}, err
110
+
}
111
+
112
+
comment := Comment{
113
+
OwnerDid: ownerDid,
114
+
RepoAt: repoAt,
115
+
Rkey: rkey,
116
+
Body: record.Body,
117
+
Issue: issueId,
118
+
CommentId: mathrand.IntN(1000000),
119
+
Created: &created,
120
+
}
121
+
122
+
return comment, nil
50
123
}
51
124
52
125
func NewIssue(tx *sql.Tx, issue *Issue) error {
···
550
623
deleted = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
551
624
where repo_at = ? and issue_id = ? and comment_id = ?
552
625
`, repoAt, issueId, commentId)
626
+
return err
627
+
}
628
+
629
+
func UpdateCommentByRkey(e Execer, ownerDid, rkey, newBody string) error {
630
+
_, err := e.Exec(
631
+
`
632
+
update comments
633
+
set body = ?,
634
+
edited = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
635
+
where owner_did = ? and rkey = ?
636
+
`, newBody, ownerDid, rkey)
637
+
return err
638
+
}
639
+
640
+
func DeleteCommentByRkey(e Execer, ownerDid, rkey string) error {
641
+
_, err := e.Exec(
642
+
`
643
+
update comments
644
+
set body = "",
645
+
deleted = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
646
+
where owner_did = ? and rkey = ?
647
+
`, ownerDid, rkey)
648
+
return err
649
+
}
650
+
651
+
func UpdateIssueByRkey(e Execer, ownerDid, rkey, title, body string) error {
652
+
_, err := e.Exec(`update issues set title = ?, body = ? where owner_did = ? and rkey = ?`, title, body, ownerDid, rkey)
653
+
return err
654
+
}
655
+
656
+
func DeleteIssueByRkey(e Execer, ownerDid, rkey string) error {
657
+
_, err := e.Exec(`delete from issues where owner_did = ? and rkey = ?`, ownerDid, rkey)
553
658
return err
554
659
}
555
660
+14
appview/db/profile.go
+14
appview/db/profile.go
···
22
22
ByMonth []ByMonth
23
23
}
24
24
25
+
func (p *ProfileTimeline) IsEmpty() bool {
26
+
if p == nil {
27
+
return true
28
+
}
29
+
30
+
for _, m := range p.ByMonth {
31
+
if !m.IsEmpty() {
32
+
return false
33
+
}
34
+
}
35
+
36
+
return true
37
+
}
38
+
25
39
type ByMonth struct {
26
40
RepoEvents []RepoEvent
27
41
IssueEvents IssueEvents
+9
-8
appview/db/pulls.go
+9
-8
appview/db/pulls.go
···
91
91
}
92
92
93
93
record := tangled.RepoPull{
94
-
Title: p.Title,
95
-
Body: &p.Body,
96
-
CreatedAt: p.Created.Format(time.RFC3339),
97
-
PullId: int64(p.PullId),
98
-
TargetRepo: p.RepoAt.String(),
99
-
TargetBranch: p.TargetBranch,
100
-
Patch: p.LatestPatch(),
101
-
Source: source,
94
+
Title: p.Title,
95
+
Body: &p.Body,
96
+
CreatedAt: p.Created.Format(time.RFC3339),
97
+
Target: &tangled.RepoPull_Target{
98
+
Repo: p.RepoAt.String(),
99
+
Branch: p.TargetBranch,
100
+
},
101
+
Patch: p.LatestPatch(),
102
+
Source: source,
102
103
}
103
104
return record
104
105
}
+4
-4
appview/db/punchcard.go
+4
-4
appview/db/punchcard.go
···
29
29
Punches []Punch
30
30
}
31
31
32
-
func MakePunchcard(e Execer, filters ...filter) (Punchcard, error) {
33
-
punchcard := Punchcard{}
32
+
func MakePunchcard(e Execer, filters ...filter) (*Punchcard, error) {
33
+
punchcard := &Punchcard{}
34
34
now := time.Now()
35
35
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
36
36
endOfYear := time.Date(now.Year(), 12, 31, 0, 0, 0, 0, time.UTC)
···
63
63
64
64
rows, err := e.Query(query, args...)
65
65
if err != nil {
66
-
return punchcard, err
66
+
return nil, err
67
67
}
68
68
defer rows.Close()
69
69
···
72
72
var date string
73
73
var count sql.NullInt64
74
74
if err := rows.Scan(&date, &count); err != nil {
75
-
return punchcard, err
75
+
return nil, err
76
76
}
77
77
78
78
punch.Date, err = time.Parse(time.DateOnly, date)
+35
-71
appview/db/repos.go
+35
-71
appview/db/repos.go
···
2
2
3
3
import (
4
4
"database/sql"
5
+
"errors"
5
6
"fmt"
6
7
"log"
7
8
"slices"
···
36
37
func (r Repo) DidSlashRepo() string {
37
38
p, _ := securejoin.SecureJoin(r.Did, r.Name)
38
39
return p
39
-
}
40
-
41
-
func GetAllRepos(e Execer, limit int) ([]Repo, error) {
42
-
var repos []Repo
43
-
44
-
rows, err := e.Query(
45
-
`select did, name, knot, rkey, description, created, source
46
-
from repos
47
-
order by created desc
48
-
limit ?
49
-
`,
50
-
limit,
51
-
)
52
-
if err != nil {
53
-
return nil, err
54
-
}
55
-
defer rows.Close()
56
-
57
-
for rows.Next() {
58
-
var repo Repo
59
-
err := scanRepo(
60
-
rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created, &repo.Source,
61
-
)
62
-
if err != nil {
63
-
return nil, err
64
-
}
65
-
repos = append(repos, repo)
66
-
}
67
-
68
-
if err := rows.Err(); err != nil {
69
-
return nil, err
70
-
}
71
-
72
-
return repos, nil
73
40
}
74
41
75
42
func GetRepos(e Execer, limit int, filters ...filter) ([]Repo, error) {
···
310
277
311
278
slices.SortFunc(repos, func(a, b Repo) int {
312
279
if a.Created.After(b.Created) {
313
-
return 1
280
+
return -1
314
281
}
315
-
return -1
282
+
return 1
316
283
})
317
284
318
285
return repos, nil
319
286
}
320
287
288
+
func CountRepos(e Execer, filters ...filter) (int64, error) {
289
+
var conditions []string
290
+
var args []any
291
+
for _, filter := range filters {
292
+
conditions = append(conditions, filter.Condition())
293
+
args = append(args, filter.Arg()...)
294
+
}
295
+
296
+
whereClause := ""
297
+
if conditions != nil {
298
+
whereClause = " where " + strings.Join(conditions, " and ")
299
+
}
300
+
301
+
repoQuery := fmt.Sprintf(`select count(1) from repos %s`, whereClause)
302
+
var count int64
303
+
err := e.QueryRow(repoQuery, args...).Scan(&count)
304
+
305
+
if !errors.Is(err, sql.ErrNoRows) && err != nil {
306
+
return 0, err
307
+
}
308
+
309
+
return count, nil
310
+
}
311
+
321
312
func GetAllReposByDid(e Execer, did string) ([]Repo, error) {
322
313
var repos []Repo
323
314
···
466
457
var repos []Repo
467
458
468
459
rows, err := e.Query(
469
-
`select did, name, knot, rkey, description, created, source
470
-
from repos
471
-
where did = ? and source is not null and source != ''
472
-
order by created desc`,
473
-
did,
460
+
`select distinct r.did, r.name, r.knot, r.rkey, r.description, r.created, r.source
461
+
from repos r
462
+
left join collaborators c on r.at_uri = c.repo_at
463
+
where (r.did = ? or c.subject_did = ?)
464
+
and r.source is not null
465
+
and r.source != ''
466
+
order by r.created desc`,
467
+
did, did,
474
468
)
475
469
if err != nil {
476
470
return nil, err
···
567
561
IssueCount IssueCount
568
562
PullCount PullCount
569
563
}
570
-
571
-
func scanRepo(rows *sql.Rows, did, name, knot, rkey, description *string, created *time.Time, source *string) error {
572
-
var createdAt string
573
-
var nullableDescription sql.NullString
574
-
var nullableSource sql.NullString
575
-
if err := rows.Scan(did, name, knot, rkey, &nullableDescription, &createdAt, &nullableSource); err != nil {
576
-
return err
577
-
}
578
-
579
-
if nullableDescription.Valid {
580
-
*description = nullableDescription.String
581
-
} else {
582
-
*description = ""
583
-
}
584
-
585
-
createdAtTime, err := time.Parse(time.RFC3339, createdAt)
586
-
if err != nil {
587
-
*created = time.Now()
588
-
} else {
589
-
*created = createdAtTime
590
-
}
591
-
592
-
if nullableSource.Valid {
593
-
*source = nullableSource.String
594
-
} else {
595
-
*source = ""
596
-
}
597
-
598
-
return nil
599
-
}
+26
appview/db/star.go
+26
appview/db/star.go
···
1
1
package db
2
2
3
3
import (
4
+
"database/sql"
5
+
"errors"
4
6
"fmt"
5
7
"log"
6
8
"strings"
···
181
183
}
182
184
183
185
return stars, nil
186
+
}
187
+
188
+
func CountStars(e Execer, filters ...filter) (int64, error) {
189
+
var conditions []string
190
+
var args []any
191
+
for _, filter := range filters {
192
+
conditions = append(conditions, filter.Condition())
193
+
args = append(args, filter.Arg()...)
194
+
}
195
+
196
+
whereClause := ""
197
+
if conditions != nil {
198
+
whereClause = " where " + strings.Join(conditions, " and ")
199
+
}
200
+
201
+
repoQuery := fmt.Sprintf(`select count(1) from stars %s`, whereClause)
202
+
var count int64
203
+
err := e.QueryRow(repoQuery, args...).Scan(&count)
204
+
205
+
if !errors.Is(err, sql.ErrNoRows) && err != nil {
206
+
return 0, err
207
+
}
208
+
209
+
return count, nil
184
210
}
185
211
186
212
func GetAllStars(e Execer, limit int) ([]Star, error) {
+24
appview/db/strings.go
+24
appview/db/strings.go
···
206
206
return all, nil
207
207
}
208
208
209
+
func CountStrings(e Execer, filters ...filter) (int64, error) {
210
+
var conditions []string
211
+
var args []any
212
+
for _, filter := range filters {
213
+
conditions = append(conditions, filter.Condition())
214
+
args = append(args, filter.Arg()...)
215
+
}
216
+
217
+
whereClause := ""
218
+
if conditions != nil {
219
+
whereClause = " where " + strings.Join(conditions, " and ")
220
+
}
221
+
222
+
repoQuery := fmt.Sprintf(`select count(1) from strings %s`, whereClause)
223
+
var count int64
224
+
err := e.QueryRow(repoQuery, args...).Scan(&count)
225
+
226
+
if !errors.Is(err, sql.ErrNoRows) && err != nil {
227
+
return 0, err
228
+
}
229
+
230
+
return count, nil
231
+
}
232
+
209
233
func DeleteString(e Execer, filters ...filter) error {
210
234
var conditions []string
211
235
var args []any
+178
-6
appview/ingester.go
+178
-6
appview/ingester.go
···
5
5
"encoding/json"
6
6
"fmt"
7
7
"log/slog"
8
+
"strings"
8
9
"time"
9
10
10
11
"github.com/bluesky-social/indigo/atproto/syntax"
···
14
15
"tangled.sh/tangled.sh/core/api/tangled"
15
16
"tangled.sh/tangled.sh/core/appview/config"
16
17
"tangled.sh/tangled.sh/core/appview/db"
18
+
"tangled.sh/tangled.sh/core/appview/pages/markup"
17
19
"tangled.sh/tangled.sh/core/appview/serververify"
18
20
"tangled.sh/tangled.sh/core/idresolver"
19
21
"tangled.sh/tangled.sh/core/rbac"
···
61
63
case tangled.ActorProfileNSID:
62
64
err = i.ingestProfile(e)
63
65
case tangled.SpindleMemberNSID:
64
-
err = i.ingestSpindleMember(e)
66
+
err = i.ingestSpindleMember(ctx, e)
65
67
case tangled.SpindleNSID:
66
-
err = i.ingestSpindle(e)
68
+
err = i.ingestSpindle(ctx, e)
67
69
case tangled.KnotMemberNSID:
68
70
err = i.ingestKnotMember(e)
69
71
case tangled.KnotNSID:
70
72
err = i.ingestKnot(e)
71
73
case tangled.StringNSID:
72
74
err = i.ingestString(e)
75
+
case tangled.RepoIssueNSID:
76
+
err = i.ingestIssue(ctx, e)
77
+
case tangled.RepoIssueCommentNSID:
78
+
err = i.ingestIssueComment(e)
73
79
}
74
80
l = i.Logger.With("nsid", e.Commit.Collection)
75
81
}
···
340
346
return nil
341
347
}
342
348
343
-
func (i *Ingester) ingestSpindleMember(e *models.Event) error {
349
+
func (i *Ingester) ingestSpindleMember(ctx context.Context, e *models.Event) error {
344
350
did := e.Did
345
351
var err error
346
352
···
363
369
return fmt.Errorf("failed to enforce permissions: %w", err)
364
370
}
365
371
366
-
memberId, err := i.IdResolver.ResolveIdent(context.Background(), record.Subject)
372
+
memberId, err := i.IdResolver.ResolveIdent(ctx, record.Subject)
367
373
if err != nil {
368
374
return err
369
375
}
···
446
452
return nil
447
453
}
448
454
449
-
func (i *Ingester) ingestSpindle(e *models.Event) error {
455
+
func (i *Ingester) ingestSpindle(ctx context.Context, e *models.Event) error {
450
456
did := e.Did
451
457
var err error
452
458
···
479
485
return err
480
486
}
481
487
482
-
err = serververify.RunVerification(context.Background(), instance, did, i.Config.Core.Dev)
488
+
err = serververify.RunVerification(ctx, instance, did, i.Config.Core.Dev)
483
489
if err != nil {
484
490
l.Error("failed to add spindle to db", "err", err, "instance", instance)
485
491
return err
···
769
775
770
776
return nil
771
777
}
778
+
func (i *Ingester) ingestIssue(ctx context.Context, e *models.Event) error {
779
+
did := e.Did
780
+
rkey := e.Commit.RKey
781
+
782
+
var err error
783
+
784
+
l := i.Logger.With("handler", "ingestIssue", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
785
+
l.Info("ingesting record")
786
+
787
+
ddb, ok := i.Db.Execer.(*db.DB)
788
+
if !ok {
789
+
return fmt.Errorf("failed to index issue record, invalid db cast")
790
+
}
791
+
792
+
switch e.Commit.Operation {
793
+
case models.CommitOperationCreate:
794
+
raw := json.RawMessage(e.Commit.Record)
795
+
record := tangled.RepoIssue{}
796
+
err = json.Unmarshal(raw, &record)
797
+
if err != nil {
798
+
l.Error("invalid record", "err", err)
799
+
return err
800
+
}
801
+
802
+
issue := db.IssueFromRecord(did, rkey, record)
803
+
804
+
sanitizer := markup.NewSanitizer()
805
+
if st := strings.TrimSpace(sanitizer.SanitizeDescription(issue.Title)); st == "" {
806
+
return fmt.Errorf("title is empty after HTML sanitization")
807
+
}
808
+
if sb := strings.TrimSpace(sanitizer.SanitizeDefault(issue.Body)); sb == "" {
809
+
return fmt.Errorf("body is empty after HTML sanitization")
810
+
}
811
+
812
+
tx, err := ddb.BeginTx(ctx, nil)
813
+
if err != nil {
814
+
l.Error("failed to begin transaction", "err", err)
815
+
return err
816
+
}
817
+
818
+
err = db.NewIssue(tx, &issue)
819
+
if err != nil {
820
+
l.Error("failed to create issue", "err", err)
821
+
return err
822
+
}
823
+
824
+
return nil
825
+
826
+
case models.CommitOperationUpdate:
827
+
raw := json.RawMessage(e.Commit.Record)
828
+
record := tangled.RepoIssue{}
829
+
err = json.Unmarshal(raw, &record)
830
+
if err != nil {
831
+
l.Error("invalid record", "err", err)
832
+
return err
833
+
}
834
+
835
+
body := ""
836
+
if record.Body != nil {
837
+
body = *record.Body
838
+
}
839
+
840
+
sanitizer := markup.NewSanitizer()
841
+
if st := strings.TrimSpace(sanitizer.SanitizeDescription(record.Title)); st == "" {
842
+
return fmt.Errorf("title is empty after HTML sanitization")
843
+
}
844
+
if sb := strings.TrimSpace(sanitizer.SanitizeDefault(body)); sb == "" {
845
+
return fmt.Errorf("body is empty after HTML sanitization")
846
+
}
847
+
848
+
err = db.UpdateIssueByRkey(ddb, did, rkey, record.Title, body)
849
+
if err != nil {
850
+
l.Error("failed to update issue", "err", err)
851
+
return err
852
+
}
853
+
854
+
return nil
855
+
856
+
case models.CommitOperationDelete:
857
+
if err := db.DeleteIssueByRkey(ddb, did, rkey); err != nil {
858
+
l.Error("failed to delete", "err", err)
859
+
return fmt.Errorf("failed to delete issue record: %w", err)
860
+
}
861
+
862
+
return nil
863
+
}
864
+
865
+
return fmt.Errorf("unknown operation: %s", e.Commit.Operation)
866
+
}
867
+
868
+
func (i *Ingester) ingestIssueComment(e *models.Event) error {
869
+
did := e.Did
870
+
rkey := e.Commit.RKey
871
+
872
+
var err error
873
+
874
+
l := i.Logger.With("handler", "ingestIssueComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
875
+
l.Info("ingesting record")
876
+
877
+
ddb, ok := i.Db.Execer.(*db.DB)
878
+
if !ok {
879
+
return fmt.Errorf("failed to index issue comment record, invalid db cast")
880
+
}
881
+
882
+
switch e.Commit.Operation {
883
+
case models.CommitOperationCreate:
884
+
raw := json.RawMessage(e.Commit.Record)
885
+
record := tangled.RepoIssueComment{}
886
+
err = json.Unmarshal(raw, &record)
887
+
if err != nil {
888
+
l.Error("invalid record", "err", err)
889
+
return err
890
+
}
891
+
892
+
comment, err := db.IssueCommentFromRecord(ddb, did, rkey, record)
893
+
if err != nil {
894
+
l.Error("failed to parse comment from record", "err", err)
895
+
return err
896
+
}
897
+
898
+
sanitizer := markup.NewSanitizer()
899
+
if sb := strings.TrimSpace(sanitizer.SanitizeDefault(comment.Body)); sb == "" {
900
+
return fmt.Errorf("body is empty after HTML sanitization")
901
+
}
902
+
903
+
err = db.NewIssueComment(ddb, &comment)
904
+
if err != nil {
905
+
l.Error("failed to create issue comment", "err", err)
906
+
return err
907
+
}
908
+
909
+
return nil
910
+
911
+
case models.CommitOperationUpdate:
912
+
raw := json.RawMessage(e.Commit.Record)
913
+
record := tangled.RepoIssueComment{}
914
+
err = json.Unmarshal(raw, &record)
915
+
if err != nil {
916
+
l.Error("invalid record", "err", err)
917
+
return err
918
+
}
919
+
920
+
sanitizer := markup.NewSanitizer()
921
+
if sb := strings.TrimSpace(sanitizer.SanitizeDefault(record.Body)); sb == "" {
922
+
return fmt.Errorf("body is empty after HTML sanitization")
923
+
}
924
+
925
+
err = db.UpdateCommentByRkey(ddb, did, rkey, record.Body)
926
+
if err != nil {
927
+
l.Error("failed to update issue comment", "err", err)
928
+
return err
929
+
}
930
+
931
+
return nil
932
+
933
+
case models.CommitOperationDelete:
934
+
if err := db.DeleteCommentByRkey(ddb, did, rkey); err != nil {
935
+
l.Error("failed to delete", "err", err)
936
+
return fmt.Errorf("failed to delete issue comment record: %w", err)
937
+
}
938
+
939
+
return nil
940
+
}
941
+
942
+
return fmt.Errorf("unknown operation: %s", e.Commit.Operation)
943
+
}
+3
-9
appview/issues/issues.go
+3
-9
appview/issues/issues.go
···
278
278
}
279
279
280
280
createdAt := time.Now().Format(time.RFC3339)
281
-
commentIdInt64 := int64(commentId)
282
281
ownerDid := user.Did
283
282
issueAt, err := db.GetIssueAt(rp.db, f.RepoAt(), issueIdInt)
284
283
if err != nil {
···
302
301
Val: &tangled.RepoIssueComment{
303
302
Repo: &atUri,
304
303
Issue: issueAt,
305
-
CommentId: &commentIdInt64,
306
304
Owner: &ownerDid,
307
305
Body: body,
308
306
CreatedAt: createdAt,
···
451
449
repoAt := record["repo"].(string)
452
450
issueAt := record["issue"].(string)
453
451
createdAt := record["createdAt"].(string)
454
-
commentIdInt64 := int64(commentIdInt)
455
452
456
453
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
457
454
Collection: tangled.RepoIssueCommentNSID,
···
462
459
Val: &tangled.RepoIssueComment{
463
460
Repo: &repoAt,
464
461
Issue: issueAt,
465
-
CommentId: &commentIdInt64,
466
462
Owner: &comment.OwnerDid,
467
463
Body: newBody,
468
464
CreatedAt: createdAt,
···
687
683
Rkey: issue.Rkey,
688
684
Record: &lexutil.LexiconTypeDecoder{
689
685
Val: &tangled.RepoIssue{
690
-
Repo: atUri,
691
-
Title: title,
692
-
Body: &body,
693
-
Owner: user.Did,
694
-
IssueId: int64(issue.IssueId),
686
+
Repo: atUri,
687
+
Title: title,
688
+
Body: &body,
695
689
},
696
690
},
697
691
})
+5
appview/oauth/handler/handler.go
+5
appview/oauth/handler/handler.go
+35
appview/pages/cache.go
+35
appview/pages/cache.go
···
1
+
package pages
2
+
3
+
import (
4
+
"sync"
5
+
)
6
+
7
+
type TmplCache[K comparable, V any] struct {
8
+
data map[K]V
9
+
mutex sync.RWMutex
10
+
}
11
+
12
+
func NewTmplCache[K comparable, V any]() *TmplCache[K, V] {
13
+
return &TmplCache[K, V]{
14
+
data: make(map[K]V),
15
+
}
16
+
}
17
+
18
+
func (c *TmplCache[K, V]) Get(key K) (V, bool) {
19
+
c.mutex.RLock()
20
+
defer c.mutex.RUnlock()
21
+
val, exists := c.data[key]
22
+
return val, exists
23
+
}
24
+
25
+
func (c *TmplCache[K, V]) Set(key K, value V) {
26
+
c.mutex.Lock()
27
+
defer c.mutex.Unlock()
28
+
c.data[key] = value
29
+
}
30
+
31
+
func (c *TmplCache[K, V]) Size() int {
32
+
c.mutex.RLock()
33
+
defer c.mutex.RUnlock()
34
+
return len(c.data)
35
+
}
+2
appview/pages/markup/markdown.go
+2
appview/pages/markup/markdown.go
···
11
11
12
12
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
13
13
"github.com/alecthomas/chroma/v2/styles"
14
+
treeblood "github.com/wyatt915/goldmark-treeblood"
14
15
"github.com/yuin/goldmark"
15
16
highlighting "github.com/yuin/goldmark-highlighting/v2"
16
17
"github.com/yuin/goldmark/ast"
···
59
60
extension.NewFootnote(
60
61
extension.WithFootnoteIDPrefix([]byte("footnote")),
61
62
),
63
+
treeblood.MathML(),
62
64
),
63
65
goldmark.WithParserOptions(
64
66
parser.WithAutoHeadingID(),
+17
appview/pages/markup/sanitizer.go
+17
appview/pages/markup/sanitizer.go
···
97
97
"margin-bottom",
98
98
)
99
99
100
+
// math
101
+
mathAttrs := []string{
102
+
"accent", "columnalign", "columnlines", "columnspan", "dir", "display",
103
+
"displaystyle", "encoding", "fence", "form", "largeop", "linebreak",
104
+
"linethickness", "lspace", "mathcolor", "mathsize", "mathvariant", "minsize",
105
+
"movablelimits", "notation", "rowalign", "rspace", "rowspacing", "rowspan",
106
+
"scriptlevel", "stretchy", "symmetric", "title", "voffset", "width",
107
+
}
108
+
mathElements := []string{
109
+
"annotation", "math", "menclose", "merror", "mfrac", "mi", "mmultiscripts",
110
+
"mn", "mo", "mover", "mpadded", "mprescripts", "mroot", "mrow", "mspace",
111
+
"msqrt", "mstyle", "msub", "msubsup", "msup", "mtable", "mtd", "mtext",
112
+
"mtr", "munder", "munderover", "semantics",
113
+
}
114
+
policy.AllowNoAttrs().OnElements(mathElements...)
115
+
policy.AllowAttrs(mathAttrs...).OnElements(mathElements...)
116
+
100
117
return policy
101
118
}
102
119
+202
-164
appview/pages/pages.go
+202
-164
appview/pages/pages.go
···
9
9
"html/template"
10
10
"io"
11
11
"io/fs"
12
-
"log"
12
+
"log/slog"
13
13
"net/http"
14
14
"os"
15
15
"path/filepath"
···
42
42
var Files embed.FS
43
43
44
44
type Pages struct {
45
-
mu sync.RWMutex
46
-
t map[string]*template.Template
45
+
mu sync.RWMutex
46
+
cache *TmplCache[string, *template.Template]
47
47
48
48
avatar config.AvatarConfig
49
49
resolver *idresolver.Resolver
50
50
dev bool
51
-
embedFS embed.FS
51
+
embedFS fs.FS
52
52
templateDir string // Path to templates on disk for dev mode
53
53
rctx *markup.RenderContext
54
+
logger *slog.Logger
54
55
}
55
56
56
57
func NewPages(config *config.Config, res *idresolver.Resolver) *Pages {
···
64
65
65
66
p := &Pages{
66
67
mu: sync.RWMutex{},
67
-
t: make(map[string]*template.Template),
68
+
cache: NewTmplCache[string, *template.Template](),
68
69
dev: config.Core.Dev,
69
70
avatar: config.Avatar,
70
-
embedFS: Files,
71
71
rctx: rctx,
72
72
resolver: res,
73
73
templateDir: "appview/pages",
74
+
logger: slog.Default().With("component", "pages"),
74
75
}
75
76
76
-
// Initial load of all templates
77
-
p.loadAllTemplates()
77
+
if p.dev {
78
+
p.embedFS = os.DirFS(p.templateDir)
79
+
} else {
80
+
p.embedFS = Files
81
+
}
78
82
79
83
return p
80
84
}
81
85
82
-
func (p *Pages) loadAllTemplates() {
83
-
templates := make(map[string]*template.Template)
84
-
var fragmentPaths []string
86
+
func (p *Pages) pathToName(s string) string {
87
+
return strings.TrimSuffix(strings.TrimPrefix(s, "templates/"), ".html")
88
+
}
85
89
86
-
// Use embedded FS for initial loading
87
-
// First, collect all fragment paths
90
+
// reverse of pathToName
91
+
func (p *Pages) nameToPath(s string) string {
92
+
return "templates/" + s + ".html"
93
+
}
94
+
95
+
func (p *Pages) fragmentPaths() ([]string, error) {
96
+
var fragmentPaths []string
88
97
err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
89
98
if err != nil {
90
99
return err
···
98
107
if !strings.Contains(path, "fragments/") {
99
108
return nil
100
109
}
101
-
name := strings.TrimPrefix(path, "templates/")
102
-
name = strings.TrimSuffix(name, ".html")
103
-
tmpl, err := template.New(name).
104
-
Funcs(p.funcMap()).
105
-
ParseFS(p.embedFS, path)
106
-
if err != nil {
107
-
log.Fatalf("setting up fragment: %v", err)
108
-
}
109
-
templates[name] = tmpl
110
110
fragmentPaths = append(fragmentPaths, path)
111
-
log.Printf("loaded fragment: %s", name)
112
111
return nil
113
112
})
114
113
if err != nil {
115
-
log.Fatalf("walking template dir for fragments: %v", err)
114
+
return nil, err
116
115
}
117
116
118
-
// Then walk through and setup the rest of the templates
119
-
err = fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
117
+
return fragmentPaths, nil
118
+
}
119
+
120
+
func (p *Pages) fragments() (*template.Template, error) {
121
+
fragmentPaths, err := p.fragmentPaths()
122
+
if err != nil {
123
+
return nil, err
124
+
}
125
+
126
+
funcs := p.funcMap()
127
+
128
+
// parse all fragments together
129
+
allFragments := template.New("").Funcs(funcs)
130
+
for _, f := range fragmentPaths {
131
+
name := p.pathToName(f)
132
+
133
+
pf, err := template.New(name).
134
+
Funcs(funcs).
135
+
ParseFS(p.embedFS, f)
120
136
if err != nil {
121
-
return err
122
-
}
123
-
if d.IsDir() {
124
-
return nil
125
-
}
126
-
if !strings.HasSuffix(path, "html") {
127
-
return nil
137
+
return nil, err
128
138
}
129
-
// Skip fragments as they've already been loaded
130
-
if strings.Contains(path, "fragments/") {
131
-
return nil
132
-
}
133
-
// Skip layouts
134
-
if strings.Contains(path, "layouts/") {
135
-
return nil
136
-
}
137
-
name := strings.TrimPrefix(path, "templates/")
138
-
name = strings.TrimSuffix(name, ".html")
139
-
// Add the page template on top of the base
140
-
allPaths := []string{}
141
-
allPaths = append(allPaths, "templates/layouts/*.html")
142
-
allPaths = append(allPaths, fragmentPaths...)
143
-
allPaths = append(allPaths, path)
144
-
tmpl, err := template.New(name).
145
-
Funcs(p.funcMap()).
146
-
ParseFS(p.embedFS, allPaths...)
139
+
140
+
allFragments, err = allFragments.AddParseTree(name, pf.Tree)
147
141
if err != nil {
148
-
return fmt.Errorf("setting up template: %w", err)
142
+
return nil, err
149
143
}
150
-
templates[name] = tmpl
151
-
log.Printf("loaded template: %s", name)
152
-
return nil
153
-
})
154
-
if err != nil {
155
-
log.Fatalf("walking template dir: %v", err)
156
144
}
157
145
158
-
log.Printf("total templates loaded: %d", len(templates))
159
-
p.mu.Lock()
160
-
defer p.mu.Unlock()
161
-
p.t = templates
146
+
return allFragments, nil
162
147
}
163
148
164
-
// loadTemplateFromDisk loads a template from the filesystem in dev mode
165
-
func (p *Pages) loadTemplateFromDisk(name string) error {
166
-
if !p.dev {
167
-
return nil
149
+
// parse without memoization
150
+
func (p *Pages) rawParse(stack ...string) (*template.Template, error) {
151
+
paths, err := p.fragmentPaths()
152
+
if err != nil {
153
+
return nil, err
154
+
}
155
+
for _, s := range stack {
156
+
paths = append(paths, p.nameToPath(s))
168
157
}
169
158
170
-
log.Printf("reloading template from disk: %s", name)
171
-
172
-
// Find all fragments first
173
-
var fragmentPaths []string
174
-
err := filepath.WalkDir(filepath.Join(p.templateDir, "templates"), func(path string, d fs.DirEntry, err error) error {
175
-
if err != nil {
176
-
return err
177
-
}
178
-
if d.IsDir() {
179
-
return nil
180
-
}
181
-
if !strings.HasSuffix(path, ".html") {
182
-
return nil
183
-
}
184
-
if !strings.Contains(path, "fragments/") {
185
-
return nil
186
-
}
187
-
fragmentPaths = append(fragmentPaths, path)
188
-
return nil
189
-
})
159
+
funcs := p.funcMap()
160
+
top := stack[len(stack)-1]
161
+
parsed, err := template.New(top).
162
+
Funcs(funcs).
163
+
ParseFS(p.embedFS, paths...)
190
164
if err != nil {
191
-
return fmt.Errorf("walking disk template dir for fragments: %w", err)
165
+
return nil, err
192
166
}
193
167
194
-
// Find the template path on disk
195
-
templatePath := filepath.Join(p.templateDir, "templates", name+".html")
196
-
if _, err := os.Stat(templatePath); os.IsNotExist(err) {
197
-
return fmt.Errorf("template not found on disk: %s", name)
198
-
}
168
+
return parsed, nil
169
+
}
199
170
200
-
// Create a new template
201
-
tmpl := template.New(name).Funcs(p.funcMap())
171
+
func (p *Pages) parse(stack ...string) (*template.Template, error) {
172
+
key := strings.Join(stack, "|")
202
173
203
-
// Parse layouts
204
-
layoutGlob := filepath.Join(p.templateDir, "templates", "layouts", "*.html")
205
-
layouts, err := filepath.Glob(layoutGlob)
206
-
if err != nil {
207
-
return fmt.Errorf("finding layout templates: %w", err)
174
+
// never cache in dev mode
175
+
if cached, exists := p.cache.Get(key); !p.dev && exists {
176
+
return cached, nil
208
177
}
209
178
210
-
// Create paths for parsing
211
-
allFiles := append(layouts, fragmentPaths...)
212
-
allFiles = append(allFiles, templatePath)
213
-
214
-
// Parse all templates
215
-
tmpl, err = tmpl.ParseFiles(allFiles...)
179
+
result, err := p.rawParse(stack...)
216
180
if err != nil {
217
-
return fmt.Errorf("parsing template files: %w", err)
181
+
return nil, err
218
182
}
219
183
220
-
// Update the template in the map
221
-
p.mu.Lock()
222
-
defer p.mu.Unlock()
223
-
p.t[name] = tmpl
224
-
log.Printf("template reloaded from disk: %s", name)
225
-
return nil
184
+
p.cache.Set(key, result)
185
+
return result, nil
226
186
}
227
187
228
-
func (p *Pages) executeOrReload(templateName string, w io.Writer, base string, params any) error {
229
-
// In dev mode, reload the template from disk before executing
230
-
if p.dev {
231
-
if err := p.loadTemplateFromDisk(templateName); err != nil {
232
-
log.Printf("warning: failed to reload template %s from disk: %v", templateName, err)
233
-
// Continue with the existing template
234
-
}
188
+
func (p *Pages) parseBase(top string) (*template.Template, error) {
189
+
stack := []string{
190
+
"layouts/base",
191
+
top,
235
192
}
193
+
return p.parse(stack...)
194
+
}
236
195
237
-
p.mu.RLock()
238
-
defer p.mu.RUnlock()
239
-
tmpl, exists := p.t[templateName]
240
-
if !exists {
241
-
return fmt.Errorf("template not found: %s", templateName)
196
+
func (p *Pages) parseRepoBase(top string) (*template.Template, error) {
197
+
stack := []string{
198
+
"layouts/base",
199
+
"layouts/repobase",
200
+
top,
242
201
}
202
+
return p.parse(stack...)
203
+
}
243
204
244
-
if base == "" {
245
-
return tmpl.Execute(w, params)
246
-
} else {
247
-
return tmpl.ExecuteTemplate(w, base, params)
205
+
func (p *Pages) parseProfileBase(top string) (*template.Template, error) {
206
+
stack := []string{
207
+
"layouts/base",
208
+
"layouts/profilebase",
209
+
top,
248
210
}
211
+
return p.parse(stack...)
212
+
}
213
+
214
+
func (p *Pages) executePlain(name string, w io.Writer, params any) error {
215
+
tpl, err := p.parse(name)
216
+
if err != nil {
217
+
return err
218
+
}
219
+
220
+
return tpl.Execute(w, params)
249
221
}
250
222
251
223
func (p *Pages) execute(name string, w io.Writer, params any) error {
252
-
return p.executeOrReload(name, w, "layouts/base", params)
224
+
tpl, err := p.parseBase(name)
225
+
if err != nil {
226
+
return err
227
+
}
228
+
229
+
return tpl.ExecuteTemplate(w, "layouts/base", params)
253
230
}
254
231
255
-
func (p *Pages) executePlain(name string, w io.Writer, params any) error {
256
-
return p.executeOrReload(name, w, "", params)
232
+
func (p *Pages) executeRepo(name string, w io.Writer, params any) error {
233
+
tpl, err := p.parseRepoBase(name)
234
+
if err != nil {
235
+
return err
236
+
}
237
+
238
+
return tpl.ExecuteTemplate(w, "layouts/base", params)
257
239
}
258
240
259
-
func (p *Pages) executeRepo(name string, w io.Writer, params any) error {
260
-
return p.executeOrReload(name, w, "layouts/repobase", params)
241
+
func (p *Pages) executeProfile(name string, w io.Writer, params any) error {
242
+
tpl, err := p.parseProfileBase(name)
243
+
if err != nil {
244
+
return err
245
+
}
246
+
247
+
return tpl.ExecuteTemplate(w, "layouts/base", params)
261
248
}
262
249
263
250
func (p *Pages) Favicon(w io.Writer) error {
···
422
409
return p.execute("repo/fork", w, params)
423
410
}
424
411
425
-
type ProfileHomePageParams struct {
412
+
type ProfileCard struct {
413
+
UserDid string
414
+
UserHandle string
415
+
FollowStatus db.FollowStatus
416
+
Punchcard *db.Punchcard
417
+
Profile *db.Profile
418
+
Stats ProfileStats
419
+
Active string
420
+
}
421
+
422
+
type ProfileStats struct {
423
+
RepoCount int64
424
+
StarredCount int64
425
+
StringCount int64
426
+
FollowersCount int64
427
+
FollowingCount int64
428
+
}
429
+
430
+
func (p *ProfileCard) GetTabs() [][]any {
431
+
tabs := [][]any{
432
+
{"overview", "overview", "square-chart-gantt", nil},
433
+
{"repos", "repos", "book-marked", p.Stats.RepoCount},
434
+
{"starred", "starred", "star", p.Stats.StarredCount},
435
+
{"strings", "strings", "line-squiggle", p.Stats.StringCount},
436
+
}
437
+
438
+
return tabs
439
+
}
440
+
441
+
type ProfileOverviewParams struct {
426
442
LoggedInUser *oauth.User
427
443
Repos []db.Repo
428
444
CollaboratingRepos []db.Repo
429
445
ProfileTimeline *db.ProfileTimeline
430
-
Card ProfileCard
431
-
Punchcard db.Punchcard
446
+
Card *ProfileCard
447
+
Active string
432
448
}
433
449
434
-
type ProfileCard struct {
435
-
UserDid string
436
-
UserHandle string
437
-
FollowStatus db.FollowStatus
438
-
FollowersCount int
439
-
FollowingCount int
450
+
func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error {
451
+
params.Active = "overview"
452
+
return p.executeProfile("user/overview", w, params)
453
+
}
440
454
441
-
Profile *db.Profile
455
+
type ProfileReposParams struct {
456
+
LoggedInUser *oauth.User
457
+
Repos []db.Repo
458
+
Card *ProfileCard
459
+
Active string
442
460
}
443
461
444
-
func (p *Pages) ProfileHomePage(w io.Writer, params ProfileHomePageParams) error {
445
-
return p.execute("user/profile", w, params)
462
+
func (p *Pages) ProfileRepos(w io.Writer, params ProfileReposParams) error {
463
+
params.Active = "repos"
464
+
return p.executeProfile("user/repos", w, params)
446
465
}
447
466
448
-
type ReposPageParams struct {
467
+
type ProfileStarredParams struct {
449
468
LoggedInUser *oauth.User
450
469
Repos []db.Repo
451
-
Card ProfileCard
470
+
Card *ProfileCard
471
+
Active string
472
+
}
473
+
474
+
func (p *Pages) ProfileStarred(w io.Writer, params ProfileStarredParams) error {
475
+
params.Active = "starred"
476
+
return p.executeProfile("user/starred", w, params)
477
+
}
478
+
479
+
type ProfileStringsParams struct {
480
+
LoggedInUser *oauth.User
481
+
Strings []db.String
482
+
Card *ProfileCard
483
+
Active string
452
484
}
453
485
454
-
func (p *Pages) ReposPage(w io.Writer, params ReposPageParams) error {
455
-
return p.execute("user/repos", w, params)
486
+
func (p *Pages) ProfileStrings(w io.Writer, params ProfileStringsParams) error {
487
+
params.Active = "strings"
488
+
return p.executeProfile("user/strings", w, params)
456
489
}
457
490
458
491
type FollowCard struct {
459
492
UserDid string
460
493
FollowStatus db.FollowStatus
461
-
FollowersCount int
462
-
FollowingCount int
494
+
FollowersCount int64
495
+
FollowingCount int64
463
496
Profile *db.Profile
464
497
}
465
498
466
-
type FollowersPageParams struct {
499
+
type ProfileFollowersParams struct {
467
500
LoggedInUser *oauth.User
468
501
Followers []FollowCard
469
-
Card ProfileCard
502
+
Card *ProfileCard
503
+
Active string
470
504
}
471
505
472
-
func (p *Pages) FollowersPage(w io.Writer, params FollowersPageParams) error {
473
-
return p.execute("user/followers", w, params)
506
+
func (p *Pages) ProfileFollowers(w io.Writer, params ProfileFollowersParams) error {
507
+
params.Active = "overview"
508
+
return p.executeProfile("user/followers", w, params)
474
509
}
475
510
476
-
type FollowingPageParams struct {
511
+
type ProfileFollowingParams struct {
477
512
LoggedInUser *oauth.User
478
513
Following []FollowCard
479
-
Card ProfileCard
514
+
Card *ProfileCard
515
+
Active string
480
516
}
481
517
482
-
func (p *Pages) FollowingPage(w io.Writer, params FollowingPageParams) error {
483
-
return p.execute("user/following", w, params)
518
+
func (p *Pages) ProfileFollowing(w io.Writer, params ProfileFollowingParams) error {
519
+
params.Active = "overview"
520
+
return p.executeProfile("user/following", w, params)
484
521
}
485
522
486
523
type FollowFragmentParams struct {
···
649
686
650
687
func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error {
651
688
params.Active = "overview"
652
-
return p.execute("repo/tree", w, params)
689
+
return p.executeRepo("repo/tree", w, params)
653
690
}
654
691
655
692
type RepoBranchesParams struct {
···
863
900
} else {
864
901
params.State = "closed"
865
902
}
866
-
return p.execute("repo/issues/issue", w, params)
903
+
return p.executeRepo("repo/issues/issue", w, params)
867
904
}
868
905
869
906
type RepoNewIssueParams struct {
···
1269
1306
1270
1307
sub, err := fs.Sub(Files, "static")
1271
1308
if err != nil {
1272
-
log.Fatalf("no static dir found? that's crazy: %v", err)
1309
+
p.logger.Error("no static dir found? that's crazy", "err", err)
1310
+
panic(err)
1273
1311
}
1274
1312
// Custom handler to apply Cache-Control headers for font files
1275
1313
return Cache(http.StripPrefix("/static/", http.FileServer(http.FS(sub))))
···
1292
1330
func CssContentHash() string {
1293
1331
cssFile, err := Files.Open("static/tw.css")
1294
1332
if err != nil {
1295
-
log.Printf("Error opening CSS file: %v", err)
1333
+
slog.Debug("Error opening CSS file", "err", err)
1296
1334
return ""
1297
1335
}
1298
1336
defer cssFile.Close()
1299
1337
1300
1338
hasher := sha256.New()
1301
1339
if _, err := io.Copy(hasher, cssFile); err != nil {
1302
-
log.Printf("Error hashing CSS file: %v", err)
1340
+
slog.Debug("Error hashing CSS file", "err", err)
1303
1341
return ""
1304
1342
}
1305
1343
+2
-7
appview/pages/repoinfo/repoinfo.go
+2
-7
appview/pages/repoinfo/repoinfo.go
···
78
78
func (r RepoInfo) TabMetadata() map[string]any {
79
79
meta := make(map[string]any)
80
80
81
-
if r.Stats.PullCount.Open > 0 {
82
-
meta["pulls"] = r.Stats.PullCount.Open
83
-
}
84
-
85
-
if r.Stats.IssueCount.Open > 0 {
86
-
meta["issues"] = r.Stats.IssueCount.Open
87
-
}
81
+
meta["pulls"] = r.Stats.PullCount.Open
82
+
meta["issues"] = r.Stats.IssueCount.Open
88
83
89
84
// more stuff?
90
85
+2
-2
appview/pages/templates/layouts/base.html
+2
-2
appview/pages/templates/layouts/base.html
···
17
17
<body class="min-h-screen grid grid-cols-1 grid-rows-[min-content_auto_min-content] md:grid-cols-12 gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white transition-colors duration-200">
18
18
{{ block "topbarLayout" . }}
19
19
<header class="px-1 col-span-1 md:col-start-3 md:col-span-8" style="z-index: 20;">
20
-
{{ template "layouts/topbar" . }}
20
+
{{ template "layouts/fragments/topbar" . }}
21
21
</header>
22
22
{{ end }}
23
23
···
39
39
40
40
{{ block "footerLayout" . }}
41
41
<footer class="px-1 col-span-1 md:col-start-3 md:col-span-8 mt-12">
42
-
{{ template "layouts/footer" . }}
42
+
{{ template "layouts/fragments/footer" . }}
43
43
</footer>
44
44
{{ end }}
45
45
</body>
+87
appview/pages/templates/layouts/fragments/topbar.html
+87
appview/pages/templates/layouts/fragments/topbar.html
···
1
+
{{ define "layouts/fragments/topbar" }}
2
+
<nav class="space-x-4 px-6 py-2 rounded bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">
3
+
<div class="flex justify-between p-0 items-center">
4
+
<div id="left-items">
5
+
<a href="/" hx-boost="true" class="flex gap-2 font-bold italic">
6
+
tangled<sub>alpha</sub>
7
+
</a>
8
+
</div>
9
+
10
+
<div id="right-items" class="flex items-center gap-2">
11
+
{{ with .LoggedInUser }}
12
+
{{ block "newButton" . }} {{ end }}
13
+
{{ block "dropDown" . }} {{ end }}
14
+
{{ else }}
15
+
<a href="/login">login</a>
16
+
<span class="text-gray-500 dark:text-gray-400">or</span>
17
+
<a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2">
18
+
join now {{ i "arrow-right" "size-4" }}
19
+
</a>
20
+
{{ end }}
21
+
</div>
22
+
</div>
23
+
</nav>
24
+
{{ if .LoggedInUser }}
25
+
<div id="upgrade-banner"
26
+
hx-get="/knots/upgradeBanner"
27
+
hx-trigger="load"
28
+
hx-swap="innerHTML">
29
+
</div>
30
+
{{ end }}
31
+
{{ end }}
32
+
33
+
{{ define "newButton" }}
34
+
<details class="relative inline-block text-left nav-dropdown">
35
+
<summary class="btn-create py-0 cursor-pointer list-none flex items-center gap-2">
36
+
{{ i "plus" "w-4 h-4" }} new
37
+
</summary>
38
+
<div class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700">
39
+
<a href="/repo/new" class="flex items-center gap-2">
40
+
{{ i "book-plus" "w-4 h-4" }}
41
+
new repository
42
+
</a>
43
+
<a href="/strings/new" class="flex items-center gap-2">
44
+
{{ i "line-squiggle" "w-4 h-4" }}
45
+
new string
46
+
</a>
47
+
</div>
48
+
</details>
49
+
{{ end }}
50
+
51
+
{{ define "dropDown" }}
52
+
<details class="relative inline-block text-left nav-dropdown">
53
+
<summary
54
+
class="cursor-pointer list-none flex items-center"
55
+
>
56
+
{{ $user := didOrHandle .Did .Handle }}
57
+
{{ template "user/fragments/picHandle" $user }}
58
+
</summary>
59
+
<div
60
+
class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700"
61
+
>
62
+
<a href="/{{ $user }}">profile</a>
63
+
<a href="/{{ $user }}?tab=repos">repositories</a>
64
+
<a href="/{{ $user }}?tab=strings">strings</a>
65
+
<a href="/knots">knots</a>
66
+
<a href="/spindles">spindles</a>
67
+
<a href="/settings">settings</a>
68
+
<a href="#"
69
+
hx-post="/logout"
70
+
hx-swap="none"
71
+
class="text-red-400 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">
72
+
logout
73
+
</a>
74
+
</div>
75
+
</details>
76
+
77
+
<script>
78
+
document.addEventListener('click', function(event) {
79
+
const dropdowns = document.querySelectorAll('.nav-dropdown');
80
+
dropdowns.forEach(function(dropdown) {
81
+
if (!dropdown.contains(event.target)) {
82
+
dropdown.removeAttribute('open');
83
+
}
84
+
});
85
+
});
86
+
</script>
87
+
{{ end }}
+104
appview/pages/templates/layouts/profilebase.html
+104
appview/pages/templates/layouts/profilebase.html
···
1
+
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
2
+
3
+
{{ define "extrameta" }}
4
+
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}" />
5
+
<meta property="og:type" content="profile" />
6
+
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab={{ .Active }}" />
7
+
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
+
{{ end }}
9
+
10
+
{{ define "content" }}
11
+
{{ template "profileTabs" . }}
12
+
<section class="bg-white dark:bg-gray-800 p-6 rounded w-full dark:text-white drop-shadow-sm">
13
+
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
14
+
<div class="md:col-span-3 order-1 md:order-1">
15
+
<div class="flex flex-col gap-4">
16
+
{{ template "user/fragments/profileCard" .Card }}
17
+
{{ block "punchcard" .Card.Punchcard }} {{ end }}
18
+
</div>
19
+
</div>
20
+
{{ block "profileContent" . }} {{ end }}
21
+
</div>
22
+
</section>
23
+
{{ end }}
24
+
25
+
{{ define "profileTabs" }}
26
+
<nav class="w-full pl-4 overflow-x-auto overflow-y-hidden">
27
+
<div class="flex z-60">
28
+
{{ $activeTabStyles := "-mb-px bg-white dark:bg-gray-800" }}
29
+
{{ $tabs := .Card.GetTabs }}
30
+
{{ $tabmeta := dict "x" "y" }}
31
+
{{ range $item := $tabs }}
32
+
{{ $key := index $item 0 }}
33
+
{{ $value := index $item 1 }}
34
+
{{ $icon := index $item 2 }}
35
+
{{ $meta := index $item 3 }}
36
+
<a
37
+
href="?tab={{ $value }}"
38
+
class="relative -mr-px group no-underline hover:no-underline"
39
+
hx-boost="true">
40
+
<div
41
+
class="px-4 py-1 mr-1 text-black dark:text-white min-w-[80px] text-center relative rounded-t whitespace-nowrap
42
+
{{ if eq $.Active $key }}
43
+
{{ $activeTabStyles }}
44
+
{{ else }}
45
+
group-hover:bg-gray-100/25 group-hover:dark:bg-gray-700/25
46
+
{{ end }}
47
+
">
48
+
<span class="flex items-center justify-center">
49
+
{{ i $icon "w-4 h-4 mr-2" }}
50
+
{{ $key }}
51
+
{{ if $meta }}
52
+
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 text-sm ml-1">{{ $meta }}</span>
53
+
{{ end }}
54
+
</span>
55
+
</div>
56
+
</a>
57
+
{{ end }}
58
+
</div>
59
+
</nav>
60
+
{{ end }}
61
+
62
+
{{ define "punchcard" }}
63
+
{{ $now := now }}
64
+
<div>
65
+
<p class="px-2 pb-4 flex gap-2 text-sm font-bold dark:text-white">
66
+
PUNCHCARD
67
+
<span class="font-mono font-normal text-sm text-gray-500 dark:text-gray-400 ">
68
+
{{ .Total | int64 | commaFmt }} commits
69
+
</span>
70
+
</p>
71
+
<div class="grid grid-cols-28 md:grid-cols-14 gap-y-3 w-full h-full">
72
+
{{ range .Punches }}
73
+
{{ $count := .Count }}
74
+
{{ $theme := "bg-gray-200 dark:bg-gray-700 size-[4px]" }}
75
+
{{ if lt $count 1 }}
76
+
{{ $theme = "bg-gray-200 dark:bg-gray-700 size-[4px]" }}
77
+
{{ else if lt $count 2 }}
78
+
{{ $theme = "bg-green-200 dark:bg-green-900 size-[5px]" }}
79
+
{{ else if lt $count 4 }}
80
+
{{ $theme = "bg-green-300 dark:bg-green-800 size-[5px]" }}
81
+
{{ else if lt $count 8 }}
82
+
{{ $theme = "bg-green-400 dark:bg-green-700 size-[6px]" }}
83
+
{{ else }}
84
+
{{ $theme = "bg-green-500 dark:bg-green-600 size-[7px]" }}
85
+
{{ end }}
86
+
87
+
{{ if .Date.After $now }}
88
+
{{ $theme = "border border-gray-200 dark:border-gray-700 size-[4px]" }}
89
+
{{ end }}
90
+
<div class="w-full h-full flex justify-center items-center">
91
+
<div
92
+
class="aspect-square rounded-full transition-all duration-300 {{ $theme }} max-w-full max-h-full"
93
+
title="{{ .Date.Format "2006-01-02" }}: {{ .Count }} commits">
94
+
</div>
95
+
</div>
96
+
{{ end }}
97
+
</div>
98
+
</div>
99
+
{{ end }}
100
+
101
+
{{ define "layouts/profilebase" }}
102
+
{{ template "layouts/base" . }}
103
+
{{ end }}
104
+
+2
-6
appview/pages/templates/layouts/repobase.html
+2
-6
appview/pages/templates/layouts/repobase.html
···
71
71
<span class="flex items-center justify-center">
72
72
{{ i $icon "w-4 h-4 mr-2" }}
73
73
{{ $key }}
74
-
{{ if not (isNil $meta) }}
75
-
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 text-sm ml-1">{{ $meta }}</span>
74
+
{{ if $meta }}
75
+
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 text-sm ml-1">{{ $meta }}</span>
76
76
{{ end }}
77
77
</span>
78
78
</div>
···
88
88
{{ block "repoAfter" . }}{{ end }}
89
89
</section>
90
90
{{ end }}
91
-
92
-
{{ define "layouts/repobase" }}
93
-
{{ template "layouts/base" . }}
94
-
{{ end }}
-87
appview/pages/templates/layouts/topbar.html
-87
appview/pages/templates/layouts/topbar.html
···
1
-
{{ define "layouts/topbar" }}
2
-
<nav class="space-x-4 px-6 py-2 rounded bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">
3
-
<div class="flex justify-between p-0 items-center">
4
-
<div id="left-items">
5
-
<a href="/" hx-boost="true" class="flex gap-2 font-bold italic">
6
-
tangled<sub>alpha</sub>
7
-
</a>
8
-
</div>
9
-
10
-
<div id="right-items" class="flex items-center gap-2">
11
-
{{ with .LoggedInUser }}
12
-
{{ block "newButton" . }} {{ end }}
13
-
{{ block "dropDown" . }} {{ end }}
14
-
{{ else }}
15
-
<a href="/login">login</a>
16
-
<span class="text-gray-500 dark:text-gray-400">or</span>
17
-
<a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2">
18
-
join now {{ i "arrow-right" "size-4" }}
19
-
</a>
20
-
{{ end }}
21
-
</div>
22
-
</div>
23
-
</nav>
24
-
{{ if .LoggedInUser }}
25
-
<div id="upgrade-banner"
26
-
hx-get="/knots/upgradeBanner"
27
-
hx-trigger="load"
28
-
hx-swap="innerHTML">
29
-
</div>
30
-
{{ end }}
31
-
{{ end }}
32
-
33
-
{{ define "newButton" }}
34
-
<details class="relative inline-block text-left nav-dropdown">
35
-
<summary class="btn-create py-0 cursor-pointer list-none flex items-center gap-2">
36
-
{{ i "plus" "w-4 h-4" }} new
37
-
</summary>
38
-
<div class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700">
39
-
<a href="/repo/new" class="flex items-center gap-2">
40
-
{{ i "book-plus" "w-4 h-4" }}
41
-
new repository
42
-
</a>
43
-
<a href="/strings/new" class="flex items-center gap-2">
44
-
{{ i "line-squiggle" "w-4 h-4" }}
45
-
new string
46
-
</a>
47
-
</div>
48
-
</details>
49
-
{{ end }}
50
-
51
-
{{ define "dropDown" }}
52
-
<details class="relative inline-block text-left nav-dropdown">
53
-
<summary
54
-
class="cursor-pointer list-none flex items-center"
55
-
>
56
-
{{ $user := didOrHandle .Did .Handle }}
57
-
{{ template "user/fragments/picHandle" $user }}
58
-
</summary>
59
-
<div
60
-
class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700"
61
-
>
62
-
<a href="/{{ $user }}">profile</a>
63
-
<a href="/{{ $user }}?tab=repos">repositories</a>
64
-
<a href="/strings/{{ $user }}">strings</a>
65
-
<a href="/knots">knots</a>
66
-
<a href="/spindles">spindles</a>
67
-
<a href="/settings">settings</a>
68
-
<a href="#"
69
-
hx-post="/logout"
70
-
hx-swap="none"
71
-
class="text-red-400 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">
72
-
logout
73
-
</a>
74
-
</div>
75
-
</details>
76
-
77
-
<script>
78
-
document.addEventListener('click', function(event) {
79
-
const dropdowns = document.querySelectorAll('.nav-dropdown');
80
-
dropdowns.forEach(function(dropdown) {
81
-
if (!dropdown.contains(event.target)) {
82
-
dropdown.removeAttribute('open');
83
-
}
84
-
});
85
-
});
86
-
</script>
87
-
{{ end }}
+2
-2
appview/pages/templates/repo/commit.html
+2
-2
appview/pages/templates/repo/commit.html
···
81
81
82
82
{{ define "topbarLayout" }}
83
83
<header class="px-1 col-span-full" style="z-index: 20;">
84
-
{{ template "layouts/topbar" . }}
84
+
{{ template "layouts/fragments/topbar" . }}
85
85
</header>
86
86
{{ end }}
87
87
···
106
106
107
107
{{ define "footerLayout" }}
108
108
<footer class="px-1 col-span-full mt-12">
109
-
{{ template "layouts/footer" . }}
109
+
{{ template "layouts/fragments/footer" . }}
110
110
</footer>
111
111
{{ end }}
112
112
+2
-2
appview/pages/templates/repo/compare/compare.html
+2
-2
appview/pages/templates/repo/compare/compare.html
···
12
12
13
13
{{ define "topbarLayout" }}
14
14
<header class="px-1 col-span-full" style="z-index: 20;">
15
-
{{ template "layouts/topbar" . }}
15
+
{{ template "layouts/fragments/topbar" . }}
16
16
</header>
17
17
{{ end }}
18
18
···
37
37
38
38
{{ define "footerLayout" }}
39
39
<footer class="px-1 col-span-full mt-12">
40
-
{{ template "layouts/footer" . }}
40
+
{{ template "layouts/fragments/footer" . }}
41
41
</footer>
42
42
{{ end }}
43
43
+29
-83
appview/pages/templates/repo/fragments/diff.html
+29
-83
appview/pages/templates/repo/fragments/diff.html
···
13
13
<div class="flex flex-col gap-4">
14
14
{{ range $idx, $hunk := $diff }}
15
15
{{ with $hunk }}
16
-
<section class="border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">
17
-
<div id="file-{{ .Name.New }}">
18
-
<div id="diff-file">
19
-
<details open>
20
-
<summary class="list-none cursor-pointer sticky top-0">
21
-
<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
22
-
<div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
23
-
<div class="flex gap-1 items-center">
24
-
{{ $markerstyle := "diff-type p-1 mr-1 font-mono text-sm rounded select-none" }}
25
-
{{ if .IsNew }}
26
-
<span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">ADDED</span>
27
-
{{ else if .IsDelete }}
28
-
<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">DELETED</span>
29
-
{{ else if .IsCopy }}
30
-
<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">COPIED</span>
31
-
{{ else if .IsRename }}
32
-
<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">RENAMED</span>
33
-
{{ else }}
34
-
<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">MODIFIED</span>
35
-
{{ end }}
36
-
37
-
{{ template "repo/fragments/diffStatPill" .Stats }}
38
-
</div>
39
-
40
-
<div class="flex gap-2 items-center overflow-x-auto">
41
-
{{ if .IsDelete }}
42
-
<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $this }}href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.Old }}"{{end}}>
43
-
{{ .Name.Old }}
44
-
</a>
45
-
{{ else if (or .IsCopy .IsRename) }}
46
-
<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $parent}}href="/{{ $repo }}/blob/{{ $parent }}/{{ .Name.Old }}"{{end}}>
47
-
{{ .Name.Old }}
48
-
</a>
49
-
{{ i "arrow-right" "w-4 h-4" }}
50
-
<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $this}}href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}"{{end}}>
51
-
{{ .Name.New }}
52
-
</a>
53
-
{{ else }}
54
-
<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $this}}href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}"{{end}}>
55
-
{{ .Name.New }}
56
-
</a>
57
-
{{ end }}
58
-
</div>
59
-
</div>
60
-
61
-
{{ $iconstyle := "p-1 mx-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" }}
62
-
<div id="right-side-items" class="p-2 flex items-center">
63
-
<a title="top of file" href="#file-{{ .Name.New }}" class="{{ $iconstyle }}">{{ i "arrow-up-to-line" "w-4 h-4" }}</a>
64
-
{{ if gt $idx 0 }}
65
-
{{ $prev := index $diff (sub $idx 1) }}
66
-
<a title="previous file" href="#file-{{ $prev.Name.New }}" class="{{ $iconstyle }}">{{ i "arrow-up" "w-4 h-4" }}</a>
67
-
{{ end }}
68
-
69
-
{{ if lt $idx $last }}
70
-
{{ $next := index $diff (add $idx 1) }}
71
-
<a title="next file" href="#file-{{ $next.Name.New }}" class="{{ $iconstyle }}">{{ i "arrow-down" "w-4 h-4" }}</a>
72
-
{{ end }}
73
-
</div>
74
-
75
-
</div>
76
-
</summary>
16
+
<details open id="file-{{ .Name.New }}" class="group border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm" tabindex="{{ add $idx 1 }}">
17
+
<summary class="list-none cursor-pointer sticky top-0">
18
+
<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
19
+
<div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
20
+
<span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span>
21
+
<span class="hidden group-open:inline">{{ i "chevron-down" "w-4 h-4" }}</span>
22
+
{{ template "repo/fragments/diffStatPill" .Stats }}
77
23
78
-
<div class="transition-all duration-700 ease-in-out">
79
-
{{ if .IsDelete }}
80
-
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
81
-
This file has been deleted.
82
-
</p>
83
-
{{ else if .IsCopy }}
84
-
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
85
-
This file has been copied.
86
-
</p>
87
-
{{ else if .IsBinary }}
88
-
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
89
-
This is a binary file and will not be displayed.
90
-
</p>
91
-
{{ else }}
92
-
{{ if $isSplit }}
93
-
{{- template "repo/fragments/splitDiff" .Split -}}
24
+
<div class="flex gap-2 items-center overflow-x-auto">
25
+
{{ if .IsDelete }}
26
+
{{ .Name.Old }}
27
+
{{ else if (or .IsCopy .IsRename) }}
28
+
{{ .Name.Old }} {{ i "arrow-right" "w-4 h-4" }} {{ .Name.New }}
94
29
{{ else }}
95
-
{{- template "repo/fragments/unifiedDiff" . -}}
30
+
{{ .Name.New }}
96
31
{{ end }}
97
-
{{- end -}}
32
+
</div>
98
33
</div>
34
+
</div>
35
+
</summary>
99
36
100
-
</details>
101
-
37
+
<div class="transition-all duration-700 ease-in-out">
38
+
{{ if .IsBinary }}
39
+
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
40
+
This is a binary file and will not be displayed.
41
+
</p>
42
+
{{ else }}
43
+
{{ if $isSplit }}
44
+
{{- template "repo/fragments/splitDiff" .Split -}}
45
+
{{ else }}
46
+
{{- template "repo/fragments/unifiedDiff" . -}}
47
+
{{ end }}
48
+
{{- end -}}
102
49
</div>
103
-
</div>
104
-
</section>
50
+
</details>
105
51
{{ end }}
106
52
{{ end }}
107
53
</div>
+4
appview/pages/templates/repo/fragments/duration.html
+4
appview/pages/templates/repo/fragments/duration.html
+44
-69
appview/pages/templates/repo/fragments/interdiff.html
+44
-69
appview/pages/templates/repo/fragments/interdiff.html
···
10
10
<div class="flex flex-col gap-4">
11
11
{{ range $idx, $hunk := $diff }}
12
12
{{ with $hunk }}
13
-
<section class="border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">
14
-
<div id="file-{{ .Name }}">
15
-
<div id="diff-file">
16
-
<details {{ if not (.Status.IsOnlyInOne) }}open{{end}}>
17
-
<summary class="list-none cursor-pointer sticky top-0">
18
-
<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
19
-
<div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
20
-
<div class="flex gap-1 items-center" style="direction: ltr;">
21
-
{{ $markerstyle := "diff-type p-1 mr-1 font-mono text-sm rounded select-none" }}
22
-
{{ if .Status.IsOk }}
23
-
<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">CHANGED</span>
24
-
{{ else if .Status.IsUnchanged }}
25
-
<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">UNCHANGED</span>
26
-
{{ else if .Status.IsOnlyInOne }}
27
-
<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">REVERTED</span>
28
-
{{ else if .Status.IsOnlyInTwo }}
29
-
<span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">NEW</span>
30
-
{{ else if .Status.IsRebased }}
31
-
<span class="bg-amber-100 text-amber-700 dark:bg-amber-800/50 dark:text-amber-400 {{ $markerstyle }}">REBASED</span>
32
-
{{ else }}
33
-
<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">ERROR</span>
34
-
{{ end }}
35
-
</div>
36
-
37
-
<div class="flex gap-2 items-center overflow-x-auto" style="direction: rtl;">
38
-
<a class="dark:text-white whitespace-nowrap overflow-x-auto" href="">
39
-
{{ .Name }}
40
-
</a>
41
-
</div>
42
-
</div>
43
-
44
-
{{ $iconstyle := "p-1 mx-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" }}
45
-
<div id="right-side-items" class="p-2 flex items-center">
46
-
<a title="top of file" href="#file-{{ .Name }}" class="{{ $iconstyle }}">{{ i "arrow-up-to-line" "w-4 h-4" }}</a>
47
-
{{ if gt $idx 0 }}
48
-
{{ $prev := index $diff (sub $idx 1) }}
49
-
<a title="previous file" href="#file-{{ $prev.Name }}" class="{{ $iconstyle }}">{{ i "arrow-up" "w-4 h-4" }}</a>
50
-
{{ end }}
51
-
52
-
{{ if lt $idx $last }}
53
-
{{ $next := index $diff (add $idx 1) }}
54
-
<a title="next file" href="#file-{{ $next.Name }}" class="{{ $iconstyle }}">{{ i "arrow-down" "w-4 h-4" }}</a>
55
-
{{ end }}
56
-
</div>
57
-
13
+
<details {{ if not (.Status.IsOnlyInOne) }}open{{end}} id="file-{{ .Name }}" class="border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">
14
+
<summary class="list-none cursor-pointer sticky top-0">
15
+
<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
16
+
<div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
17
+
<div class="flex gap-1 items-center" style="direction: ltr;">
18
+
{{ $markerstyle := "diff-type p-1 mr-1 font-mono text-sm rounded select-none" }}
19
+
{{ if .Status.IsOk }}
20
+
<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">CHANGED</span>
21
+
{{ else if .Status.IsUnchanged }}
22
+
<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">UNCHANGED</span>
23
+
{{ else if .Status.IsOnlyInOne }}
24
+
<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">REVERTED</span>
25
+
{{ else if .Status.IsOnlyInTwo }}
26
+
<span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">NEW</span>
27
+
{{ else if .Status.IsRebased }}
28
+
<span class="bg-amber-100 text-amber-700 dark:bg-amber-800/50 dark:text-amber-400 {{ $markerstyle }}">REBASED</span>
29
+
{{ else }}
30
+
<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">ERROR</span>
31
+
{{ end }}
58
32
</div>
59
-
</summary>
60
33
61
-
<div class="transition-all duration-700 ease-in-out">
62
-
{{ if .Status.IsUnchanged }}
63
-
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
64
-
This file has not been changed.
65
-
</p>
66
-
{{ else if .Status.IsRebased }}
67
-
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
68
-
This patch was likely rebased, as context lines do not match.
69
-
</p>
70
-
{{ else if .Status.IsError }}
71
-
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
72
-
Failed to calculate interdiff for this file.
73
-
</p>
74
-
{{ else }}
75
-
{{ if $isSplit }}
76
-
{{- template "repo/fragments/splitDiff" .Split -}}
77
-
{{ else }}
78
-
{{- template "repo/fragments/unifiedDiff" . -}}
79
-
{{ end }}
80
-
{{- end -}}
34
+
<div class="flex gap-2 items-center overflow-x-auto" style="direction: rtl;">{{ .Name }}</div>
81
35
</div>
82
36
83
-
</details>
37
+
</div>
38
+
</summary>
84
39
40
+
<div class="transition-all duration-700 ease-in-out">
41
+
{{ if .Status.IsUnchanged }}
42
+
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
43
+
This file has not been changed.
44
+
</p>
45
+
{{ else if .Status.IsRebased }}
46
+
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
47
+
This patch was likely rebased, as context lines do not match.
48
+
</p>
49
+
{{ else if .Status.IsError }}
50
+
<p class="text-center text-gray-400 dark:text-gray-500 p-4">
51
+
Failed to calculate interdiff for this file.
52
+
</p>
53
+
{{ else }}
54
+
{{ if $isSplit }}
55
+
{{- template "repo/fragments/splitDiff" .Split -}}
56
+
{{ else }}
57
+
{{- template "repo/fragments/unifiedDiff" . -}}
58
+
{{ end }}
59
+
{{- end -}}
85
60
</div>
86
-
</div>
87
-
</section>
61
+
62
+
</details>
88
63
{{ end }}
89
64
{{ end }}
90
65
</div>
+4
appview/pages/templates/repo/fragments/shortTime.html
+4
appview/pages/templates/repo/fragments/shortTime.html
+4
appview/pages/templates/repo/fragments/shortTimeAgo.html
+4
appview/pages/templates/repo/fragments/shortTimeAgo.html
-16
appview/pages/templates/repo/fragments/time.html
-16
appview/pages/templates/repo/fragments/time.html
···
1
-
{{ define "repo/fragments/timeWrapper" }}
2
-
<time datetime="{{ .Time | iso8601DateTimeFmt }}" title="{{ .Time | longTimeFmt }}">{{ .Content }}</time>
3
-
{{ end }}
4
-
5
1
{{ define "repo/fragments/time" }}
6
2
{{ template "repo/fragments/timeWrapper" (dict "Time" . "Content" (. | relTimeFmt)) }}
7
3
{{ end }}
8
-
9
-
{{ define "repo/fragments/shortTime" }}
10
-
{{ template "repo/fragments/timeWrapper" (dict "Time" . "Content" (. | shortRelTimeFmt)) }}
11
-
{{ end }}
12
-
13
-
{{ define "repo/fragments/shortTimeAgo" }}
14
-
{{ template "repo/fragments/timeWrapper" (dict "Time" . "Content" (print (. | shortRelTimeFmt) " ago")) }}
15
-
{{ end }}
16
-
17
-
{{ define "repo/fragments/duration" }}
18
-
<time datetime="{{ . | iso8601DurationFmt }}" title="{{ . | longDurationFmt }}">{{ . | durationFmt }}</time>
19
-
{{ end }}
+5
appview/pages/templates/repo/fragments/timeWrapper.html
+5
appview/pages/templates/repo/fragments/timeWrapper.html
+2
-2
appview/pages/templates/repo/pulls/fragments/pullCompareForks.html
+2
-2
appview/pages/templates/repo/pulls/fragments/pullCompareForks.html
···
19
19
>
20
20
<option disabled selected>select a fork</option>
21
21
{{ range .Forks }}
22
-
<option value="{{ .Name }}" {{ if eq .Name $.Selected }}selected{{ end }} class="py-1">
23
-
{{ .Name }}
22
+
<option value="{{ .Did }}/{{ .Name }}" {{ if eq .Name $.Selected }}selected{{ end }} class="py-1">
23
+
{{ .Did | resolve }}/{{ .Name }}
24
24
</option>
25
25
{{ end }}
26
26
</select>
+1
-1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
+1
-1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
···
17
17
{{ $icon = "git-merge" }}
18
18
{{ end }}
19
19
20
-
{{ $owner := resolve .Pull.OwnerDid }}
21
20
<section class="mt-2">
22
21
<div class="flex items-center gap-2">
23
22
<div
···
45
44
<span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">
46
45
{{ if .Pull.IsForkBased }}
47
46
{{ if .Pull.PullSource.Repo }}
47
+
{{ $owner := resolve .Pull.PullSource.Repo.Did }}
48
48
<a href="/{{ $owner }}/{{ .Pull.PullSource.Repo.Name }}" class="no-underline hover:underline">{{ $owner }}/{{ .Pull.PullSource.Repo.Name }}</a>:
49
49
{{- else -}}
50
50
<span class="italic">[deleted fork]</span>
+1
-1
appview/pages/templates/repo/pulls/fragments/pullStack.html
+1
-1
appview/pages/templates/repo/pulls/fragments/pullStack.html
···
52
52
</div>
53
53
{{ end }}
54
54
<div class="{{ if not $isCurrent }} pl-6 {{ end }} flex-grow min-w-0 w-full py-2">
55
-
{{ template "repo/pulls/fragments/summarizedHeader" (list $pull $pipeline) }}
55
+
{{ template "repo/pulls/fragments/summarizedPullHeader" (list $pull $pipeline) }}
56
56
</div>
57
57
</div>
58
58
</a>
+1
-1
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
+1
-1
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
+2
-2
appview/pages/templates/repo/pulls/interdiff.html
+2
-2
appview/pages/templates/repo/pulls/interdiff.html
···
30
30
31
31
{{ define "topbarLayout" }}
32
32
<header class="px-1 col-span-full" style="z-index: 20;">
33
-
{{ template "layouts/topbar" . }}
33
+
{{ template "layouts/fragments/topbar" . }}
34
34
</header>
35
35
{{ end }}
36
36
···
55
55
56
56
{{ define "footerLayout" }}
57
57
<footer class="px-1 col-span-full mt-12">
58
-
{{ template "layouts/footer" . }}
58
+
{{ template "layouts/fragments/footer" . }}
59
59
</footer>
60
60
{{ end }}
61
61
+2
-2
appview/pages/templates/repo/pulls/patch.html
+2
-2
appview/pages/templates/repo/pulls/patch.html
···
36
36
37
37
{{ define "topbarLayout" }}
38
38
<header class="px-1 col-span-full" style="z-index: 20;">
39
-
{{ template "layouts/topbar" . }}
39
+
{{ template "layouts/fragments/topbar" . }}
40
40
</header>
41
41
{{ end }}
42
42
···
61
61
62
62
{{ define "footerLayout" }}
63
63
<footer class="px-1 col-span-full mt-12">
64
-
{{ template "layouts/footer" . }}
64
+
{{ template "layouts/fragments/footer" . }}
65
65
</footer>
66
66
{{ end }}
67
67
+1
-1
appview/pages/templates/repo/pulls/pulls.html
+1
-1
appview/pages/templates/repo/pulls/pulls.html
···
144
144
<a href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $pull.PullId }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25">
145
145
<div class="flex gap-2 items-center px-6">
146
146
<div class="flex-grow min-w-0 w-full py-2">
147
-
{{ template "repo/pulls/fragments/summarizedHeader" (list $pull $pipeline) }}
147
+
{{ template "repo/pulls/fragments/summarizedPullHeader" (list $pull $pipeline) }}
148
148
</div>
149
149
</div>
150
150
</a>
-4
appview/pages/templates/strings/put.html
-4
appview/pages/templates/strings/put.html
-4
appview/pages/templates/strings/string.html
-4
appview/pages/templates/strings/string.html
···
8
8
<meta property="og:description" content="{{ .String.Description }}" />
9
9
{{ end }}
10
10
11
-
{{ define "topbar" }}
12
-
{{ template "layouts/topbar" $ }}
13
-
{{ end }}
14
-
15
11
{{ define "content" }}
16
12
{{ $ownerId := didOrHandle .Owner.DID.String .Owner.Handle.String }}
17
13
<section id="string-header" class="mb-4 py-2 px-6 dark:text-white">
-4
appview/pages/templates/strings/timeline.html
-4
appview/pages/templates/strings/timeline.html
+4
-16
appview/pages/templates/user/followers.html
+4
-16
appview/pages/templates/user/followers.html
···
1
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} ยท followers {{ end }}
2
2
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}'s followers" />
5
-
<meta property="og:type" content="object" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab=followers" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
{{ template "user/fragments/profileCard" .Card }}
14
-
</div>
15
-
<div id="all-followers" class="md:col-span-8 order-2 md:order-2">
16
-
{{ block "followers" . }}{{ end }}
17
-
</div>
18
-
</div>
3
+
{{ define "profileContent" }}
4
+
<div id="all-followers" class="md:col-span-8 order-2 md:order-2">
5
+
{{ block "followers" . }}{{ end }}
6
+
</div>
19
7
{{ end }}
20
8
21
9
{{ define "followers" }}
+4
-16
appview/pages/templates/user/following.html
+4
-16
appview/pages/templates/user/following.html
···
1
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} ยท following {{ end }}
2
2
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}'s following" />
5
-
<meta property="og:type" content="object" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab=following" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
{{ template "user/fragments/profileCard" .Card }}
14
-
</div>
15
-
<div id="all-following" class="md:col-span-8 order-2 md:order-2">
16
-
{{ block "following" . }}{{ end }}
17
-
</div>
18
-
</div>
3
+
{{ define "profileContent" }}
4
+
<div id="all-following" class="md:col-span-8 order-2 md:order-2">
5
+
{{ block "following" . }}{{ end }}
6
+
</div>
19
7
{{ end }}
20
8
21
9
{{ define "following" }}
+1
-1
appview/pages/templates/user/fragments/editBio.html
+1
-1
appview/pages/templates/user/fragments/editBio.html
+2
-4
appview/pages/templates/user/fragments/profileCard.html
+2
-4
appview/pages/templates/user/fragments/profileCard.html
···
1
1
{{ define "user/fragments/profileCard" }}
2
2
{{ $userIdent := didOrHandle .UserDid .UserHandle }}
3
-
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm max-h-fit">
4
3
<div class="grid grid-cols-3 md:grid-cols-1 gap-1 items-center">
5
4
<div id="avatar" class="col-span-1 flex justify-center items-center">
6
5
<div class="w-3/4 aspect-square relative">
···
85
84
<div id="update-profile" class="text-red-400 dark:text-red-500"></div>
86
85
</div>
87
86
</div>
88
-
</div>
89
87
{{ end }}
90
88
91
89
{{ define "followerFollowing" }}
···
94
92
{{ with $root }}
95
93
<div class="flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm">
96
94
<span class="flex-shrink-0">{{ i "users" "size-4" }}</span>
97
-
<span id="followers"><a href="/{{ $userIdent }}?tab=followers">{{ .FollowersCount }} followers</a></span>
95
+
<span id="followers"><a href="/{{ $userIdent }}?tab=followers">{{ .Stats.FollowersCount }} followers</a></span>
98
96
<span class="select-none after:content-['ยท']"></span>
99
-
<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .FollowingCount }} following</a></span>
97
+
<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .Stats.FollowingCount }} following</a></span>
100
98
</div>
101
99
{{ end }}
102
100
{{ end }}
+258
appview/pages/templates/user/overview.html
+258
appview/pages/templates/user/overview.html
···
1
+
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
2
+
3
+
{{ define "profileContent" }}
4
+
<div id="all-repos" class="md:col-span-4 order-2 md:order-2">
5
+
<div class="grid grid-cols-1 gap-4">
6
+
{{ block "ownRepos" . }}{{ end }}
7
+
{{ block "collaboratingRepos" . }}{{ end }}
8
+
</div>
9
+
</div>
10
+
<div class="md:col-span-4 order-3 md:order-3">
11
+
{{ block "profileTimeline" . }}{{ end }}
12
+
</div>
13
+
{{ end }}
14
+
15
+
{{ define "profileTimeline" }}
16
+
<p class="text-sm font-bold px-2 pb-4 dark:text-white">ACTIVITY</p>
17
+
<div class="flex flex-col gap-4 relative">
18
+
{{ if .ProfileTimeline.IsEmpty }}
19
+
<p class="dark:text-white">This user does not have any activity yet.</p>
20
+
{{ end }}
21
+
22
+
{{ with .ProfileTimeline }}
23
+
{{ range $idx, $byMonth := .ByMonth }}
24
+
{{ with $byMonth }}
25
+
{{ if not .IsEmpty }}
26
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm py-4 px-6">
27
+
<p class="text-sm font-mono mb-2 text-gray-500 dark:text-gray-400">
28
+
{{ if eq $idx 0 }}
29
+
this month
30
+
{{ else }}
31
+
{{$idx}} month{{if ne $idx 1}}s{{end}} ago
32
+
{{ end }}
33
+
</p>
34
+
35
+
<div class="flex flex-col gap-1">
36
+
{{ block "repoEvents" .RepoEvents }} {{ end }}
37
+
{{ block "issueEvents" .IssueEvents }} {{ end }}
38
+
{{ block "pullEvents" .PullEvents }} {{ end }}
39
+
</div>
40
+
</div>
41
+
{{ end }}
42
+
{{ end }}
43
+
{{ end }}
44
+
{{ end }}
45
+
</div>
46
+
{{ end }}
47
+
48
+
{{ define "repoEvents" }}
49
+
{{ if gt (len .) 0 }}
50
+
<details>
51
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
52
+
<div class="flex flex-wrap items-center gap-2">
53
+
{{ i "book-plus" "w-4 h-4" }}
54
+
created {{ len . }} {{if eq (len .) 1 }}repository{{else}}repositories{{end}}
55
+
</div>
56
+
</summary>
57
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
58
+
{{ range . }}
59
+
<div class="flex flex-wrap items-center gap-2">
60
+
<span class="text-gray-500 dark:text-gray-400">
61
+
{{ if .Source }}
62
+
{{ i "git-fork" "w-4 h-4" }}
63
+
{{ else }}
64
+
{{ i "book-plus" "w-4 h-4" }}
65
+
{{ end }}
66
+
</span>
67
+
<a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
68
+
{{- .Repo.Name -}}
69
+
</a>
70
+
</div>
71
+
{{ end }}
72
+
</div>
73
+
</details>
74
+
{{ end }}
75
+
{{ end }}
76
+
77
+
{{ define "issueEvents" }}
78
+
{{ $items := .Items }}
79
+
{{ $stats := .Stats }}
80
+
81
+
{{ if gt (len $items) 0 }}
82
+
<details>
83
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
84
+
<div class="flex flex-wrap items-center gap-2">
85
+
{{ i "circle-dot" "w-4 h-4" }}
86
+
87
+
<div>
88
+
created {{ len $items }} {{if eq (len $items) 1 }}issue{{else}}issues{{end}}
89
+
</div>
90
+
91
+
{{ if gt $stats.Open 0 }}
92
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
93
+
{{$stats.Open}} open
94
+
</span>
95
+
{{ end }}
96
+
97
+
{{ if gt $stats.Closed 0 }}
98
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-gray-800 dark:bg-gray-700">
99
+
{{$stats.Closed}} closed
100
+
</span>
101
+
{{ end }}
102
+
103
+
</div>
104
+
</summary>
105
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
106
+
{{ range $items }}
107
+
{{ $repoOwner := resolve .Metadata.Repo.Did }}
108
+
{{ $repoName := .Metadata.Repo.Name }}
109
+
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
110
+
111
+
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
112
+
{{ if .Open }}
113
+
<span class="text-green-600 dark:text-green-500">
114
+
{{ i "circle-dot" "w-4 h-4" }}
115
+
</span>
116
+
{{ else }}
117
+
<span class="text-gray-500 dark:text-gray-400">
118
+
{{ i "ban" "w-4 h-4" }}
119
+
</span>
120
+
{{ end }}
121
+
<div class="flex-none min-w-8 text-right">
122
+
<span class="text-gray-500 dark:text-gray-400">#{{ .IssueId }}</span>
123
+
</div>
124
+
<div class="break-words max-w-full">
125
+
<a href="/{{$repoUrl}}/issues/{{ .IssueId }}" class="no-underline hover:underline">
126
+
{{ .Title -}}
127
+
</a>
128
+
on
129
+
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
130
+
{{$repoUrl}}
131
+
</a>
132
+
</div>
133
+
</div>
134
+
{{ end }}
135
+
</div>
136
+
</details>
137
+
{{ end }}
138
+
{{ end }}
139
+
140
+
{{ define "pullEvents" }}
141
+
{{ $items := .Items }}
142
+
{{ $stats := .Stats }}
143
+
{{ if gt (len $items) 0 }}
144
+
<details>
145
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
146
+
<div class="flex flex-wrap items-center gap-2">
147
+
{{ i "git-pull-request" "w-4 h-4" }}
148
+
149
+
<div>
150
+
created {{ len $items }} {{if eq (len $items) 1 }}pull request{{else}}pull requests{{end}}
151
+
</div>
152
+
153
+
{{ if gt $stats.Open 0 }}
154
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
155
+
{{$stats.Open}} open
156
+
</span>
157
+
{{ end }}
158
+
159
+
{{ if gt $stats.Merged 0 }}
160
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-purple-600 dark:bg-purple-700">
161
+
{{$stats.Merged}} merged
162
+
</span>
163
+
{{ end }}
164
+
165
+
166
+
{{ if gt $stats.Closed 0 }}
167
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-gray-800 dark:bg-gray-700">
168
+
{{$stats.Closed}} closed
169
+
</span>
170
+
{{ end }}
171
+
172
+
</div>
173
+
</summary>
174
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
175
+
{{ range $items }}
176
+
{{ $repoOwner := resolve .Repo.Did }}
177
+
{{ $repoName := .Repo.Name }}
178
+
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
179
+
180
+
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
181
+
{{ if .State.IsOpen }}
182
+
<span class="text-green-600 dark:text-green-500">
183
+
{{ i "git-pull-request" "w-4 h-4" }}
184
+
</span>
185
+
{{ else if .State.IsMerged }}
186
+
<span class="text-purple-600 dark:text-purple-500">
187
+
{{ i "git-merge" "w-4 h-4" }}
188
+
</span>
189
+
{{ else }}
190
+
<span class="text-gray-600 dark:text-gray-300">
191
+
{{ i "git-pull-request-closed" "w-4 h-4" }}
192
+
</span>
193
+
{{ end }}
194
+
<div class="flex-none min-w-8 text-right">
195
+
<span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span>
196
+
</div>
197
+
<div class="break-words max-w-full">
198
+
<a href="/{{$repoUrl}}/pulls/{{ .PullId }}" class="no-underline hover:underline">
199
+
{{ .Title -}}
200
+
</a>
201
+
on
202
+
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
203
+
{{$repoUrl}}
204
+
</a>
205
+
</div>
206
+
</div>
207
+
{{ end }}
208
+
</div>
209
+
</details>
210
+
{{ end }}
211
+
{{ end }}
212
+
213
+
{{ define "ownRepos" }}
214
+
<div>
215
+
<div class="text-sm font-bold px-2 pb-4 dark:text-white flex items-center gap-2">
216
+
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}?tab=repos"
217
+
class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">
218
+
<span>PINNED REPOS</span>
219
+
</a>
220
+
{{ if and .LoggedInUser (eq .LoggedInUser.Did .Card.UserDid) }}
221
+
<button
222
+
hx-get="profile/edit-pins"
223
+
hx-target="#all-repos"
224
+
class="py-0 font-normal text-sm flex gap-2 items-center group">
225
+
{{ i "pencil" "w-3 h-3" }}
226
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
227
+
</button>
228
+
{{ end }}
229
+
</div>
230
+
<div id="repos" class="grid grid-cols-1 gap-4 items-stretch">
231
+
{{ range .Repos }}
232
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
233
+
{{ template "user/fragments/repoCard" (list $ . false) }}
234
+
</div>
235
+
{{ else }}
236
+
<p class="dark:text-white">This user does not have any pinned repos.</p>
237
+
{{ end }}
238
+
</div>
239
+
</div>
240
+
{{ end }}
241
+
242
+
{{ define "collaboratingRepos" }}
243
+
{{ if gt (len .CollaboratingRepos) 0 }}
244
+
<div>
245
+
<p class="text-sm font-bold px-2 pb-4 dark:text-white">COLLABORATING ON</p>
246
+
<div id="collaborating" class="grid grid-cols-1 gap-4">
247
+
{{ range .CollaboratingRepos }}
248
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
249
+
{{ template "user/fragments/repoCard" (list $ . true) }}
250
+
</div>
251
+
{{ else }}
252
+
<p class="px-6 dark:text-white">This user is not collaborating.</p>
253
+
{{ end }}
254
+
</div>
255
+
</div>
256
+
{{ end }}
257
+
{{ end }}
258
+
-318
appview/pages/templates/user/profile.html
-318
appview/pages/templates/user/profile.html
···
1
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
2
-
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}" />
5
-
<meta property="og:type" content="profile" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
<div class="grid grid-cols-1 gap-4">
14
-
{{ template "user/fragments/profileCard" .Card }}
15
-
{{ block "punchcard" .Punchcard }} {{ end }}
16
-
</div>
17
-
</div>
18
-
<div id="all-repos" class="md:col-span-4 order-2 md:order-2">
19
-
<div class="grid grid-cols-1 gap-4">
20
-
{{ block "ownRepos" . }}{{ end }}
21
-
{{ block "collaboratingRepos" . }}{{ end }}
22
-
</div>
23
-
</div>
24
-
<div class="md:col-span-4 order-3 md:order-3">
25
-
{{ block "profileTimeline" . }}{{ end }}
26
-
</div>
27
-
</div>
28
-
{{ end }}
29
-
30
-
{{ define "profileTimeline" }}
31
-
<p class="text-sm font-bold p-2 dark:text-white">ACTIVITY</p>
32
-
<div class="flex flex-col gap-4 relative">
33
-
{{ with .ProfileTimeline }}
34
-
{{ range $idx, $byMonth := .ByMonth }}
35
-
{{ with $byMonth }}
36
-
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm">
37
-
{{ if eq $idx 0 }}
38
-
39
-
{{ else }}
40
-
{{ $s := "s" }}
41
-
{{ if eq $idx 1 }}
42
-
{{ $s = "" }}
43
-
{{ end }}
44
-
<p class="text-sm font-bold dark:text-white mb-2">{{$idx}} month{{$s}} ago</p>
45
-
{{ end }}
46
-
47
-
{{ if .IsEmpty }}
48
-
<div class="text-gray-500 dark:text-gray-400">
49
-
No activity for this month
50
-
</div>
51
-
{{ else }}
52
-
<div class="flex flex-col gap-1">
53
-
{{ block "repoEvents" .RepoEvents }} {{ end }}
54
-
{{ block "issueEvents" .IssueEvents }} {{ end }}
55
-
{{ block "pullEvents" .PullEvents }} {{ end }}
56
-
</div>
57
-
{{ end }}
58
-
</div>
59
-
60
-
{{ end }}
61
-
{{ else }}
62
-
<p class="dark:text-white">This user does not have any activity yet.</p>
63
-
{{ end }}
64
-
{{ end }}
65
-
</div>
66
-
{{ end }}
67
-
68
-
{{ define "repoEvents" }}
69
-
{{ if gt (len .) 0 }}
70
-
<details>
71
-
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
72
-
<div class="flex flex-wrap items-center gap-2">
73
-
{{ i "book-plus" "w-4 h-4" }}
74
-
created {{ len . }} {{if eq (len .) 1 }}repository{{else}}repositories{{end}}
75
-
</div>
76
-
</summary>
77
-
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
78
-
{{ range . }}
79
-
<div class="flex flex-wrap items-center gap-2">
80
-
<span class="text-gray-500 dark:text-gray-400">
81
-
{{ if .Source }}
82
-
{{ i "git-fork" "w-4 h-4" }}
83
-
{{ else }}
84
-
{{ i "book-plus" "w-4 h-4" }}
85
-
{{ end }}
86
-
</span>
87
-
<a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
88
-
{{- .Repo.Name -}}
89
-
</a>
90
-
</div>
91
-
{{ end }}
92
-
</div>
93
-
</details>
94
-
{{ end }}
95
-
{{ end }}
96
-
97
-
{{ define "issueEvents" }}
98
-
{{ $items := .Items }}
99
-
{{ $stats := .Stats }}
100
-
101
-
{{ if gt (len $items) 0 }}
102
-
<details>
103
-
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
104
-
<div class="flex flex-wrap items-center gap-2">
105
-
{{ i "circle-dot" "w-4 h-4" }}
106
-
107
-
<div>
108
-
created {{ len $items }} {{if eq (len $items) 1 }}issue{{else}}issues{{end}}
109
-
</div>
110
-
111
-
{{ if gt $stats.Open 0 }}
112
-
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
113
-
{{$stats.Open}} open
114
-
</span>
115
-
{{ end }}
116
-
117
-
{{ if gt $stats.Closed 0 }}
118
-
<span class="px-2 py-1/2 text-sm rounded text-white bg-gray-800 dark:bg-gray-700">
119
-
{{$stats.Closed}} closed
120
-
</span>
121
-
{{ end }}
122
-
123
-
</div>
124
-
</summary>
125
-
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
126
-
{{ range $items }}
127
-
{{ $repoOwner := resolve .Metadata.Repo.Did }}
128
-
{{ $repoName := .Metadata.Repo.Name }}
129
-
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
130
-
131
-
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
132
-
{{ if .Open }}
133
-
<span class="text-green-600 dark:text-green-500">
134
-
{{ i "circle-dot" "w-4 h-4" }}
135
-
</span>
136
-
{{ else }}
137
-
<span class="text-gray-500 dark:text-gray-400">
138
-
{{ i "ban" "w-4 h-4" }}
139
-
</span>
140
-
{{ end }}
141
-
<div class="flex-none min-w-8 text-right">
142
-
<span class="text-gray-500 dark:text-gray-400">#{{ .IssueId }}</span>
143
-
</div>
144
-
<div class="break-words max-w-full">
145
-
<a href="/{{$repoUrl}}/issues/{{ .IssueId }}" class="no-underline hover:underline">
146
-
{{ .Title -}}
147
-
</a>
148
-
on
149
-
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
150
-
{{$repoUrl}}
151
-
</a>
152
-
</div>
153
-
</div>
154
-
{{ end }}
155
-
</div>
156
-
</details>
157
-
{{ end }}
158
-
{{ end }}
159
-
160
-
{{ define "pullEvents" }}
161
-
{{ $items := .Items }}
162
-
{{ $stats := .Stats }}
163
-
{{ if gt (len $items) 0 }}
164
-
<details>
165
-
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
166
-
<div class="flex flex-wrap items-center gap-2">
167
-
{{ i "git-pull-request" "w-4 h-4" }}
168
-
169
-
<div>
170
-
created {{ len $items }} {{if eq (len $items) 1 }}pull request{{else}}pull requests{{end}}
171
-
</div>
172
-
173
-
{{ if gt $stats.Open 0 }}
174
-
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
175
-
{{$stats.Open}} open
176
-
</span>
177
-
{{ end }}
178
-
179
-
{{ if gt $stats.Merged 0 }}
180
-
<span class="px-2 py-1/2 text-sm rounded text-white bg-purple-600 dark:bg-purple-700">
181
-
{{$stats.Merged}} merged
182
-
</span>
183
-
{{ end }}
184
-
185
-
186
-
{{ if gt $stats.Closed 0 }}
187
-
<span class="px-2 py-1/2 text-sm rounded text-white bg-gray-800 dark:bg-gray-700">
188
-
{{$stats.Closed}} closed
189
-
</span>
190
-
{{ end }}
191
-
192
-
</div>
193
-
</summary>
194
-
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
195
-
{{ range $items }}
196
-
{{ $repoOwner := resolve .Repo.Did }}
197
-
{{ $repoName := .Repo.Name }}
198
-
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
199
-
200
-
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
201
-
{{ if .State.IsOpen }}
202
-
<span class="text-green-600 dark:text-green-500">
203
-
{{ i "git-pull-request" "w-4 h-4" }}
204
-
</span>
205
-
{{ else if .State.IsMerged }}
206
-
<span class="text-purple-600 dark:text-purple-500">
207
-
{{ i "git-merge" "w-4 h-4" }}
208
-
</span>
209
-
{{ else }}
210
-
<span class="text-gray-600 dark:text-gray-300">
211
-
{{ i "git-pull-request-closed" "w-4 h-4" }}
212
-
</span>
213
-
{{ end }}
214
-
<div class="flex-none min-w-8 text-right">
215
-
<span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span>
216
-
</div>
217
-
<div class="break-words max-w-full">
218
-
<a href="/{{$repoUrl}}/pulls/{{ .PullId }}" class="no-underline hover:underline">
219
-
{{ .Title -}}
220
-
</a>
221
-
on
222
-
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
223
-
{{$repoUrl}}
224
-
</a>
225
-
</div>
226
-
</div>
227
-
{{ end }}
228
-
</div>
229
-
</details>
230
-
{{ end }}
231
-
{{ end }}
232
-
233
-
{{ define "ownRepos" }}
234
-
<div>
235
-
<div class="text-sm font-bold p-2 pr-0 dark:text-white flex items-center justify-between gap-2">
236
-
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}?tab=repos"
237
-
class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">
238
-
<span>PINNED REPOS</span>
239
-
<span class="flex gap-1 items-center font-normal text-sm text-gray-500 dark:text-gray-400 ">
240
-
view all {{ i "chevron-right" "w-4 h-4" }}
241
-
</span>
242
-
</a>
243
-
{{ if and .LoggedInUser (eq .LoggedInUser.Did .Card.UserDid) }}
244
-
<button
245
-
hx-get="profile/edit-pins"
246
-
hx-target="#all-repos"
247
-
class="btn py-0 font-normal text-sm flex gap-2 items-center group">
248
-
{{ i "pencil" "w-3 h-3" }}
249
-
edit
250
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
251
-
</button>
252
-
{{ end }}
253
-
</div>
254
-
<div id="repos" class="grid grid-cols-1 gap-4 items-stretch">
255
-
{{ range .Repos }}
256
-
{{ template "user/fragments/repoCard" (list $ . false) }}
257
-
{{ else }}
258
-
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
259
-
{{ end }}
260
-
</div>
261
-
</div>
262
-
{{ end }}
263
-
264
-
{{ define "collaboratingRepos" }}
265
-
{{ if gt (len .CollaboratingRepos) 0 }}
266
-
<div>
267
-
<p class="text-sm font-bold p-2 dark:text-white">COLLABORATING ON</p>
268
-
<div id="collaborating" class="grid grid-cols-1 gap-4">
269
-
{{ range .CollaboratingRepos }}
270
-
{{ template "user/fragments/repoCard" (list $ . true) }}
271
-
{{ else }}
272
-
<p class="px-6 dark:text-white">This user is not collaborating.</p>
273
-
{{ end }}
274
-
</div>
275
-
</div>
276
-
{{ end }}
277
-
{{ end }}
278
-
279
-
{{ define "punchcard" }}
280
-
{{ $now := now }}
281
-
<div>
282
-
<p class="p-2 flex gap-2 text-sm font-bold dark:text-white">
283
-
PUNCHCARD
284
-
<span class="font-normal text-sm text-gray-500 dark:text-gray-400 ">
285
-
{{ .Total | int64 | commaFmt }} commits
286
-
</span>
287
-
</p>
288
-
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm">
289
-
<div class="grid grid-cols-28 md:grid-cols-14 gap-y-2 w-full h-full">
290
-
{{ range .Punches }}
291
-
{{ $count := .Count }}
292
-
{{ $theme := "bg-gray-200 dark:bg-gray-700 size-[4px]" }}
293
-
{{ if lt $count 1 }}
294
-
{{ $theme = "bg-gray-200 dark:bg-gray-700 size-[4px]" }}
295
-
{{ else if lt $count 2 }}
296
-
{{ $theme = "bg-green-200 dark:bg-green-900 size-[5px]" }}
297
-
{{ else if lt $count 4 }}
298
-
{{ $theme = "bg-green-300 dark:bg-green-800 size-[5px]" }}
299
-
{{ else if lt $count 8 }}
300
-
{{ $theme = "bg-green-400 dark:bg-green-700 size-[6px]" }}
301
-
{{ else }}
302
-
{{ $theme = "bg-green-500 dark:bg-green-600 size-[7px]" }}
303
-
{{ end }}
304
-
305
-
{{ if .Date.After $now }}
306
-
{{ $theme = "border border-gray-200 dark:border-gray-700 size-[4px]" }}
307
-
{{ end }}
308
-
<div class="w-full h-full flex justify-center items-center">
309
-
<div
310
-
class="aspect-square rounded-full transition-all duration-300 {{ $theme }} max-w-full max-h-full"
311
-
title="{{ .Date.Format "2006-01-02" }}: {{ .Count }} commits">
312
-
</div>
313
-
</div>
314
-
{{ end }}
315
-
</div>
316
-
</div>
317
-
</div>
318
-
{{ end }}
+7
-18
appview/pages/templates/user/repos.html
+7
-18
appview/pages/templates/user/repos.html
···
1
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} ยท repos {{ end }}
2
2
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}'s repos" />
5
-
<meta property="og:type" content="object" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab=repos" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
{{ template "user/fragments/profileCard" .Card }}
14
-
</div>
15
-
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
16
-
{{ block "ownRepos" . }}{{ end }}
17
-
</div>
18
-
</div>
3
+
{{ define "profileContent" }}
4
+
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
5
+
{{ block "ownRepos" . }}{{ end }}
6
+
</div>
19
7
{{ end }}
20
8
21
9
{{ define "ownRepos" }}
22
-
<p class="text-sm font-bold p-2 dark:text-white">ALL REPOSITORIES</p>
23
10
<div id="repos" class="grid grid-cols-1 gap-4 mb-6">
24
11
{{ range .Repos }}
25
-
{{ template "user/fragments/repoCard" (list $ . false) }}
12
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
13
+
{{ template "user/fragments/repoCard" (list $ . false) }}
14
+
</div>
26
15
{{ else }}
27
16
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
28
17
{{ end }}
+19
appview/pages/templates/user/starred.html
+19
appview/pages/templates/user/starred.html
···
1
+
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} ยท repos {{ end }}
2
+
3
+
{{ define "profileContent" }}
4
+
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
5
+
{{ block "starredRepos" . }}{{ end }}
6
+
</div>
7
+
{{ end }}
8
+
9
+
{{ define "starredRepos" }}
10
+
<div id="repos" class="grid grid-cols-1 gap-4 mb-6">
11
+
{{ range .Repos }}
12
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
13
+
{{ template "user/fragments/repoCard" (list $ . true) }}
14
+
</div>
15
+
{{ else }}
16
+
<p class="px-6 dark:text-white">This user does not have any starred repos yet.</p>
17
+
{{ end }}
18
+
</div>
19
+
{{ end }}
+45
appview/pages/templates/user/strings.html
+45
appview/pages/templates/user/strings.html
···
1
+
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} ยท strings {{ end }}
2
+
3
+
{{ define "profileContent" }}
4
+
<div id="all-strings" class="md:col-span-8 order-2 md:order-2">
5
+
{{ block "allStrings" . }}{{ end }}
6
+
</div>
7
+
{{ end }}
8
+
9
+
{{ define "allStrings" }}
10
+
<div id="strings" class="grid grid-cols-1 gap-4 mb-6">
11
+
{{ range .Strings }}
12
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
13
+
{{ template "singleString" (list $ .) }}
14
+
</div>
15
+
{{ else }}
16
+
<p class="px-6 dark:text-white">This user does not have any strings yet.</p>
17
+
{{ end }}
18
+
</div>
19
+
{{ end }}
20
+
21
+
{{ define "singleString" }}
22
+
{{ $root := index . 0 }}
23
+
{{ $s := index . 1 }}
24
+
<div class="py-4 px-6 rounded bg-white dark:bg-gray-800">
25
+
<div class="font-medium dark:text-white flex gap-2 items-center">
26
+
<a href="/strings/{{ or $root.Card.UserHandle $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a>
27
+
</div>
28
+
{{ with $s.Description }}
29
+
<div class="text-gray-600 dark:text-gray-300 text-sm">
30
+
{{ . }}
31
+
</div>
32
+
{{ end }}
33
+
34
+
{{ $stat := $s.Stats }}
35
+
<div class="text-gray-400 pt-4 text-sm font-mono inline-flex gap-2 mt-auto">
36
+
<span>{{ $stat.LineCount }} line{{if ne $stat.LineCount 1}}s{{end}}</span>
37
+
<span class="select-none [&:before]:content-['ยท']"></span>
38
+
{{ with $s.Edited }}
39
+
<span>edited {{ template "repo/fragments/shortTimeAgo" . }}</span>
40
+
{{ else }}
41
+
{{ template "repo/fragments/shortTimeAgo" $s.Created }}
42
+
{{ end }}
43
+
</div>
44
+
</div>
45
+
{{ end }}
+24
-21
appview/pulls/pulls.go
+24
-21
appview/pulls/pulls.go
···
605
605
defer tx.Rollback()
606
606
607
607
createdAt := time.Now().Format(time.RFC3339)
608
-
ownerDid := user.Did
609
608
610
609
pullAt, err := db.GetPullAt(s.db, f.RepoAt(), pull.PullId)
611
610
if err != nil {
···
614
613
return
615
614
}
616
615
617
-
atUri := f.RepoAt().String()
618
616
client, err := s.oauth.AuthorizedClient(r)
619
617
if err != nil {
620
618
log.Println("failed to get authorized client", err)
···
627
625
Rkey: tid.TID(),
628
626
Record: &lexutil.LexiconTypeDecoder{
629
627
Val: &tangled.RepoPullComment{
630
-
Repo: &atUri,
631
628
Pull: string(pullAt),
632
-
Owner: &ownerDid,
633
629
Body: body,
634
630
CreatedAt: createdAt,
635
631
},
···
854
850
}
855
851
856
852
func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *reporesolver.ResolvedRepo, user *oauth.User, forkRepo string, title, body, targetBranch, sourceBranch string, isStacked bool) {
857
-
fork, err := db.GetForkByDid(s.db, user.Did, forkRepo)
853
+
repoString := strings.SplitN(forkRepo, "/", 2)
854
+
forkOwnerDid := repoString[0]
855
+
repoName := repoString[1]
856
+
fork, err := db.GetForkByDid(s.db, forkOwnerDid, repoName)
858
857
if errors.Is(err, sql.ErrNoRows) {
859
858
s.pages.Notice(w, "pull", "No such fork.")
860
859
return
···
912
911
// hiddenRef: hidden/feature-1/main (on repo-fork)
913
912
// targetBranch: main (on repo-1)
914
913
// sourceBranch: feature-1 (on repo-fork)
915
-
comparison, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch)
914
+
comparison, err := us.Compare(fork.Did, fork.Name, hiddenRef, sourceBranch)
916
915
if err != nil {
917
916
log.Println("failed to compare across branches", err)
918
917
s.pages.Notice(w, "pull", err.Error())
···
1038
1037
Rkey: rkey,
1039
1038
Record: &lexutil.LexiconTypeDecoder{
1040
1039
Val: &tangled.RepoPull{
1041
-
Title: title,
1042
-
PullId: int64(pullId),
1043
-
TargetRepo: string(f.RepoAt()),
1044
-
TargetBranch: targetBranch,
1045
-
Patch: patch,
1046
-
Source: recordPullSource,
1040
+
Title: title,
1041
+
Target: &tangled.RepoPull_Target{
1042
+
Repo: string(f.RepoAt()),
1043
+
Branch: targetBranch,
1044
+
},
1045
+
Patch: patch,
1046
+
Source: recordPullSource,
1047
1047
},
1048
1048
},
1049
1049
})
···
1274
1274
}
1275
1275
1276
1276
forkVal := r.URL.Query().Get("fork")
1277
-
1277
+
repoString := strings.SplitN(forkVal, "/", 2)
1278
+
forkOwnerDid := repoString[0]
1279
+
forkName := repoString[1]
1278
1280
// fork repo
1279
-
repo, err := db.GetRepo(s.db, user.Did, forkVal)
1281
+
repo, err := db.GetRepo(s.db, forkOwnerDid, forkName)
1280
1282
if err != nil {
1281
1283
log.Println("failed to get repo", user.Did, forkVal)
1282
1284
return
···
1289
1291
return
1290
1292
}
1291
1293
1292
-
sourceResult, err := sourceBranchesClient.Branches(user.Did, repo.Name)
1294
+
sourceResult, err := sourceBranchesClient.Branches(forkOwnerDid, repo.Name)
1293
1295
if err != nil {
1294
1296
log.Println("failed to reach knotserver for source branches", err)
1295
1297
return
···
1609
1611
SwapRecord: ex.Cid,
1610
1612
Record: &lexutil.LexiconTypeDecoder{
1611
1613
Val: &tangled.RepoPull{
1612
-
Title: pull.Title,
1613
-
PullId: int64(pull.PullId),
1614
-
TargetRepo: string(f.RepoAt()),
1615
-
TargetBranch: pull.TargetBranch,
1616
-
Patch: patch, // new patch
1617
-
Source: recordPullSource,
1614
+
Title: pull.Title,
1615
+
Target: &tangled.RepoPull_Target{
1616
+
Repo: string(f.RepoAt()),
1617
+
Branch: pull.TargetBranch,
1618
+
},
1619
+
Patch: patch, // new patch
1620
+
Source: recordPullSource,
1618
1621
},
1619
1622
},
1620
1623
})
+191
-132
appview/state/profile.go
+191
-132
appview/state/profile.go
···
17
17
"github.com/gorilla/feeds"
18
18
"tangled.sh/tangled.sh/core/api/tangled"
19
19
"tangled.sh/tangled.sh/core/appview/db"
20
-
"tangled.sh/tangled.sh/core/appview/oauth"
20
+
// "tangled.sh/tangled.sh/core/appview/oauth"
21
21
"tangled.sh/tangled.sh/core/appview/pages"
22
22
)
23
23
24
24
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
25
25
tabVal := r.URL.Query().Get("tab")
26
26
switch tabVal {
27
-
case "":
28
-
s.profileHomePage(w, r)
29
27
case "repos":
30
28
s.reposPage(w, r)
31
29
case "followers":
32
30
s.followersPage(w, r)
33
31
case "following":
34
32
s.followingPage(w, r)
33
+
case "starred":
34
+
s.starredPage(w, r)
35
+
case "strings":
36
+
s.stringsPage(w, r)
37
+
default:
38
+
s.profileOverview(w, r)
35
39
}
36
40
}
37
41
38
-
type ProfilePageParams struct {
39
-
Id identity.Identity
40
-
LoggedInUser *oauth.User
41
-
Card pages.ProfileCard
42
-
}
43
-
44
-
func (s *State) profilePage(w http.ResponseWriter, r *http.Request) *ProfilePageParams {
42
+
func (s *State) profile(r *http.Request) (*pages.ProfileCard, error) {
45
43
didOrHandle := chi.URLParam(r, "user")
46
44
if didOrHandle == "" {
47
-
http.Error(w, "bad request", http.StatusBadRequest)
48
-
return nil
45
+
return nil, fmt.Errorf("empty DID or handle")
49
46
}
50
47
51
48
ident, ok := r.Context().Value("resolvedId").(identity.Identity)
52
49
if !ok {
53
-
log.Printf("malformed middleware")
54
-
w.WriteHeader(http.StatusInternalServerError)
55
-
return nil
50
+
return nil, fmt.Errorf("failed to resolve ID")
56
51
}
57
52
did := ident.DID.String()
58
53
59
54
profile, err := db.GetProfile(s.db, did)
60
55
if err != nil {
61
-
log.Printf("getting profile data for %s: %s", did, err)
62
-
s.pages.Error500(w)
63
-
return nil
56
+
return nil, fmt.Errorf("failed to get profile: %w", err)
57
+
}
58
+
59
+
repoCount, err := db.CountRepos(s.db, db.FilterEq("did", did))
60
+
if err != nil {
61
+
return nil, fmt.Errorf("failed to get repo count: %w", err)
62
+
}
63
+
64
+
stringCount, err := db.CountStrings(s.db, db.FilterEq("did", did))
65
+
if err != nil {
66
+
return nil, fmt.Errorf("failed to get string count: %w", err)
67
+
}
68
+
69
+
starredCount, err := db.CountStars(s.db, db.FilterEq("starred_by_did", did))
70
+
if err != nil {
71
+
return nil, fmt.Errorf("failed to get starred repo count: %w", err)
64
72
}
65
73
66
74
followStats, err := db.GetFollowerFollowingCount(s.db, did)
67
75
if err != nil {
68
-
log.Printf("getting follow stats for %s: %s", did, err)
76
+
return nil, fmt.Errorf("failed to get follower stats: %w", err)
69
77
}
70
78
71
79
loggedInUser := s.oauth.GetUser(r)
···
74
82
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did)
75
83
}
76
84
77
-
return &ProfilePageParams{
78
-
Id: ident,
79
-
LoggedInUser: loggedInUser,
80
-
Card: pages.ProfileCard{
81
-
UserDid: did,
82
-
UserHandle: ident.Handle.String(),
83
-
Profile: profile,
84
-
FollowStatus: followStatus,
85
+
now := time.Now()
86
+
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
87
+
punchcard, err := db.MakePunchcard(
88
+
s.db,
89
+
db.FilterEq("did", did),
90
+
db.FilterGte("date", startOfYear.Format(time.DateOnly)),
91
+
db.FilterLte("date", now.Format(time.DateOnly)),
92
+
)
93
+
if err != nil {
94
+
return nil, fmt.Errorf("failed to get punchcard for %s: %w", did, err)
95
+
}
96
+
97
+
return &pages.ProfileCard{
98
+
UserDid: did,
99
+
UserHandle: ident.Handle.String(),
100
+
Profile: profile,
101
+
FollowStatus: followStatus,
102
+
Stats: pages.ProfileStats{
103
+
RepoCount: repoCount,
104
+
StringCount: stringCount,
105
+
StarredCount: starredCount,
85
106
FollowersCount: followStats.Followers,
86
107
FollowingCount: followStats.Following,
87
108
},
88
-
}
109
+
Punchcard: punchcard,
110
+
}, nil
89
111
}
90
112
91
-
func (s *State) profileHomePage(w http.ResponseWriter, r *http.Request) {
92
-
pageWithProfile := s.profilePage(w, r)
93
-
if pageWithProfile == nil {
113
+
func (s *State) profileOverview(w http.ResponseWriter, r *http.Request) {
114
+
l := s.logger.With("handler", "profileHomePage")
115
+
116
+
profile, err := s.profile(r)
117
+
if err != nil {
118
+
l.Error("failed to build profile card", "err", err)
119
+
s.pages.Error500(w)
94
120
return
95
121
}
122
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
96
123
97
-
id := pageWithProfile.Id
98
124
repos, err := db.GetRepos(
99
125
s.db,
100
126
0,
101
-
db.FilterEq("did", id.DID),
127
+
db.FilterEq("did", profile.UserDid),
102
128
)
103
129
if err != nil {
104
-
log.Printf("getting repos for %s: %s", id.DID, err)
130
+
l.Error("failed to fetch repos", "err", err)
105
131
}
106
132
107
-
profile := pageWithProfile.Card.Profile
108
133
// filter out ones that are pinned
109
134
pinnedRepos := []db.Repo{}
110
135
for i, r := range repos {
111
136
// if this is a pinned repo, add it
112
-
if slices.Contains(profile.PinnedRepos[:], r.RepoAt()) {
137
+
if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) {
113
138
pinnedRepos = append(pinnedRepos, r)
114
139
}
115
140
116
141
// if there are no saved pins, add the first 4 repos
117
-
if profile.IsPinnedReposEmpty() && i < 4 {
142
+
if profile.Profile.IsPinnedReposEmpty() && i < 4 {
118
143
pinnedRepos = append(pinnedRepos, r)
119
144
}
120
145
}
121
146
122
-
collaboratingRepos, err := db.CollaboratingIn(s.db, id.DID.String())
147
+
collaboratingRepos, err := db.CollaboratingIn(s.db, profile.UserDid)
123
148
if err != nil {
124
-
log.Printf("getting collaborating repos for %s: %s", id.DID, err)
149
+
l.Error("failed to fetch collaborating repos", "err", err)
125
150
}
126
151
127
152
pinnedCollaboratingRepos := []db.Repo{}
128
153
for _, r := range collaboratingRepos {
129
154
// if this is a pinned repo, add it
130
-
if slices.Contains(profile.PinnedRepos[:], r.RepoAt()) {
155
+
if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) {
131
156
pinnedCollaboratingRepos = append(pinnedCollaboratingRepos, r)
132
157
}
133
158
}
134
159
135
-
timeline, err := db.MakeProfileTimeline(s.db, id.DID.String())
160
+
timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid)
136
161
if err != nil {
137
-
log.Printf("failed to create profile timeline for %s: %s", id.DID, err)
162
+
l.Error("failed to create timeline", "err", err)
138
163
}
139
164
140
-
var didsToResolve []string
141
-
for _, r := range collaboratingRepos {
142
-
didsToResolve = append(didsToResolve, r.Did)
143
-
}
144
-
for _, byMonth := range timeline.ByMonth {
145
-
for _, pe := range byMonth.PullEvents.Items {
146
-
didsToResolve = append(didsToResolve, pe.Repo.Did)
147
-
}
148
-
for _, ie := range byMonth.IssueEvents.Items {
149
-
didsToResolve = append(didsToResolve, ie.Metadata.Repo.Did)
150
-
}
151
-
for _, re := range byMonth.RepoEvents {
152
-
didsToResolve = append(didsToResolve, re.Repo.Did)
153
-
if re.Source != nil {
154
-
didsToResolve = append(didsToResolve, re.Source.Did)
155
-
}
156
-
}
165
+
s.pages.ProfileOverview(w, pages.ProfileOverviewParams{
166
+
LoggedInUser: s.oauth.GetUser(r),
167
+
Card: profile,
168
+
Repos: pinnedRepos,
169
+
CollaboratingRepos: pinnedCollaboratingRepos,
170
+
ProfileTimeline: timeline,
171
+
})
172
+
}
173
+
174
+
func (s *State) reposPage(w http.ResponseWriter, r *http.Request) {
175
+
l := s.logger.With("handler", "reposPage")
176
+
177
+
profile, err := s.profile(r)
178
+
if err != nil {
179
+
l.Error("failed to build profile card", "err", err)
180
+
s.pages.Error500(w)
181
+
return
157
182
}
183
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
158
184
159
-
now := time.Now()
160
-
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
161
-
punchcard, err := db.MakePunchcard(
185
+
repos, err := db.GetRepos(
162
186
s.db,
163
-
db.FilterEq("did", id.DID),
164
-
db.FilterGte("date", startOfYear.Format(time.DateOnly)),
165
-
db.FilterLte("date", now.Format(time.DateOnly)),
187
+
0,
188
+
db.FilterEq("did", profile.UserDid),
166
189
)
167
190
if err != nil {
168
-
log.Println("failed to get punchcard for did", "did", id.DID, "err", err)
191
+
l.Error("failed to get repos", "err", err)
192
+
s.pages.Error500(w)
193
+
return
169
194
}
170
195
171
-
s.pages.ProfileHomePage(w, pages.ProfileHomePageParams{
172
-
LoggedInUser: pageWithProfile.LoggedInUser,
173
-
Repos: pinnedRepos,
174
-
CollaboratingRepos: pinnedCollaboratingRepos,
175
-
Card: pageWithProfile.Card,
176
-
Punchcard: punchcard,
177
-
ProfileTimeline: timeline,
196
+
err = s.pages.ProfileRepos(w, pages.ProfileReposParams{
197
+
LoggedInUser: s.oauth.GetUser(r),
198
+
Repos: repos,
199
+
Card: profile,
178
200
})
179
201
}
180
202
181
-
func (s *State) reposPage(w http.ResponseWriter, r *http.Request) {
182
-
pageWithProfile := s.profilePage(w, r)
183
-
if pageWithProfile == nil {
203
+
func (s *State) starredPage(w http.ResponseWriter, r *http.Request) {
204
+
l := s.logger.With("handler", "starredPage")
205
+
206
+
profile, err := s.profile(r)
207
+
if err != nil {
208
+
l.Error("failed to build profile card", "err", err)
209
+
s.pages.Error500(w)
210
+
return
211
+
}
212
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
213
+
214
+
stars, err := db.GetStars(s.db, 0, db.FilterEq("starred_by_did", profile.UserDid))
215
+
if err != nil {
216
+
l.Error("failed to get stars", "err", err)
217
+
s.pages.Error500(w)
184
218
return
185
219
}
220
+
var repoAts []string
221
+
for _, s := range stars {
222
+
repoAts = append(repoAts, string(s.RepoAt))
223
+
}
186
224
187
-
id := pageWithProfile.Id
188
225
repos, err := db.GetRepos(
189
226
s.db,
190
227
0,
191
-
db.FilterEq("did", id.DID),
228
+
db.FilterIn("at_uri", repoAts),
192
229
)
193
230
if err != nil {
194
-
log.Printf("getting repos for %s: %s", id.DID, err)
231
+
l.Error("failed to get repos", "err", err)
232
+
s.pages.Error500(w)
233
+
return
195
234
}
196
235
197
-
s.pages.ReposPage(w, pages.ReposPageParams{
198
-
LoggedInUser: pageWithProfile.LoggedInUser,
236
+
err = s.pages.ProfileStarred(w, pages.ProfileStarredParams{
237
+
LoggedInUser: s.oauth.GetUser(r),
199
238
Repos: repos,
200
-
Card: pageWithProfile.Card,
239
+
Card: profile,
240
+
})
241
+
}
242
+
243
+
func (s *State) stringsPage(w http.ResponseWriter, r *http.Request) {
244
+
l := s.logger.With("handler", "stringsPage")
245
+
246
+
profile, err := s.profile(r)
247
+
if err != nil {
248
+
l.Error("failed to build profile card", "err", err)
249
+
s.pages.Error500(w)
250
+
return
251
+
}
252
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
253
+
254
+
strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid))
255
+
if err != nil {
256
+
l.Error("failed to get strings", "err", err)
257
+
s.pages.Error500(w)
258
+
return
259
+
}
260
+
261
+
err = s.pages.ProfileStrings(w, pages.ProfileStringsParams{
262
+
LoggedInUser: s.oauth.GetUser(r),
263
+
Strings: strings,
264
+
Card: profile,
201
265
})
202
266
}
203
267
204
268
type FollowsPageParams struct {
205
-
LoggedInUser *oauth.User
206
-
Follows []pages.FollowCard
207
-
Card pages.ProfileCard
269
+
Follows []pages.FollowCard
270
+
Card *pages.ProfileCard
208
271
}
209
272
210
-
func (s *State) followPage(w http.ResponseWriter, r *http.Request, fetchFollows func(db.Execer, string) ([]db.Follow, error), extractDid func(db.Follow) string) (FollowsPageParams, error) {
211
-
pageWithProfile := s.profilePage(w, r)
212
-
if pageWithProfile == nil {
213
-
return FollowsPageParams{}, nil
273
+
func (s *State) followPage(
274
+
r *http.Request,
275
+
fetchFollows func(db.Execer, string) ([]db.Follow, error),
276
+
extractDid func(db.Follow) string,
277
+
) (*FollowsPageParams, error) {
278
+
l := s.logger.With("handler", "reposPage")
279
+
280
+
profile, err := s.profile(r)
281
+
if err != nil {
282
+
return nil, err
214
283
}
284
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
215
285
216
-
id := pageWithProfile.Id
217
-
loggedInUser := pageWithProfile.LoggedInUser
286
+
loggedInUser := s.oauth.GetUser(r)
218
287
219
-
follows, err := fetchFollows(s.db, id.DID.String())
288
+
follows, err := fetchFollows(s.db, profile.UserDid)
220
289
if err != nil {
221
-
log.Printf("getting followers for %s: %s", id.DID, err)
222
-
return FollowsPageParams{}, err
290
+
l.Error("failed to fetch follows", "err", err)
291
+
return nil, err
223
292
}
224
293
225
294
if len(follows) == 0 {
226
-
return FollowsPageParams{
227
-
LoggedInUser: loggedInUser,
228
-
Follows: []pages.FollowCard{},
229
-
Card: pageWithProfile.Card,
230
-
}, nil
295
+
return nil, nil
231
296
}
232
297
233
298
followDids := make([]string, 0, len(follows))
···
237
302
238
303
profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids))
239
304
if err != nil {
240
-
log.Printf("getting profile for %s: %s", followDids, err)
241
-
return FollowsPageParams{}, err
305
+
l.Error("failed to get profiles", "followDids", followDids, "err", err)
306
+
return nil, err
242
307
}
243
308
244
309
followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids)
···
246
311
log.Printf("getting follow counts for %s: %s", followDids, err)
247
312
}
248
313
249
-
var loggedInUserFollowing map[string]struct{}
314
+
loggedInUserFollowing := make(map[string]struct{})
250
315
if loggedInUser != nil {
251
316
following, err := db.GetFollowing(s.db, loggedInUser.Did)
252
317
if err != nil {
253
-
return FollowsPageParams{}, err
318
+
l.Error("failed to get follow list", "err", err, "loggedInUser", loggedInUser.Did)
319
+
return nil, err
254
320
}
255
-
if len(following) > 0 {
256
-
loggedInUserFollowing = make(map[string]struct{}, len(following))
257
-
for _, follow := range following {
258
-
loggedInUserFollowing[follow.SubjectDid] = struct{}{}
259
-
}
321
+
loggedInUserFollowing = make(map[string]struct{}, len(following))
322
+
for _, follow := range following {
323
+
loggedInUserFollowing[follow.SubjectDid] = struct{}{}
260
324
}
261
325
}
262
326
263
-
followCards := make([]pages.FollowCard, 0, len(follows))
264
-
for _, did := range followDids {
265
-
followStats, exists := followStatsMap[did]
266
-
if !exists {
267
-
followStats = db.FollowStats{}
268
-
}
327
+
followCards := make([]pages.FollowCard, len(follows))
328
+
for i, did := range followDids {
329
+
followStats := followStatsMap[did]
269
330
followStatus := db.IsNotFollowing
270
-
if loggedInUserFollowing != nil {
271
-
if _, exists := loggedInUserFollowing[did]; exists {
272
-
followStatus = db.IsFollowing
273
-
} else if loggedInUser.Did == did {
274
-
followStatus = db.IsSelf
275
-
}
331
+
if _, exists := loggedInUserFollowing[did]; exists {
332
+
followStatus = db.IsFollowing
333
+
} else if loggedInUser != nil && loggedInUser.Did == did {
334
+
followStatus = db.IsSelf
276
335
}
336
+
277
337
var profile *db.Profile
278
338
if p, exists := profiles[did]; exists {
279
339
profile = p
···
281
341
profile = &db.Profile{}
282
342
profile.Did = did
283
343
}
284
-
followCards = append(followCards, pages.FollowCard{
344
+
followCards[i] = pages.FollowCard{
285
345
UserDid: did,
286
346
FollowStatus: followStatus,
287
347
FollowersCount: followStats.Followers,
288
348
FollowingCount: followStats.Following,
289
349
Profile: profile,
290
-
})
350
+
}
291
351
}
292
352
293
-
return FollowsPageParams{
294
-
LoggedInUser: loggedInUser,
295
-
Follows: followCards,
296
-
Card: pageWithProfile.Card,
353
+
return &FollowsPageParams{
354
+
Follows: followCards,
355
+
Card: profile,
297
356
}, nil
298
357
}
299
358
300
359
func (s *State) followersPage(w http.ResponseWriter, r *http.Request) {
301
-
followPage, err := s.followPage(w, r, db.GetFollowers, func(f db.Follow) string { return f.UserDid })
360
+
followPage, err := s.followPage(r, db.GetFollowers, func(f db.Follow) string { return f.UserDid })
302
361
if err != nil {
303
362
s.pages.Notice(w, "all-followers", "Failed to load followers")
304
363
return
305
364
}
306
365
307
-
s.pages.FollowersPage(w, pages.FollowersPageParams{
308
-
LoggedInUser: followPage.LoggedInUser,
366
+
s.pages.ProfileFollowers(w, pages.ProfileFollowersParams{
367
+
LoggedInUser: s.oauth.GetUser(r),
309
368
Followers: followPage.Follows,
310
369
Card: followPage.Card,
311
370
})
312
371
}
313
372
314
373
func (s *State) followingPage(w http.ResponseWriter, r *http.Request) {
315
-
followPage, err := s.followPage(w, r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid })
374
+
followPage, err := s.followPage(r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid })
316
375
if err != nil {
317
376
s.pages.Notice(w, "all-following", "Failed to load following")
318
377
return
319
378
}
320
379
321
-
s.pages.FollowingPage(w, pages.FollowingPageParams{
322
-
LoggedInUser: followPage.LoggedInUser,
380
+
s.pages.ProfileFollowing(w, pages.ProfileFollowingParams{
381
+
LoggedInUser: s.oauth.GetUser(r),
323
382
Following: followPage.Follows,
324
383
Card: followPage.Card,
325
384
})
+2
appview/state/state.go
+2
appview/state/state.go
+1
-59
appview/strings/strings.go
+1
-59
appview/strings/strings.go
···
5
5
"log/slog"
6
6
"net/http"
7
7
"path"
8
-
"slices"
9
8
"strconv"
10
9
"time"
11
10
···
161
160
}
162
161
163
162
func (s *Strings) dashboard(w http.ResponseWriter, r *http.Request) {
164
-
l := s.Logger.With("handler", "dashboard")
165
-
166
-
id, ok := r.Context().Value("resolvedId").(identity.Identity)
167
-
if !ok {
168
-
l.Error("malformed middleware")
169
-
w.WriteHeader(http.StatusInternalServerError)
170
-
return
171
-
}
172
-
l = l.With("did", id.DID, "handle", id.Handle)
173
-
174
-
all, err := db.GetStrings(
175
-
s.Db,
176
-
0,
177
-
db.FilterEq("did", id.DID),
178
-
)
179
-
if err != nil {
180
-
l.Error("failed to fetch strings", "err", err)
181
-
w.WriteHeader(http.StatusInternalServerError)
182
-
return
183
-
}
184
-
185
-
slices.SortFunc(all, func(a, b db.String) int {
186
-
if a.Created.After(b.Created) {
187
-
return -1
188
-
} else {
189
-
return 1
190
-
}
191
-
})
192
-
193
-
profile, err := db.GetProfile(s.Db, id.DID.String())
194
-
if err != nil {
195
-
l.Error("failed to fetch user profile", "err", err)
196
-
w.WriteHeader(http.StatusInternalServerError)
197
-
return
198
-
}
199
-
loggedInUser := s.OAuth.GetUser(r)
200
-
followStatus := db.IsNotFollowing
201
-
if loggedInUser != nil {
202
-
followStatus = db.GetFollowStatus(s.Db, loggedInUser.Did, id.DID.String())
203
-
}
204
-
205
-
followStats, err := db.GetFollowerFollowingCount(s.Db, id.DID.String())
206
-
if err != nil {
207
-
l.Error("failed to get follow stats", "err", err)
208
-
}
209
-
210
-
s.Pages.StringsDashboard(w, pages.StringsDashboardParams{
211
-
LoggedInUser: s.OAuth.GetUser(r),
212
-
Card: pages.ProfileCard{
213
-
UserDid: id.DID.String(),
214
-
UserHandle: id.Handle.String(),
215
-
Profile: profile,
216
-
FollowStatus: followStatus,
217
-
FollowersCount: followStats.Followers,
218
-
FollowingCount: followStats.Following,
219
-
},
220
-
Strings: all,
221
-
})
163
+
http.Redirect(w, r, fmt.Sprintf("/%s?tab=strings", chi.URLParam(r, "user")), http.StatusFound)
222
164
}
223
165
224
166
func (s *Strings) edit(w http.ResponseWriter, r *http.Request) {
+5
-4
cmd/gen.go
+5
-4
cmd/gen.go
···
18
18
tangled.FeedReaction{},
19
19
tangled.FeedStar{},
20
20
tangled.GitRefUpdate{},
21
+
tangled.GitRefUpdate_CommitCountBreakdown{},
22
+
tangled.GitRefUpdate_IndividualEmailCommitCount{},
23
+
tangled.GitRefUpdate_LangBreakdown{},
24
+
tangled.GitRefUpdate_IndividualLanguageSize{},
21
25
tangled.GitRefUpdate_Meta{},
22
-
tangled.GitRefUpdate_Meta_CommitCount{},
23
-
tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem{},
24
-
tangled.GitRefUpdate_Meta_LangBreakdown{},
25
-
tangled.GitRefUpdate_Pair{},
26
26
tangled.GraphFollow{},
27
27
tangled.Knot{},
28
28
tangled.KnotMember{},
···
47
47
tangled.RepoPullComment{},
48
48
tangled.RepoPull_Source{},
49
49
tangled.RepoPullStatus{},
50
+
tangled.RepoPull_Target{},
50
51
tangled.Spindle{},
51
52
tangled.SpindleMember{},
52
53
tangled.String{},
+3
-3
docs/contributing.md
+3
-3
docs/contributing.md
···
11
11
### message format
12
12
13
13
```
14
-
<service/top-level directory>: <affected package/directory>: <short summary of change>
14
+
<service/top-level directory>/<affected package/directory>: <short summary of change>
15
15
16
16
17
17
Optional longer description can go here, if necessary. Explain what the
···
23
23
Here are some examples:
24
24
25
25
```
26
-
appview: state: fix token expiry check in middleware
26
+
appview/state: fix token expiry check in middleware
27
27
28
28
The previous check did not account for clock drift, leading to premature
29
29
token invalidation.
30
30
```
31
31
32
32
```
33
-
knotserver: git/service: improve error checking in upload-pack
33
+
knotserver/git/service: improve error checking in upload-pack
34
34
```
35
35
36
36
+53
-12
docs/hacking.md
+53
-12
docs/hacking.md
···
48
48
redis-server
49
49
```
50
50
51
-
## running a knot
51
+
## running knots and spindles
52
52
53
53
An end-to-end knot setup requires setting up a machine with
54
54
`sshd`, `AuthorizedKeysCommand`, and git user, which is
55
55
quite cumbersome. So the nix flake provides a
56
56
`nixosConfiguration` to do so.
57
57
58
-
To begin, grab your DID from http://localhost:3000/settings.
59
-
Then, set `TANGLED_VM_KNOT_OWNER` and
60
-
`TANGLED_VM_SPINDLE_OWNER` to your DID.
58
+
<details>
59
+
<summary><strong>MacOS users will have to setup a Nix Builder first</strong></summary>
60
+
61
+
In order to build Tangled's dev VM on macOS, you will
62
+
first need to set up a Linux Nix builder. The recommended
63
+
way to do so is to run a [`darwin.linux-builder`
64
+
VM](https://nixos.org/manual/nixpkgs/unstable/#sec-darwin-builder)
65
+
and to register it in `nix.conf` as a builder for Linux
66
+
with the same architecture as your Mac (`linux-aarch64` if
67
+
you are using Apple Silicon).
68
+
69
+
> IMPORTANT: You must build `darwin.linux-builder` somewhere other than inside
70
+
> the tangled repo so that it doesn't conflict with the other VM. For example,
71
+
> you can do
72
+
>
73
+
> ```shell
74
+
> cd $(mktemp -d buildervm.XXXXX) && nix run nixpkgs#darwin.linux-builder
75
+
> ```
76
+
>
77
+
> to store the builder VM in a temporary dir.
78
+
>
79
+
> You should read and follow [all the other intructions][darwin builder vm] to
80
+
> avoid subtle problems.
81
+
82
+
Alternatively, you can use any other method to set up a
83
+
Linux machine with `nix` installed that you can `sudo ssh`
84
+
into (in other words, root user on your Mac has to be able
85
+
to ssh into the Linux machine without entering a password)
86
+
and that has the same architecture as your Mac. See
87
+
[remote builder
88
+
instructions](https://nix.dev/manual/nix/2.28/advanced-topics/distributed-builds.html#requirements)
89
+
for how to register such a builder in `nix.conf`.
61
90
62
-
If you don't want to [set up a spindle](#running-a-spindle),
63
-
you can use any placeholder value.
91
+
> WARNING: If you'd like to use
92
+
> [`nixos-lima`](https://github.com/nixos-lima/nixos-lima) or
93
+
> [Orbstack](https://orbstack.dev/), note that setting them up so that `sudo
94
+
> ssh` works can be tricky. It seems to be [possible with
95
+
> Orbstack](https://github.com/orgs/orbstack/discussions/1669).
64
96
65
-
You can now start a lightweight NixOS VM like so:
97
+
</details>
98
+
99
+
To begin, grab your DID from http://localhost:3000/settings.
100
+
Then, set `TANGLED_VM_KNOT_OWNER` and
101
+
`TANGLED_VM_SPINDLE_OWNER` to your DID. You can now start a
102
+
lightweight NixOS VM like so:
66
103
67
104
```bash
68
105
nix run --impure .#vm
···
74
111
with `ssh` exposed on port 2222.
75
112
76
113
Once the services are running, head to
77
-
http://localhost:3000/knots and hit verify (and similarly,
78
-
http://localhost:3000/spindles to verify your spindle). It
79
-
should verify the ownership of the services instantly if
80
-
everything went smoothly.
114
+
http://localhost:3000/knots and hit verify. It should
115
+
verify the ownership of the services instantly if everything
116
+
went smoothly.
81
117
82
118
You can push repositories to this VM with this ssh config
83
119
block on your main machine:
···
97
133
git push local-dev main
98
134
```
99
135
100
-
## running a spindle
136
+
### running a spindle
101
137
102
138
The above VM should already be running a spindle on
103
139
`localhost:6555`. Head to http://localhost:3000/spindles and
···
119
155
# litecli has a nicer REPL interface:
120
156
litecli /var/lib/spindle/spindle.db
121
157
```
158
+
159
+
If for any reason you wish to disable either one of the
160
+
services in the VM, modify [nix/vm.nix](/nix/vm.nix) and set
161
+
`services.tangled-spindle.enable` (or
162
+
`services.tangled-knot.enable`) to `false`.
+130
-54
docs/spindle/pipeline.md
+130
-54
docs/spindle/pipeline.md
···
1
-
# spindle pipeline manifest
1
+
# spindle pipelines
2
+
3
+
Spindle workflows allow you to write CI/CD pipelines in a simple format. They're located in the `.tangled/workflows` directory at the root of your repository, and are defined using YAML.
4
+
5
+
The fields are:
2
6
3
-
Spindle pipelines are defined under the `.tangled/workflows` directory in a
4
-
repo. Generally:
7
+
- [Trigger](#trigger): A **required** field that defines when a workflow should be triggered.
8
+
- [Engine](#engine): A **required** field that defines which engine a workflow should run on.
9
+
- [Clone options](#clone-options): An **optional** field that defines how the repository should be cloned.
10
+
- [Dependencies](#dependencies): An **optional** field that allows you to list dependencies you may need.
11
+
- [Environment](#environment): An **optional** field that allows you to define environment variables.
12
+
- [Steps](#steps): An **optional** field that allows you to define what steps should run in the workflow.
5
13
6
-
* Pipelines are defined in YAML.
7
-
* Workflows can run using different *engines*.
14
+
## Trigger
8
15
9
-
The most barebones workflow looks like this:
16
+
The first thing to add to a workflow is the trigger, which defines when a workflow runs. This is defined using a `when` field, which takes in a list of conditions. Each condition has the following fields:
17
+
18
+
- `event`: This is a **required** field that defines when your workflow should run. It's a list that can take one or more of the following values:
19
+
- `push`: The workflow should run every time a commit is pushed to the repository.
20
+
- `pull_request`: The workflow should run every time a pull request is made or updated.
21
+
- `manual`: The workflow can be triggered manually.
22
+
- `branch`: This is a **required** field that defines which branches the workflow should run for. If used with the `push` event, commits to the branch(es) listed here will trigger the workflow. If used with the `pull_request` event, updates to pull requests targeting the branch(es) listed here will trigger the workflow. This field has no effect with the `manual` event.
23
+
24
+
For example, if you'd like define a workflow that runs when commits are pushed to the `main` and `develop` branches, or when pull requests that target the `main` branch are updated, or manually, you can do so with:
10
25
11
26
```yaml
12
27
when:
13
-
- event: ["push"]
28
+
- event: ["push", "manual"]
29
+
branch: ["main", "develop"]
30
+
- event: ["pull_request"]
14
31
branch: ["main"]
32
+
```
15
33
34
+
## Engine
35
+
36
+
Next is the engine on which the workflow should run, defined using the **required** `engine` field. The currently supported engines are:
37
+
38
+
- `nixery`: This uses an instance of [Nixery](https://nixery.dev) to run steps, which allows you to add [dependencies](#dependencies) from [Nixpkgs](https://github.com/NixOS/nixpkgs). You can search for packages on https://search.nixos.org, and there's a pretty good chance the package(s) you're looking for will be there.
39
+
40
+
Example:
41
+
42
+
```yaml
16
43
engine: "nixery"
44
+
```
45
+
46
+
## Clone options
17
47
18
-
# optional
48
+
When a workflow starts, the first step is to clone the repository. You can customize this behavior using the **optional** `clone` field. It has the following fields:
49
+
50
+
- `skip`: Setting this to `true` will skip cloning the repository. This can be useful if your workflow is doing something that doesn't require anything from the repository itself. This is `false` by default.
51
+
- `depth`: This sets the number of commits, or the "clone depth", to fetch from the repository. For example, if you set this to 2, the last 2 commits will be fetched. By default, the depth is set to 1, meaning only the most recent commit will be fetched, which is the commit that triggered the workflow.
52
+
- `submodules`: If you use [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) in your repository, setting this field to `true` will recursively fetch all submodules. This is `false` by default.
53
+
54
+
The default settings are:
55
+
56
+
```yaml
19
57
clone:
20
58
skip: false
21
-
depth: 50
22
-
submodules: true
59
+
depth: 1
60
+
submodules: false
23
61
```
24
62
25
-
The `when` and `engine` fields are required, while every other aspect
26
-
of how the definition is parsed is up to the engine. Currently, a spindle
27
-
provides at least one of these built-in engines:
63
+
## Dependencies
28
64
29
-
## `nixery`
65
+
Usually when you're running a workflow, you'll need additional dependencies. The `dependencies` field lets you define which dependencies to get, and from where. It's a key-value map, with the key being the registry to fetch dependencies from, and the value being the list of dependencies to fetch.
30
66
31
-
The Nixery engine uses an instance of [Nixery](https://nixery.dev) to run
32
-
steps that use dependencies from [Nixpkgs](https://github.com/NixOS/nixpkgs).
33
-
34
-
Here's an example that uses all fields:
67
+
Say you want to fetch Node.js and Go from `nixpkgs`, and a package called `my_pkg` you've made from your own registry at your repository at `https://tangled.sh/@example.com/my_pkg`. You can define those dependencies like so:
35
68
36
69
```yaml
37
-
# build_and_test.yaml
38
-
when:
39
-
- event: ["push", "pull_request"]
40
-
branch: ["main", "develop"]
41
-
- event: ["manual"]
42
-
43
70
dependencies:
44
-
## from nixpkgs
71
+
# nixpkgs
45
72
nixpkgs:
46
73
- nodejs
47
-
## custom registry
48
-
git+https://tangled.sh/@oppi.li/statix:
49
-
- statix
74
+
- go
75
+
# custom registry
76
+
git+https://tangled.sh/@example.com/my_pkg:
77
+
- my_pkg
78
+
```
50
79
51
-
steps:
52
-
- name: "Install dependencies"
53
-
command: "npm install"
54
-
environment:
55
-
NODE_ENV: "development"
56
-
CI: "true"
80
+
Now these dependencies are available to use in your workflow!
57
81
58
-
- name: "Run linter"
59
-
command: "npm run lint"
82
+
## Environment
83
+
84
+
The `environment` field allows you define environment variables that will be available throughout the entire workflow. **Do not put secrets here, these environment variables are visible to anyone viewing the repository. You can add secrets for pipelines in your repository's settings.**
85
+
86
+
Example:
87
+
88
+
```yaml
89
+
environment:
90
+
GOOS: "linux"
91
+
GOARCH: "arm64"
92
+
NODE_ENV: "production"
93
+
MY_ENV_VAR: "MY_ENV_VALUE"
94
+
```
60
95
61
-
- name: "Run tests"
62
-
command: "npm test"
63
-
environment:
64
-
NODE_ENV: "test"
65
-
JEST_WORKERS: "2"
96
+
## Steps
66
97
67
-
- name: "Build application"
98
+
The `steps` field allows you to define what steps should run in the workflow. It's a list of step objects, each with the following fields:
99
+
100
+
- `name`: This field allows you to give your step a name. This name is visible in your workflow runs, and is used to describe what the step is doing.
101
+
- `command`: This field allows you to define a command to run in that step. The step is run in a Bash shell, and the logs from the command will be visible in the pipelines page on the Tangled website. The [dependencies](#dependencies) you added will be available to use here.
102
+
- `environment`: Similar to the global [environment](#environment) config, this **optional** field is a key-value map that allows you to set environment variables for the step. **Do not put secrets here, these environment variables are visible to anyone viewing the repository. You can add secrets for pipelines in your repository's settings.**
103
+
104
+
Example:
105
+
106
+
```yaml
107
+
steps:
108
+
- name: "Build backend"
109
+
command: "go build"
110
+
environment:
111
+
GOOS: "darwin"
112
+
GOARCH: "arm64"
113
+
- name: "Build frontend"
68
114
command: "npm run build"
69
115
environment:
70
116
NODE_ENV: "production"
117
+
```
71
118
72
-
environment:
73
-
BUILD_NUMBER: "123"
74
-
GIT_BRANCH: "main"
119
+
## Complete workflow
75
120
76
-
## current repository is cloned and checked out at the target ref
77
-
## by default.
121
+
```yaml
122
+
# .tangled/workflows/build.yml
123
+
124
+
when:
125
+
- event: ["push", "manual"]
126
+
branch: ["main", "develop"]
127
+
- event: ["pull_request"]
128
+
branch: ["main"]
129
+
130
+
engine: "nixery"
131
+
132
+
# using the default values
78
133
clone:
79
134
skip: false
80
-
depth: 50
81
-
submodules: true
82
-
```
135
+
depth: 1
136
+
submodules: false
83
137
84
-
## git push options
138
+
dependencies:
139
+
# nixpkgs
140
+
nixpkgs:
141
+
- nodejs
142
+
- go
143
+
# custom registry
144
+
git+https://tangled.sh/@example.com/my_pkg:
145
+
- my_pkg
85
146
86
-
These are push options that can be used with the `--push-option (-o)` flag of git push:
147
+
environment:
148
+
GOOS: "linux"
149
+
GOARCH: "arm64"
150
+
NODE_ENV: "production"
151
+
MY_ENV_VAR: "MY_ENV_VALUE"
152
+
153
+
steps:
154
+
- name: "Build backend"
155
+
command: "go build"
156
+
environment:
157
+
GOOS: "darwin"
158
+
GOARCH: "arm64"
159
+
- name: "Build frontend"
160
+
command: "npm run build"
161
+
environment:
162
+
NODE_ENV: "production"
163
+
```
87
164
88
-
- `verbose-ci`, `ci-verbose`: enables diagnostics reporting for the CI pipeline, allowing you to see any issues when you push.
89
-
- `skip-ci`, `ci-skip`: skips triggering the CI pipeline.
165
+
If you want another example of a workflow, you can look at the one [Tangled uses to build the project](https://tangled.sh/@tangled.sh/core/blob/master/.tangled/workflows/build.yml).
+3
-1
go.mod
+3
-1
go.mod
···
39
39
github.com/stretchr/testify v1.10.0
40
40
github.com/urfave/cli/v3 v3.3.3
41
41
github.com/whyrusleeping/cbor-gen v0.3.1
42
-
github.com/yuin/goldmark v1.4.15
42
+
github.com/wyatt915/goldmark-treeblood v0.0.0-20250825231212-5dcbdb2f4b57
43
+
github.com/yuin/goldmark v1.7.12
43
44
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
44
45
golang.org/x/crypto v0.40.0
45
46
golang.org/x/net v0.42.0
···
154
155
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
155
156
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
156
157
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
158
+
github.com/wyatt915/treeblood v0.1.15 // indirect
157
159
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
158
160
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
159
161
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+6
-1
go.sum
+6
-1
go.sum
···
426
426
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
427
427
github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0=
428
428
github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
429
+
github.com/wyatt915/goldmark-treeblood v0.0.0-20250825231212-5dcbdb2f4b57 h1:UqtQdzLXnvdBdqn/go53qGyncw1wJ7Mq5SQdieM1/Ew=
430
+
github.com/wyatt915/goldmark-treeblood v0.0.0-20250825231212-5dcbdb2f4b57/go.mod h1:BxSCWByWSRSuembL3cDG1IBUbkBoO/oW/6tF19aA4hs=
431
+
github.com/wyatt915/treeblood v0.1.15 h1:3KZ3o2LpcKZAzOLqMoW9qeUzKEaKArKpbcPpTkNfQC8=
432
+
github.com/wyatt915/treeblood v0.1.15/go.mod h1:i7+yhhmzdDP17/97pIsOSffw74EK/xk+qJ0029cSXUY=
429
433
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
430
434
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
431
435
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
432
436
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
433
437
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
434
-
github.com/yuin/goldmark v1.4.15 h1:CFa84T0goNn/UIXYS+dmjjVxMyTAvpOmzld40N/nfK0=
435
438
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
439
+
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
440
+
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
436
441
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
437
442
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
438
443
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
+9
-10
knotserver/git/post_receive.go
+9
-10
knotserver/git/post_receive.go
···
145
145
}
146
146
147
147
func (m RefUpdateMeta) AsRecord() tangled.GitRefUpdate_Meta {
148
-
var byEmail []*tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem
148
+
var byEmail []*tangled.GitRefUpdate_IndividualEmailCommitCount
149
149
for e, v := range m.CommitCount.ByEmail {
150
-
byEmail = append(byEmail, &tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem{
150
+
byEmail = append(byEmail, &tangled.GitRefUpdate_IndividualEmailCommitCount{
151
151
Email: e,
152
152
Count: int64(v),
153
153
})
154
154
}
155
155
156
-
var langs []*tangled.GitRefUpdate_Pair
156
+
var langs []*tangled.GitRefUpdate_IndividualLanguageSize
157
157
for lang, size := range m.LangBreakdown {
158
-
langs = append(langs, &tangled.GitRefUpdate_Pair{
158
+
langs = append(langs, &tangled.GitRefUpdate_IndividualLanguageSize{
159
159
Lang: lang,
160
160
Size: size,
161
161
})
162
162
}
163
-
langBreakdown := &tangled.GitRefUpdate_Meta_LangBreakdown{
164
-
Inputs: langs,
165
-
}
166
163
167
164
return tangled.GitRefUpdate_Meta{
168
-
CommitCount: &tangled.GitRefUpdate_Meta_CommitCount{
165
+
CommitCount: &tangled.GitRefUpdate_CommitCountBreakdown{
169
166
ByEmail: byEmail,
170
167
},
171
-
IsDefaultRef: m.IsDefaultRef,
172
-
LangBreakdown: langBreakdown,
168
+
IsDefaultRef: m.IsDefaultRef,
169
+
LangBreakdown: &tangled.GitRefUpdate_LangBreakdown{
170
+
Inputs: langs,
171
+
},
173
172
}
174
173
}
+4
-4
knotserver/ingester.go
+4
-4
knotserver/ingester.go
···
98
98
l := log.FromContext(ctx)
99
99
l = l.With("handler", "processPull")
100
100
l = l.With("did", did)
101
-
l = l.With("target_repo", record.TargetRepo)
102
-
l = l.With("target_branch", record.TargetBranch)
101
+
l = l.With("target_repo", record.Target.Repo)
102
+
l = l.With("target_branch", record.Target.Branch)
103
103
104
104
if record.Source == nil {
105
105
return fmt.Errorf("ignoring pull record: not a branch-based pull request")
···
109
109
return fmt.Errorf("ignoring pull record: fork based pull")
110
110
}
111
111
112
-
repoAt, err := syntax.ParseATURI(record.TargetRepo)
112
+
repoAt, err := syntax.ParseATURI(record.Target.Repo)
113
113
if err != nil {
114
114
return fmt.Errorf("failed to parse ATURI: %w", err)
115
115
}
···
178
178
Action: "create",
179
179
SourceBranch: record.Source.Branch,
180
180
SourceSha: record.Source.Sha,
181
-
TargetBranch: record.TargetBranch,
181
+
TargetBranch: record.Target.Branch,
182
182
}
183
183
184
184
compiler := workflow.Compiler{
+59
-52
lexicons/git/refUpdate.json
+59
-52
lexicons/git/refUpdate.json
···
51
51
"maxLength": 40
52
52
},
53
53
"meta": {
54
-
"type": "object",
55
-
"required": [
56
-
"isDefaultRef",
57
-
"commitCount"
58
-
],
59
-
"properties": {
60
-
"isDefaultRef": {
61
-
"type": "boolean",
62
-
"default": "false"
63
-
},
64
-
"langBreakdown": {
65
-
"type": "object",
66
-
"properties": {
67
-
"inputs": {
68
-
"type": "array",
69
-
"items": {
70
-
"type": "ref",
71
-
"ref": "#pair"
72
-
}
73
-
}
74
-
}
75
-
},
76
-
"commitCount": {
77
-
"type": "object",
78
-
"required": [],
79
-
"properties": {
80
-
"byEmail": {
81
-
"type": "array",
82
-
"items": {
83
-
"type": "object",
84
-
"required": [
85
-
"email",
86
-
"count"
87
-
],
88
-
"properties": {
89
-
"email": {
90
-
"type": "string"
91
-
},
92
-
"count": {
93
-
"type": "integer"
94
-
}
95
-
}
96
-
}
97
-
}
98
-
}
99
-
}
100
-
}
54
+
"type": "ref",
55
+
"ref": "#meta"
56
+
}
57
+
}
58
+
}
59
+
},
60
+
"meta": {
61
+
"type": "object",
62
+
"required": ["isDefaultRef", "commitCount"],
63
+
"properties": {
64
+
"isDefaultRef": {
65
+
"type": "boolean",
66
+
"default": false
67
+
},
68
+
"langBreakdown": {
69
+
"type": "ref",
70
+
"ref": "#langBreakdown"
71
+
},
72
+
"commitCount": {
73
+
"type": "ref",
74
+
"ref": "#commitCountBreakdown"
75
+
}
76
+
}
77
+
},
78
+
"langBreakdown": {
79
+
"type": "object",
80
+
"properties": {
81
+
"inputs": {
82
+
"type": "array",
83
+
"items": {
84
+
"type": "ref",
85
+
"ref": "#individualLanguageSize"
101
86
}
102
87
}
103
88
}
104
89
},
105
-
"pair": {
90
+
"individualLanguageSize": {
106
91
"type": "object",
107
-
"required": [
108
-
"lang",
109
-
"size"
110
-
],
92
+
"required": ["lang", "size"],
111
93
"properties": {
112
94
"lang": {
113
95
"type": "string"
114
96
},
115
97
"size": {
98
+
"type": "integer"
99
+
}
100
+
}
101
+
},
102
+
"commitCountBreakdown": {
103
+
"type": "object",
104
+
"required": [],
105
+
"properties": {
106
+
"byEmail": {
107
+
"type": "array",
108
+
"items": {
109
+
"type": "ref",
110
+
"ref": "#individualEmailCommitCount"
111
+
}
112
+
}
113
+
}
114
+
},
115
+
"individualEmailCommitCount": {
116
+
"type": "object",
117
+
"required": ["email", "count"],
118
+
"properties": {
119
+
"email": {
120
+
"type": "string"
121
+
},
122
+
"count": {
116
123
"type": "integer"
117
124
}
118
125
}
+1
-8
lexicons/issue/comment.json
+1
-8
lexicons/issue/comment.json
···
9
9
"key": "tid",
10
10
"record": {
11
11
"type": "object",
12
-
"required": [
13
-
"issue",
14
-
"body",
15
-
"createdAt"
16
-
],
12
+
"required": ["issue", "body", "createdAt"],
17
13
"properties": {
18
14
"issue": {
19
15
"type": "string",
···
22
18
"repo": {
23
19
"type": "string",
24
20
"format": "at-uri"
25
-
},
26
-
"commentId": {
27
-
"type": "integer"
28
21
},
29
22
"owner": {
30
23
"type": "string",
+1
-14
lexicons/issue/issue.json
+1
-14
lexicons/issue/issue.json
···
9
9
"key": "tid",
10
10
"record": {
11
11
"type": "object",
12
-
"required": [
13
-
"repo",
14
-
"issueId",
15
-
"owner",
16
-
"title",
17
-
"createdAt"
18
-
],
12
+
"required": ["repo", "title", "createdAt"],
19
13
"properties": {
20
14
"repo": {
21
15
"type": "string",
22
16
"format": "at-uri"
23
-
},
24
-
"issueId": {
25
-
"type": "integer"
26
-
},
27
-
"owner": {
28
-
"type": "string",
29
-
"format": "did"
30
17
},
31
18
"title": {
32
19
"type": "string"
-11
lexicons/pulls/comment.json
-11
lexicons/pulls/comment.json
+20
-12
lexicons/pulls/pull.json
+20
-12
lexicons/pulls/pull.json
···
10
10
"record": {
11
11
"type": "object",
12
12
"required": [
13
-
"targetRepo",
14
-
"targetBranch",
15
-
"pullId",
13
+
"target",
16
14
"title",
17
15
"patch",
18
16
"createdAt"
19
17
],
20
18
"properties": {
21
-
"targetRepo": {
22
-
"type": "string",
23
-
"format": "at-uri"
24
-
},
25
-
"targetBranch": {
26
-
"type": "string"
27
-
},
28
-
"pullId": {
29
-
"type": "integer"
19
+
"target": {
20
+
"type": "ref",
21
+
"ref": "#target"
30
22
},
31
23
"title": {
32
24
"type": "string"
···
45
37
"type": "string",
46
38
"format": "datetime"
47
39
}
40
+
}
41
+
}
42
+
},
43
+
"target": {
44
+
"type": "object",
45
+
"required": [
46
+
"repo",
47
+
"branch"
48
+
],
49
+
"properties": {
50
+
"repo": {
51
+
"type": "string",
52
+
"format": "at-uri"
53
+
},
54
+
"branch": {
55
+
"type": "string"
48
56
}
49
57
}
50
58
},
+16
nix/modules/spindle.nix
+16
nix/modules/spindle.nix
···
55
55
description = "DID of owner (required)";
56
56
};
57
57
58
+
maxJobCount = mkOption {
59
+
type = types.int;
60
+
default = 2;
61
+
example = 5;
62
+
description = "Maximum number of concurrent jobs to run";
63
+
};
64
+
65
+
queueSize = mkOption {
66
+
type = types.int;
67
+
default = 100;
68
+
example = 100;
69
+
description = "Maximum number of jobs queue up";
70
+
};
71
+
58
72
secrets = {
59
73
provider = mkOption {
60
74
type = types.str;
···
108
122
"SPINDLE_SERVER_JETSTREAM=${cfg.server.jetstreamEndpoint}"
109
123
"SPINDLE_SERVER_DEV=${lib.boolToString cfg.server.dev}"
110
124
"SPINDLE_SERVER_OWNER=${cfg.server.owner}"
125
+
"SPINDLE_SERVER_MAX_JOB_COUNT=${toString cfg.server.maxJobCount}"
126
+
"SPINDLE_SERVER_QUEUE_SIZE=${toString cfg.server.queueSize}"
111
127
"SPINDLE_SERVER_SECRETS_PROVIDER=${cfg.server.secrets.provider}"
112
128
"SPINDLE_SERVER_SECRETS_OPENBAO_PROXY_ADDR=${cfg.server.secrets.openbao.proxyAddr}"
113
129
"SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT=${cfg.server.secrets.openbao.mount}"
+2
nix/vm.nix
+2
nix/vm.nix
+2
spindle/config/config.go
+2
spindle/config/config.go
···
17
17
Owner string `env:"OWNER, required"`
18
18
Secrets Secrets `env:",prefix=SECRETS_"`
19
19
LogDir string `env:"LOG_DIR, default=/var/log/spindle"`
20
+
QueueSize int `env:"QUEUE_SIZE, default=100"`
21
+
MaxJobCount int `env:"MAX_JOB_COUNT, default=2"` // max number of jobs that run at a time
20
22
}
21
23
22
24
func (s Server) Did() syntax.DID {
+2
-1
spindle/server.go
+2
-1
spindle/server.go
···
100
100
return err
101
101
}
102
102
103
-
jq := queue.NewQueue(100, 5)
103
+
jq := queue.NewQueue(cfg.Server.QueueSize, cfg.Server.MaxJobCount)
104
+
logger.Info("initialized queue", "queueSize", cfg.Server.QueueSize, "numWorkers", cfg.Server.MaxJobCount)
104
105
105
106
collections := []string{
106
107
tangled.SpindleMemberNSID,