-416
api/tangled/cbor_gen.go
-416
api/tangled/cbor_gen.go
···
561
561
562
562
return nil
563
563
}
564
-
func (t *Comment) MarshalCBOR(w io.Writer) error {
565
-
if t == nil {
566
-
_, err := w.Write(cbg.CborNull)
567
-
return err
568
-
}
569
-
570
-
cw := cbg.NewCborWriter(w)
571
-
fieldCount := 7
572
-
573
-
if t.Mentions == nil {
574
-
fieldCount--
575
-
}
576
-
577
-
if t.References == nil {
578
-
fieldCount--
579
-
}
580
-
581
-
if t.ReplyTo == nil {
582
-
fieldCount--
583
-
}
584
-
585
-
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
586
-
return err
587
-
}
588
-
589
-
// t.Body (string) (string)
590
-
if len("body") > 1000000 {
591
-
return xerrors.Errorf("Value in field \"body\" was too long")
592
-
}
593
-
594
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("body"))); err != nil {
595
-
return err
596
-
}
597
-
if _, err := cw.WriteString(string("body")); err != nil {
598
-
return err
599
-
}
600
-
601
-
if len(t.Body) > 1000000 {
602
-
return xerrors.Errorf("Value in field t.Body was too long")
603
-
}
604
-
605
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Body))); err != nil {
606
-
return err
607
-
}
608
-
if _, err := cw.WriteString(string(t.Body)); err != nil {
609
-
return err
610
-
}
611
-
612
-
// t.LexiconTypeID (string) (string)
613
-
if len("$type") > 1000000 {
614
-
return xerrors.Errorf("Value in field \"$type\" was too long")
615
-
}
616
-
617
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("$type"))); err != nil {
618
-
return err
619
-
}
620
-
if _, err := cw.WriteString(string("$type")); err != nil {
621
-
return err
622
-
}
623
-
624
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.comment"))); err != nil {
625
-
return err
626
-
}
627
-
if _, err := cw.WriteString(string("sh.tangled.comment")); err != nil {
628
-
return err
629
-
}
630
-
631
-
// t.ReplyTo (string) (string)
632
-
if t.ReplyTo != nil {
633
-
634
-
if len("replyTo") > 1000000 {
635
-
return xerrors.Errorf("Value in field \"replyTo\" was too long")
636
-
}
637
-
638
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("replyTo"))); err != nil {
639
-
return err
640
-
}
641
-
if _, err := cw.WriteString(string("replyTo")); err != nil {
642
-
return err
643
-
}
644
-
645
-
if t.ReplyTo == nil {
646
-
if _, err := cw.Write(cbg.CborNull); err != nil {
647
-
return err
648
-
}
649
-
} else {
650
-
if len(*t.ReplyTo) > 1000000 {
651
-
return xerrors.Errorf("Value in field t.ReplyTo was too long")
652
-
}
653
-
654
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.ReplyTo))); err != nil {
655
-
return err
656
-
}
657
-
if _, err := cw.WriteString(string(*t.ReplyTo)); err != nil {
658
-
return err
659
-
}
660
-
}
661
-
}
662
-
663
-
// t.Subject (string) (string)
664
-
if len("subject") > 1000000 {
665
-
return xerrors.Errorf("Value in field \"subject\" was too long")
666
-
}
667
-
668
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("subject"))); err != nil {
669
-
return err
670
-
}
671
-
if _, err := cw.WriteString(string("subject")); err != nil {
672
-
return err
673
-
}
674
-
675
-
if len(t.Subject) > 1000000 {
676
-
return xerrors.Errorf("Value in field t.Subject was too long")
677
-
}
678
-
679
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Subject))); err != nil {
680
-
return err
681
-
}
682
-
if _, err := cw.WriteString(string(t.Subject)); err != nil {
683
-
return err
684
-
}
685
-
686
-
// t.Mentions ([]string) (slice)
687
-
if t.Mentions != nil {
688
-
689
-
if len("mentions") > 1000000 {
690
-
return xerrors.Errorf("Value in field \"mentions\" was too long")
691
-
}
692
-
693
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("mentions"))); err != nil {
694
-
return err
695
-
}
696
-
if _, err := cw.WriteString(string("mentions")); err != nil {
697
-
return err
698
-
}
699
-
700
-
if len(t.Mentions) > 8192 {
701
-
return xerrors.Errorf("Slice value in field t.Mentions was too long")
702
-
}
703
-
704
-
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Mentions))); err != nil {
705
-
return err
706
-
}
707
-
for _, v := range t.Mentions {
708
-
if len(v) > 1000000 {
709
-
return xerrors.Errorf("Value in field v was too long")
710
-
}
711
-
712
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
713
-
return err
714
-
}
715
-
if _, err := cw.WriteString(string(v)); err != nil {
716
-
return err
717
-
}
718
-
719
-
}
720
-
}
721
-
722
-
// t.CreatedAt (string) (string)
723
-
if len("createdAt") > 1000000 {
724
-
return xerrors.Errorf("Value in field \"createdAt\" was too long")
725
-
}
726
-
727
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("createdAt"))); err != nil {
728
-
return err
729
-
}
730
-
if _, err := cw.WriteString(string("createdAt")); err != nil {
731
-
return err
732
-
}
733
-
734
-
if len(t.CreatedAt) > 1000000 {
735
-
return xerrors.Errorf("Value in field t.CreatedAt was too long")
736
-
}
737
-
738
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.CreatedAt))); err != nil {
739
-
return err
740
-
}
741
-
if _, err := cw.WriteString(string(t.CreatedAt)); err != nil {
742
-
return err
743
-
}
744
-
745
-
// t.References ([]string) (slice)
746
-
if t.References != nil {
747
-
748
-
if len("references") > 1000000 {
749
-
return xerrors.Errorf("Value in field \"references\" was too long")
750
-
}
751
-
752
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("references"))); err != nil {
753
-
return err
754
-
}
755
-
if _, err := cw.WriteString(string("references")); err != nil {
756
-
return err
757
-
}
758
-
759
-
if len(t.References) > 8192 {
760
-
return xerrors.Errorf("Slice value in field t.References was too long")
761
-
}
762
-
763
-
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.References))); err != nil {
764
-
return err
765
-
}
766
-
for _, v := range t.References {
767
-
if len(v) > 1000000 {
768
-
return xerrors.Errorf("Value in field v was too long")
769
-
}
770
-
771
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
772
-
return err
773
-
}
774
-
if _, err := cw.WriteString(string(v)); err != nil {
775
-
return err
776
-
}
777
-
778
-
}
779
-
}
780
-
return nil
781
-
}
782
-
783
-
func (t *Comment) UnmarshalCBOR(r io.Reader) (err error) {
784
-
*t = Comment{}
785
-
786
-
cr := cbg.NewCborReader(r)
787
-
788
-
maj, extra, err := cr.ReadHeader()
789
-
if err != nil {
790
-
return err
791
-
}
792
-
defer func() {
793
-
if err == io.EOF {
794
-
err = io.ErrUnexpectedEOF
795
-
}
796
-
}()
797
-
798
-
if maj != cbg.MajMap {
799
-
return fmt.Errorf("cbor input should be of type map")
800
-
}
801
-
802
-
if extra > cbg.MaxLength {
803
-
return fmt.Errorf("Comment: map struct too large (%d)", extra)
804
-
}
805
-
806
-
n := extra
807
-
808
-
nameBuf := make([]byte, 10)
809
-
for i := uint64(0); i < n; i++ {
810
-
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
811
-
if err != nil {
812
-
return err
813
-
}
814
-
815
-
if !ok {
816
-
// Field doesn't exist on this type, so ignore it
817
-
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
818
-
return err
819
-
}
820
-
continue
821
-
}
822
-
823
-
switch string(nameBuf[:nameLen]) {
824
-
// t.Body (string) (string)
825
-
case "body":
826
-
827
-
{
828
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
829
-
if err != nil {
830
-
return err
831
-
}
832
-
833
-
t.Body = string(sval)
834
-
}
835
-
// t.LexiconTypeID (string) (string)
836
-
case "$type":
837
-
838
-
{
839
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
840
-
if err != nil {
841
-
return err
842
-
}
843
-
844
-
t.LexiconTypeID = string(sval)
845
-
}
846
-
// t.ReplyTo (string) (string)
847
-
case "replyTo":
848
-
849
-
{
850
-
b, err := cr.ReadByte()
851
-
if err != nil {
852
-
return err
853
-
}
854
-
if b != cbg.CborNull[0] {
855
-
if err := cr.UnreadByte(); err != nil {
856
-
return err
857
-
}
858
-
859
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
860
-
if err != nil {
861
-
return err
862
-
}
863
-
864
-
t.ReplyTo = (*string)(&sval)
865
-
}
866
-
}
867
-
// t.Subject (string) (string)
868
-
case "subject":
869
-
870
-
{
871
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
872
-
if err != nil {
873
-
return err
874
-
}
875
-
876
-
t.Subject = string(sval)
877
-
}
878
-
// t.Mentions ([]string) (slice)
879
-
case "mentions":
880
-
881
-
maj, extra, err = cr.ReadHeader()
882
-
if err != nil {
883
-
return err
884
-
}
885
-
886
-
if extra > 8192 {
887
-
return fmt.Errorf("t.Mentions: array too large (%d)", extra)
888
-
}
889
-
890
-
if maj != cbg.MajArray {
891
-
return fmt.Errorf("expected cbor array")
892
-
}
893
-
894
-
if extra > 0 {
895
-
t.Mentions = make([]string, extra)
896
-
}
897
-
898
-
for i := 0; i < int(extra); i++ {
899
-
{
900
-
var maj byte
901
-
var extra uint64
902
-
var err error
903
-
_ = maj
904
-
_ = extra
905
-
_ = err
906
-
907
-
{
908
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
909
-
if err != nil {
910
-
return err
911
-
}
912
-
913
-
t.Mentions[i] = string(sval)
914
-
}
915
-
916
-
}
917
-
}
918
-
// t.CreatedAt (string) (string)
919
-
case "createdAt":
920
-
921
-
{
922
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
923
-
if err != nil {
924
-
return err
925
-
}
926
-
927
-
t.CreatedAt = string(sval)
928
-
}
929
-
// t.References ([]string) (slice)
930
-
case "references":
931
-
932
-
maj, extra, err = cr.ReadHeader()
933
-
if err != nil {
934
-
return err
935
-
}
936
-
937
-
if extra > 8192 {
938
-
return fmt.Errorf("t.References: array too large (%d)", extra)
939
-
}
940
-
941
-
if maj != cbg.MajArray {
942
-
return fmt.Errorf("expected cbor array")
943
-
}
944
-
945
-
if extra > 0 {
946
-
t.References = make([]string, extra)
947
-
}
948
-
949
-
for i := 0; i < int(extra); i++ {
950
-
{
951
-
var maj byte
952
-
var extra uint64
953
-
var err error
954
-
_ = maj
955
-
_ = extra
956
-
_ = err
957
-
958
-
{
959
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
960
-
if err != nil {
961
-
return err
962
-
}
963
-
964
-
t.References[i] = string(sval)
965
-
}
966
-
967
-
}
968
-
}
969
-
970
-
default:
971
-
// Field doesn't exist on this type, so ignore it
972
-
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
973
-
return err
974
-
}
975
-
}
976
-
}
977
-
978
-
return nil
979
-
}
980
564
func (t *FeedReaction) MarshalCBOR(w io.Writer) error {
981
565
if t == nil {
982
566
_, err := w.Write(cbg.CborNull)
-27
api/tangled/tangledcomment.go
-27
api/tangled/tangledcomment.go
···
1
-
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
-
3
-
package tangled
4
-
5
-
// schema: sh.tangled.comment
6
-
7
-
import (
8
-
"github.com/bluesky-social/indigo/lex/util"
9
-
)
10
-
11
-
const (
12
-
CommentNSID = "sh.tangled.comment"
13
-
)
14
-
15
-
func init() {
16
-
util.RegisterType("sh.tangled.comment", &Comment{})
17
-
} //
18
-
// RECORDTYPE: Comment
19
-
type Comment struct {
20
-
LexiconTypeID string `json:"$type,const=sh.tangled.comment" cborgen:"$type,const=sh.tangled.comment"`
21
-
Body string `json:"body" cborgen:"body"`
22
-
CreatedAt string `json:"createdAt" cborgen:"createdAt"`
23
-
Mentions []string `json:"mentions,omitempty" cborgen:"mentions,omitempty"`
24
-
References []string `json:"references,omitempty" cborgen:"references,omitempty"`
25
-
ReplyTo *string `json:"replyTo,omitempty" cborgen:"replyTo,omitempty"`
26
-
Subject string `json:"subject" cborgen:"subject"`
27
-
}
+8
-9
appview/issues/issues.go
+8
-9
appview/issues/issues.go
···
129
129
}
130
130
131
131
rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
132
-
LoggedInUser: user,
133
-
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
134
-
Issue: issue,
135
-
CommentList: issue.CommentList(),
136
-
Backlinks: backlinks,
137
-
OrderedReactionKinds: models.OrderedReactionKinds,
138
-
Reactions: reactionMap,
139
-
UserReacted: userReactions,
140
-
LabelDefs: defs,
132
+
LoggedInUser: user,
133
+
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
134
+
Issue: issue,
135
+
CommentList: issue.CommentList(),
136
+
Backlinks: backlinks,
137
+
Reactions: reactionMap,
138
+
UserReacted: userReactions,
139
+
LabelDefs: defs,
141
140
})
142
141
}
143
142
+2
-2
appview/issues/opengraph.go
+2
-2
appview/issues/opengraph.go
···
193
193
dollyX := dollyBounds.Min.X + (dollyBounds.Dx() / 2) - (dollySize / 2)
194
194
dollyY := statsY + iconBaselineOffset - dollySize/2 + 25
195
195
dollyColor := color.RGBA{180, 180, 180, 255} // light gray
196
-
err = dollyArea.DrawDollySilhouette(dollyX, dollyY, dollySize, dollyColor)
196
+
err = dollyArea.DrawDolly(dollyX, dollyY, dollySize, dollyColor)
197
197
if err != nil {
198
-
log.Printf("dolly silhouette not available (this is ok): %v", err)
198
+
log.Printf("dolly not available (this is ok): %v", err)
199
199
}
200
200
201
201
// Draw "opened by @author" and date at the bottom with more spacing
-15
appview/knots/knots.go
-15
appview/knots/knots.go
···
40
40
Knotstream *eventconsumer.Consumer
41
41
}
42
42
43
-
type tab = map[string]any
44
-
45
-
var (
46
-
knotsTabs []tab = []tab{
47
-
{"Name": "profile", "Icon": "user"},
48
-
{"Name": "keys", "Icon": "key"},
49
-
{"Name": "emails", "Icon": "mail"},
50
-
{"Name": "notifications", "Icon": "bell"},
51
-
{"Name": "knots", "Icon": "volleyball"},
52
-
{"Name": "spindles", "Icon": "spool"},
53
-
}
54
-
)
55
-
56
43
func (k *Knots) Router() http.Handler {
57
44
r := chi.NewRouter()
58
45
···
84
71
k.Pages.Knots(w, pages.KnotsParams{
85
72
LoggedInUser: user,
86
73
Registrations: registrations,
87
-
Tabs: knotsTabs,
88
74
Tab: "knots",
89
75
})
90
76
}
···
148
134
Members: members,
149
135
Repos: repoMap,
150
136
IsOwner: true,
151
-
Tabs: knotsTabs,
152
137
Tab: "knots",
153
138
})
154
139
}
+9
-9
appview/ogcard/card.go
+9
-9
appview/ogcard/card.go
···
334
334
return nil
335
335
}
336
336
337
-
func (c *Card) DrawDollySilhouette(x, y, size int, iconColor color.Color) error {
337
+
func (c *Card) DrawDolly(x, y, size int, iconColor color.Color) error {
338
338
tpl, err := template.New("dolly").
339
-
ParseFS(pages.Files, "templates/fragments/dolly/silhouette.html")
339
+
ParseFS(pages.Files, "templates/fragments/dolly/logo.html")
340
340
if err != nil {
341
-
return fmt.Errorf("failed to read dolly silhouette template: %w", err)
341
+
return fmt.Errorf("failed to read dolly template: %w", err)
342
342
}
343
343
344
344
var svgData bytes.Buffer
345
-
if err = tpl.ExecuteTemplate(&svgData, "fragments/dolly/silhouette", nil); err != nil {
346
-
return fmt.Errorf("failed to execute dolly silhouette template: %w", err)
345
+
if err = tpl.ExecuteTemplate(&svgData, "fragments/dolly/logo", nil); err != nil {
346
+
return fmt.Errorf("failed to execute dolly template: %w", err)
347
347
}
348
348
349
349
icon, err := BuildSVGIconFromData(svgData.Bytes(), iconColor)
···
453
453
454
454
// Handle SVG separately
455
455
if contentType == "image/svg+xml" || strings.HasSuffix(url, ".svg") {
456
-
return c.convertSVGToPNG(bodyBytes)
456
+
return convertSVGToPNG(bodyBytes)
457
457
}
458
458
459
459
// Support content types are in-sync with the allowed custom avatar file types
···
493
493
}
494
494
495
495
// convertSVGToPNG converts SVG data to a PNG image
496
-
func (c *Card) convertSVGToPNG(svgData []byte) (image.Image, bool) {
496
+
func convertSVGToPNG(svgData []byte) (image.Image, bool) {
497
497
// Parse the SVG
498
498
icon, err := oksvg.ReadIconStream(bytes.NewReader(svgData))
499
499
if err != nil {
···
547
547
draw.CatmullRom.Scale(scaledImg, scaledImg.Bounds(), img, srcBounds, draw.Src, nil)
548
548
549
549
// Draw the image with circular clipping
550
-
for cy := 0; cy < size; cy++ {
551
-
for cx := 0; cx < size; cx++ {
550
+
for cy := range size {
551
+
for cx := range size {
552
552
// Calculate distance from center
553
553
dx := float64(cx - center)
554
554
dy := float64(cy - center)
+22
appview/pages/funcmap.go
+22
appview/pages/funcmap.go
···
32
32
"tangled.org/core/crypto"
33
33
)
34
34
35
+
type tab map[string]string
36
+
35
37
func (p *Pages) funcMap() template.FuncMap {
36
38
return template.FuncMap{
37
39
"split": func(s string) []string {
···
384
386
return "error"
385
387
}
386
388
return fp
389
+
},
390
+
// constant values used to define a template
391
+
"const": func() map[string]any {
392
+
return map[string]any{
393
+
"OrderedReactionKinds": models.OrderedReactionKinds,
394
+
// would be great to have ordered maps right about now
395
+
"UserSettingsTabs": []tab{
396
+
{"Name": "profile", "Icon": "user"},
397
+
{"Name": "keys", "Icon": "key"},
398
+
{"Name": "emails", "Icon": "mail"},
399
+
{"Name": "notifications", "Icon": "bell"},
400
+
{"Name": "knots", "Icon": "volleyball"},
401
+
{"Name": "spindles", "Icon": "spool"},
402
+
},
403
+
"RepoSettingsTabs": []tab{
404
+
{"Name": "general", "Icon": "sliders-horizontal"},
405
+
{"Name": "access", "Icon": "users"},
406
+
{"Name": "pipelines", "Icon": "layers-2"},
407
+
},
408
+
}
387
409
},
388
410
}
389
411
}
+30
-35
appview/pages/pages.go
+30
-35
appview/pages/pages.go
···
210
210
return tpl.ExecuteTemplate(w, "layouts/base", params)
211
211
}
212
212
213
+
type DollyParams struct {
214
+
Classes string
215
+
FillColor string
216
+
}
217
+
218
+
func (p *Pages) Dolly(w io.Writer, params DollyParams) error {
219
+
return p.executePlain("fragments/dolly/logo", w, params)
220
+
}
221
+
213
222
func (p *Pages) Favicon(w io.Writer) error {
214
-
return p.executePlain("fragments/dolly/silhouette", w, nil)
223
+
return p.Dolly(w, DollyParams{
224
+
Classes: "text-black dark:text-white",
225
+
})
215
226
}
216
227
217
228
type LoginParams struct {
···
325
336
326
337
type UserProfileSettingsParams struct {
327
338
LoggedInUser *oauth.User
328
-
Tabs []map[string]any
329
339
Tab string
330
340
}
331
341
···
364
374
type UserKeysSettingsParams struct {
365
375
LoggedInUser *oauth.User
366
376
PubKeys []models.PublicKey
367
-
Tabs []map[string]any
368
377
Tab string
369
378
}
370
379
···
375
384
type UserEmailsSettingsParams struct {
376
385
LoggedInUser *oauth.User
377
386
Emails []models.Email
378
-
Tabs []map[string]any
379
387
Tab string
380
388
}
381
389
···
386
394
type UserNotificationSettingsParams struct {
387
395
LoggedInUser *oauth.User
388
396
Preferences *models.NotificationPreferences
389
-
Tabs []map[string]any
390
397
Tab string
391
398
}
392
399
···
406
413
type KnotsParams struct {
407
414
LoggedInUser *oauth.User
408
415
Registrations []models.Registration
409
-
Tabs []map[string]any
410
416
Tab string
411
417
}
412
418
···
420
426
Members []string
421
427
Repos map[string][]models.Repo
422
428
IsOwner bool
423
-
Tabs []map[string]any
424
429
Tab string
425
430
}
426
431
···
439
444
type SpindlesParams struct {
440
445
LoggedInUser *oauth.User
441
446
Spindles []models.Spindle
442
-
Tabs []map[string]any
443
447
Tab string
444
448
}
445
449
···
449
453
450
454
type SpindleListingParams struct {
451
455
models.Spindle
452
-
Tabs []map[string]any
453
456
Tab string
454
457
}
455
458
···
462
465
Spindle models.Spindle
463
466
Members []string
464
467
Repos map[string][]models.Repo
465
-
Tabs []map[string]any
466
468
Tab string
467
469
}
468
470
···
870
872
SubscribedLabels map[string]struct{}
871
873
ShouldSubscribeAll bool
872
874
Active string
873
-
Tabs []map[string]any
874
875
Tab string
875
876
Branches []types.Branch
876
877
}
···
884
885
LoggedInUser *oauth.User
885
886
RepoInfo repoinfo.RepoInfo
886
887
Active string
887
-
Tabs []map[string]any
888
888
Tab string
889
889
Collaborators []Collaborator
890
890
}
···
898
898
LoggedInUser *oauth.User
899
899
RepoInfo repoinfo.RepoInfo
900
900
Active string
901
-
Tabs []map[string]any
902
901
Tab string
903
902
Spindles []string
904
903
CurrentSpindle string
···
936
935
Backlinks []models.RichReferenceLink
937
936
LabelDefs map[string]*models.LabelDefinition
938
937
939
-
OrderedReactionKinds []models.ReactionKind
940
-
Reactions map[models.ReactionKind]models.ReactionDisplayData
941
-
UserReacted map[models.ReactionKind]bool
938
+
Reactions map[models.ReactionKind]models.ReactionDisplayData
939
+
UserReacted map[models.ReactionKind]bool
942
940
}
943
941
944
942
func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error {
···
1093
1091
ResubmitCheck ResubmitResult
1094
1092
Pipelines map[string]models.Pipeline
1095
1093
1096
-
OrderedReactionKinds []models.ReactionKind
1097
-
Reactions map[models.ReactionKind]models.ReactionDisplayData
1098
-
UserReacted map[models.ReactionKind]bool
1094
+
Reactions map[models.ReactionKind]models.ReactionDisplayData
1095
+
UserReacted map[models.ReactionKind]bool
1099
1096
1100
1097
LabelDefs map[string]*models.LabelDefinition
1101
1098
}
···
1106
1103
}
1107
1104
1108
1105
type RepoPullPatchParams struct {
1109
-
LoggedInUser *oauth.User
1110
-
RepoInfo repoinfo.RepoInfo
1111
-
Pull *models.Pull
1112
-
Stack models.Stack
1113
-
Diff *types.NiceDiff
1114
-
Round int
1115
-
Submission *models.PullSubmission
1116
-
OrderedReactionKinds []models.ReactionKind
1117
-
DiffOpts types.DiffOpts
1106
+
LoggedInUser *oauth.User
1107
+
RepoInfo repoinfo.RepoInfo
1108
+
Pull *models.Pull
1109
+
Stack models.Stack
1110
+
Diff *types.NiceDiff
1111
+
Round int
1112
+
Submission *models.PullSubmission
1113
+
DiffOpts types.DiffOpts
1118
1114
}
1119
1115
1120
1116
// this name is a mouthful
···
1123
1119
}
1124
1120
1125
1121
type RepoPullInterdiffParams struct {
1126
-
LoggedInUser *oauth.User
1127
-
RepoInfo repoinfo.RepoInfo
1128
-
Pull *models.Pull
1129
-
Round int
1130
-
Interdiff *patchutil.InterdiffResult
1131
-
OrderedReactionKinds []models.ReactionKind
1132
-
DiffOpts types.DiffOpts
1122
+
LoggedInUser *oauth.User
1123
+
RepoInfo repoinfo.RepoInfo
1124
+
Pull *models.Pull
1125
+
Round int
1126
+
Interdiff *patchutil.InterdiffResult
1127
+
DiffOpts types.DiffOpts
1133
1128
}
1134
1129
1135
1130
// this name is a mouthful
+9
-29
appview/pages/templates/brand/brand.html
+9
-29
appview/pages/templates/brand/brand.html
···
4
4
<div class="grid grid-cols-10">
5
5
<header class="col-span-full md:col-span-10 px-6 py-2 mb-4">
6
6
<h1 class="text-2xl font-bold dark:text-white mb-1">Brand</h1>
7
-
<p class="text-gray-600 dark:text-gray-400 mb-1">
7
+
<p class="text-gray-500 dark:text-gray-300 mb-1">
8
8
Assets and guidelines for using Tangled's logo and brand elements.
9
9
</p>
10
10
</header>
···
14
14
15
15
<!-- Introduction Section -->
16
16
<section>
17
-
<p class="text-gray-600 dark:text-gray-400 mb-2">
17
+
<p class="text-gray-500 dark:text-gray-300 mb-2">
18
18
Tangled's logo and mascot is <strong>Dolly</strong>, the first ever <em>cloned</em> mammal. Please
19
19
follow the below guidelines when using Dolly and the logotype.
20
20
</p>
21
-
<p class="text-gray-600 dark:text-gray-400 mb-2">
21
+
<p class="text-gray-500 dark:text-gray-300 mb-2">
22
22
All assets are served as SVGs, and can be downloaded by right-clicking and clicking "Save image as".
23
23
</p>
24
24
</section>
···
34
34
</div>
35
35
<div class="order-1 lg:order-2">
36
36
<h2 class="text-xl font-semibold dark:text-white mb-3">Black logotype</h2>
37
-
<p class="text-gray-600 dark:text-gray-400 mb-4">For use on light-colored backgrounds.</p>
37
+
<p class="text-gray-500 dark:text-gray-300 mb-4">For use on light-colored backgrounds.</p>
38
38
<p class="text-gray-700 dark:text-gray-300">
39
39
This is the preferred version of the logotype, featuring dark text and elements, ideal for light
40
40
backgrounds and designs.
···
53
53
</div>
54
54
<div class="order-1 lg:order-2">
55
55
<h2 class="text-xl font-semibold dark:text-white mb-3">White logotype</h2>
56
-
<p class="text-gray-600 dark:text-gray-400 mb-4">For use on dark-colored backgrounds.</p>
56
+
<p class="text-gray-500 dark:text-gray-300 mb-4">For use on dark-colored backgrounds.</p>
57
57
<p class="text-gray-700 dark:text-gray-300">
58
58
This version features white text and elements, ideal for dark backgrounds
59
59
and inverted designs.
···
81
81
</div>
82
82
<div class="order-1 lg:order-2">
83
83
<h2 class="text-xl font-semibold dark:text-white mb-3">Mark only</h2>
84
-
<p class="text-gray-600 dark:text-gray-400 mb-4">
84
+
<p class="text-gray-500 dark:text-gray-300 mb-4">
85
85
When a smaller 1:1 logo or icon is needed, Dolly's face may be used on its own.
86
86
</p>
87
87
<p class="text-gray-700 dark:text-gray-300 mb-4">
···
123
123
</div>
124
124
<div class="order-1 lg:order-2">
125
125
<h2 class="text-xl font-semibold dark:text-white mb-3">Colored backgrounds</h2>
126
-
<p class="text-gray-600 dark:text-gray-400 mb-4">
126
+
<p class="text-gray-500 dark:text-gray-300 mb-4">
127
127
White logo mark on colored backgrounds.
128
128
</p>
129
129
<p class="text-gray-700 dark:text-gray-300 mb-4">
···
165
165
</div>
166
166
<div class="order-1 lg:order-2">
167
167
<h2 class="text-xl font-semibold dark:text-white mb-3">Lighter backgrounds</h2>
168
-
<p class="text-gray-600 dark:text-gray-400 mb-4">
168
+
<p class="text-gray-500 dark:text-gray-300 mb-4">
169
169
Dark logo mark on lighter, pastel backgrounds.
170
170
</p>
171
171
<p class="text-gray-700 dark:text-gray-300 mb-4">
···
186
186
</div>
187
187
<div class="order-1 lg:order-2">
188
188
<h2 class="text-xl font-semibold dark:text-white mb-3">Recoloring</h2>
189
-
<p class="text-gray-600 dark:text-gray-400 mb-4">
189
+
<p class="text-gray-500 dark:text-gray-300 mb-4">
190
190
Custom coloring of the logotype is permitted.
191
191
</p>
192
192
<p class="text-gray-700 dark:text-gray-300 mb-4">
···
194
194
</p>
195
195
<p class="text-gray-700 dark:text-gray-300 text-sm">
196
196
<strong>Example:</strong> Gray/sand colored logotype on a light yellow/tan background.
197
-
</p>
198
-
</div>
199
-
</section>
200
-
201
-
<!-- Silhouette Section -->
202
-
<section class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
203
-
<div class="order-2 lg:order-1">
204
-
<div class="border border-gray-200 dark:border-gray-700 p-8 sm:p-16 bg-gray-50 dark:bg-gray-100 rounded">
205
-
<img src="https://assets.tangled.network/tangled_dolly_silhouette.svg"
206
-
alt="Dolly silhouette"
207
-
class="w-full max-w-32 mx-auto" />
208
-
</div>
209
-
</div>
210
-
<div class="order-1 lg:order-2">
211
-
<h2 class="text-xl font-semibold dark:text-white mb-3">Dolly silhouette</h2>
212
-
<p class="text-gray-600 dark:text-gray-400 mb-4">A minimalist version of Dolly.</p>
213
-
<p class="text-gray-700 dark:text-gray-300">
214
-
The silhouette can be used where a subtle brand presence is needed,
215
-
or as a background element. Works on any background color with proper contrast.
216
-
For example, we use this as the site's favicon.
217
197
</p>
218
198
</div>
219
199
</section>
+14
-2
appview/pages/templates/fragments/dolly/logo.html
+14
-2
appview/pages/templates/fragments/dolly/logo.html
···
2
2
<svg
3
3
version="1.1"
4
4
id="svg1"
5
-
class="{{ . }}"
5
+
class="{{ .Classes }}"
6
6
width="25"
7
7
height="25"
8
8
viewBox="0 0 25 25"
···
17
17
xmlns:svg="http://www.w3.org/2000/svg"
18
18
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
19
19
xmlns:cc="http://creativecommons.org/ns#">
20
+
<style>
21
+
.dolly {
22
+
color: #000000;
23
+
}
24
+
25
+
@media (prefers-color-scheme: dark) {
26
+
.dolly {
27
+
color: #ffffff;
28
+
}
29
+
}
30
+
</style>
20
31
<sodipodi:namedview
21
32
id="namedview1"
22
33
pagecolor="#ffffff"
···
51
62
id="g1"
52
63
transform="translate(-0.42924038,-0.87777209)">
53
64
<path
54
-
fill="currentColor"
65
+
class="dolly"
66
+
fill="{{ or .FillColor "currentColor" }}"
55
67
style="stroke-width:0.111183;"
56
68
d="m 16.775491,24.987061 c -0.78517,-0.0064 -1.384202,-0.234614 -2.033994,-0.631295 -0.931792,-0.490188 -1.643475,-1.31368 -2.152014,-2.221647 C 11.781409,23.136647 10.701392,23.744942 9.4922931,24.0886 8.9774725,24.238111 8.0757679,24.389777 6.5811304,23.84827 4.4270703,23.124679 2.8580086,20.883331 3.0363279,18.599583 3.0037061,17.652919 3.3488675,16.723769 3.8381157,15.925061 2.5329485,15.224503 1.4686756,14.048584 1.0611184,12.606459 0.81344502,11.816973 0.82385989,10.966486 0.91519098,10.154906 1.2422711,8.2387903 2.6795811,6.5725716 4.5299585,5.9732484 5.2685364,4.290122 6.8802592,3.0349975 8.706276,2.7794663 c 1.2124148,-0.1688264 2.46744,0.084987 3.52811,0.7011837 1.545426,-1.7139736 4.237779,-2.2205077 6.293579,-1.1676231 1.568222,0.7488935 2.689625,2.3113526 2.961888,4.0151464 1.492195,0.5977882 2.749007,1.8168898 3.242225,3.3644951 0.329805,0.9581836 0.340709,2.0135956 0.127128,2.9974286 -0.381606,1.535184 -1.465322,2.842146 -2.868035,3.556463 0.0034,0.273204 0.901506,2.243045 0.751284,3.729647 -0.03281,1.858525 -1.211631,3.619894 -2.846433,4.475452 -0.953967,0.556812 -2.084452,0.546309 -3.120531,0.535398 z m -4.470079,-5.349839 c 1.322246,-0.147248 2.189053,-1.300106 2.862307,-2.338363 0.318287,-0.472954 0.561404,-1.002348 0.803,-1.505815 0.313265,0.287151 0.578698,0.828085 1.074141,0.956909 0.521892,0.162542 1.133743,0.03052 1.45325,-0.443554 0.611414,-1.140449 0.31004,-2.516537 -0.04602,-3.698347 C 18.232844,11.92927 17.945151,11.232927 17.397785,10.751793 17.514522,9.9283111 17.026575,9.0919791 16.332883,8.6609491 15.741721,9.1323278 14.842258,9.1294949 14.271975,8.6252369 13.178927,9.7400102 12.177239,9.7029996 11.209704,8.8195135 10.992255,8.6209543 10.577326,10.031484 9.1211947,9.2324497 8.2846288,9.9333947 7.6359672,10.607693 7.0611981,11.578553 6.5026891,12.62523 5.9177873,13.554793 5.867393,14.69141 c -0.024234,0.66432 0.4948601,1.360337 1.1982269,1.306329 0.702996,0.06277 1.1815208,-0.629091 1.7138087,-0.916491 0.079382,0.927141 0.1688108,1.923227 0.4821259,2.828358 0.3596254,1.171275 1.6262605,1.915695 2.8251855,1.745211 0.08481,-0.0066 0.218672,-0.01769 0.218672,-0.0176 z m 0.686342,-3.497495 c -0.643126,-0.394168 -0.33365,-1.249599 -0.359402,-1.870938 0.064,-0.749774 0.115321,-1.538054 0.452402,-2.221125 0.356724,-0.487008 1.226721,-0.299139 1.265134,0.325689 -0.02558,0.628509 -0.314101,1.25416 -0.279646,1.9057 -0.07482,0.544043 0.05418,1.155133 -0.186476,1.652391 -0.197455,0.275121 -0.599638,0.355105 -0.892012,0.208283 z m -2.808766,-0.358124 c -0.605767,-0.328664 -0.4133176,-1.155655 -0.5083256,-1.73063 0.078762,-0.66567 0.013203,-1.510085 0.5705316,-1.976886 0.545037,-0.380109 1.286917,0.270803 1.029164,0.868384 -0.274913,0.755214 -0.09475,1.580345 -0.08893,2.34609 -0.104009,0.451702 -0.587146,0.691508 -1.002445,0.493042 z"
57
69
id="path4"
-95
appview/pages/templates/fragments/dolly/silhouette.html
-95
appview/pages/templates/fragments/dolly/silhouette.html
···
1
-
{{ define "fragments/dolly/silhouette" }}
2
-
<svg
3
-
version="1.1"
4
-
id="svg1"
5
-
width="25"
6
-
height="25"
7
-
viewBox="0 0 25 25"
8
-
sodipodi:docname="tangled_dolly_face_only_black_on_trans.svg"
9
-
inkscape:export-filename="tangled_dolly_silhouette_black_on_trans.svg"
10
-
inkscape:export-xdpi="96"
11
-
inkscape:export-ydpi="96"
12
-
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
13
-
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
14
-
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
15
-
xmlns="http://www.w3.org/2000/svg"
16
-
xmlns:svg="http://www.w3.org/2000/svg"
17
-
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
18
-
xmlns:cc="http://creativecommons.org/ns#">
19
-
<style>
20
-
.dolly {
21
-
color: #000000;
22
-
}
23
-
24
-
@media (prefers-color-scheme: dark) {
25
-
.dolly {
26
-
color: #ffffff;
27
-
}
28
-
}
29
-
</style>
30
-
<sodipodi:namedview
31
-
id="namedview1"
32
-
pagecolor="#ffffff"
33
-
bordercolor="#000000"
34
-
borderopacity="0.25"
35
-
inkscape:showpageshadow="2"
36
-
inkscape:pageopacity="0.0"
37
-
inkscape:pagecheckerboard="true"
38
-
inkscape:deskcolor="#d5d5d5"
39
-
inkscape:zoom="64"
40
-
inkscape:cx="4.96875"
41
-
inkscape:cy="13.429688"
42
-
inkscape:window-width="3840"
43
-
inkscape:window-height="2160"
44
-
inkscape:window-x="0"
45
-
inkscape:window-y="0"
46
-
inkscape:window-maximized="0"
47
-
inkscape:current-layer="g1"
48
-
borderlayer="true">
49
-
<inkscape:page
50
-
x="0"
51
-
y="0"
52
-
width="25"
53
-
height="25"
54
-
id="page2"
55
-
margin="0"
56
-
bleed="0" />
57
-
</sodipodi:namedview>
58
-
<g
59
-
inkscape:groupmode="layer"
60
-
inkscape:label="Image"
61
-
id="g1"
62
-
transform="translate(-0.42924038,-0.87777209)">
63
-
<path
64
-
class="dolly"
65
-
fill="currentColor"
66
-
style="stroke-width:0.111183"
67
-
d="m 16.775491,24.987061 c -0.78517,-0.0064 -1.384202,-0.234614 -2.033994,-0.631295 -0.931792,-0.490188 -1.643475,-1.31368 -2.152014,-2.221647 C 11.781409,23.136647 10.701392,23.744942 9.4922931,24.0886 8.9774725,24.238111 8.0757679,24.389777 6.5811304,23.84827 4.4270703,23.124679 2.8580086,20.883331 3.0363279,18.599583 3.0037061,17.652919 3.3488675,16.723769 3.8381157,15.925061 2.5329485,15.224503 1.4686756,14.048584 1.0611184,12.606459 0.81344502,11.816973 0.82385989,10.966486 0.91519098,10.154906 1.2422711,8.2387903 2.6795811,6.5725716 4.5299585,5.9732484 5.2685364,4.290122 6.8802592,3.0349975 8.706276,2.7794663 c 1.2124148,-0.1688264 2.46744,0.084987 3.52811,0.7011837 1.545426,-1.7139736 4.237779,-2.2205077 6.293579,-1.1676231 1.568222,0.7488935 2.689625,2.3113526 2.961888,4.0151464 1.492195,0.5977882 2.749007,1.8168898 3.242225,3.3644951 0.329805,0.9581836 0.340709,2.0135956 0.127128,2.9974286 -0.381606,1.535184 -1.465322,2.842146 -2.868035,3.556463 0.0034,0.273204 0.901506,2.243045 0.751284,3.729647 -0.03281,1.858525 -1.211631,3.619894 -2.846433,4.475452 -0.953967,0.556812 -2.084452,0.546309 -3.120531,0.535398 z m -4.470079,-5.349839 c 1.322246,-0.147248 2.189053,-1.300106 2.862307,-2.338363 0.318287,-0.472954 0.561404,-1.002348 0.803,-1.505815 0.313265,0.287151 0.578698,0.828085 1.074141,0.956909 0.521892,0.162542 1.133743,0.03052 1.45325,-0.443554 0.611414,-1.140449 0.31004,-2.516537 -0.04602,-3.698347 C 18.232844,11.92927 17.945151,11.232927 17.397785,10.751793 17.514522,9.9283111 17.026575,9.0919791 16.332883,8.6609491 15.741721,9.1323278 14.842258,9.1294949 14.271975,8.6252369 13.178927,9.7400102 12.177239,9.7029996 11.209704,8.8195135 10.992255,8.6209543 10.577326,10.031484 9.1211947,9.2324497 8.2846288,9.9333947 7.6359672,10.607693 7.0611981,11.578553 6.5026891,12.62523 5.9177873,13.554793 5.867393,14.69141 c -0.024234,0.66432 0.4948601,1.360337 1.1982269,1.306329 0.702996,0.06277 1.1815208,-0.629091 1.7138087,-0.916491 0.079382,0.927141 0.1688108,1.923227 0.4821259,2.828358 0.3596254,1.171275 1.6262605,1.915695 2.8251855,1.745211 0.08481,-0.0066 0.218672,-0.01769 0.218672,-0.0176 z"
68
-
id="path7"
69
-
sodipodi:nodetypes="sccccccccccccccccccsscccccccccscccccccsc" />
70
-
</g>
71
-
<metadata
72
-
id="metadata1">
73
-
<rdf:RDF>
74
-
<cc:Work
75
-
rdf:about="">
76
-
<cc:license
77
-
rdf:resource="http://creativecommons.org/licenses/by/4.0/" />
78
-
</cc:Work>
79
-
<cc:License
80
-
rdf:about="http://creativecommons.org/licenses/by/4.0/">
81
-
<cc:permits
82
-
rdf:resource="http://creativecommons.org/ns#Reproduction" />
83
-
<cc:permits
84
-
rdf:resource="http://creativecommons.org/ns#Distribution" />
85
-
<cc:requires
86
-
rdf:resource="http://creativecommons.org/ns#Notice" />
87
-
<cc:requires
88
-
rdf:resource="http://creativecommons.org/ns#Attribution" />
89
-
<cc:permits
90
-
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
91
-
</cc:License>
92
-
</rdf:RDF>
93
-
</metadata>
94
-
</svg>
95
-
{{ end }}
+1
-1
appview/pages/templates/fragments/logotype.html
+1
-1
appview/pages/templates/fragments/logotype.html
···
1
1
{{ define "fragments/logotype" }}
2
2
<span class="flex items-center gap-2">
3
-
{{ template "fragments/dolly/logo" "size-16 text-black dark:text-white" }}
3
+
{{ template "fragments/dolly/logo" (dict "Classes" "size-16 text-black dark:text-white") }}
4
4
<span class="font-bold text-4xl not-italic">tangled</span>
5
5
<span class="font-normal not-italic text-xs rounded bg-gray-100 dark:bg-gray-700 px-1">
6
6
alpha
+1
-1
appview/pages/templates/fragments/logotypeSmall.html
+1
-1
appview/pages/templates/fragments/logotypeSmall.html
···
1
1
{{ define "fragments/logotypeSmall" }}
2
2
<span class="flex items-center gap-2">
3
-
{{ template "fragments/dolly/logo" "size-8 text-black dark:text-white" }}
3
+
{{ template "fragments/dolly/logo" (dict "Classes" "size-8 text-black dark:text-white")}}
4
4
<span class="font-bold text-xl not-italic">tangled</span>
5
5
<span class="font-normal not-italic text-xs rounded bg-gray-100 dark:bg-gray-700 px-1">
6
6
alpha
+4
appview/pages/templates/layouts/base.html
+4
appview/pages/templates/layouts/base.html
···
11
11
<script defer src="/static/htmx-ext-ws.min.js"></script>
12
12
<script defer src="/static/actor-typeahead.js" type="module"></script>
13
13
14
+
<link rel="icon" href="/static/logos/dolly.ico" sizes="48x48"/>
15
+
<link rel="icon" href="/static/logos/dolly.svg" sizes="any" type="image/svg+xml"/>
16
+
<link rel="apple-touch-icon" href="/static/logos/dolly.png"/>
17
+
14
18
<!-- preconnect to image cdn -->
15
19
<link rel="preconnect" href="https://avatar.tangled.sh" />
16
20
<link rel="preconnect" href="https://camo.tangled.sh" />
+1
-5
appview/pages/templates/layouts/fragments/topbar.html
+1
-5
appview/pages/templates/layouts/fragments/topbar.html
···
3
3
<div class="flex justify-between p-0 items-center">
4
4
<div id="left-items">
5
5
<a href="/" hx-boost="true" class="text-2xl no-underline hover:no-underline flex items-center gap-2">
6
-
{{ template "fragments/dolly/logo" "size-8 text-black dark:text-white" }}
7
-
<span class="font-bold text-xl not-italic hidden md:inline">tangled</span>
8
-
<span class="font-normal not-italic text-xs rounded bg-gray-100 dark:bg-gray-700 px-1 hidden md:inline">
9
-
alpha
10
-
</span>
6
+
{{ template "fragments/logotypeSmall" }}
11
7
</a>
12
8
</div>
13
9
+50
appview/pages/templates/repo/fragments/reactions.html
+50
appview/pages/templates/repo/fragments/reactions.html
···
1
+
{{ define "repo/fragments/reactions" }}
2
+
<div class="flex flex-wrap items-center gap-2">
3
+
{{- $reactions := .Reactions -}}
4
+
{{- $userReacted := .UserReacted -}}
5
+
{{- $threadAt := .ThreadAt -}}
6
+
7
+
{{ template "reactionsPopup" }}
8
+
{{ range $kind := const.OrderedReactionKinds }}
9
+
{{ $reactionData := index $reactions $kind }}
10
+
{{ template "repo/fragments/reaction"
11
+
(dict
12
+
"Kind" $kind
13
+
"Count" $reactionData.Count
14
+
"IsReacted" (index $userReacted $kind)
15
+
"ThreadAt" $threadAt
16
+
"Users" $reactionData.Users) }}
17
+
{{ end }}
18
+
</div>
19
+
{{ end }}
20
+
21
+
{{ define "reactionsPopup" }}
22
+
<details
23
+
id="reactionsPopUp"
24
+
class="relative inline-block"
25
+
>
26
+
<summary
27
+
class="flex justify-center items-center min-w-8 min-h-8 rounded border border-gray-200 dark:border-gray-700
28
+
hover:bg-gray-50
29
+
hover:border-gray-300
30
+
dark:hover:bg-gray-700
31
+
dark:hover:border-gray-600
32
+
cursor-pointer list-none"
33
+
>
34
+
{{ i "smile" "size-4" }}
35
+
</summary>
36
+
<div
37
+
class="absolute flex left-0 z-10 mt-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg"
38
+
>
39
+
{{ range $kind := const.OrderedReactionKinds }}
40
+
<button
41
+
id="reactBtn-{{ $kind }}"
42
+
class="size-12 hover:bg-gray-100 dark:hover:bg-gray-700"
43
+
hx-on:click="this.parentElement.parentElement.removeAttribute('open')"
44
+
>
45
+
{{ $kind }}
46
+
</button>
47
+
{{ end }}
48
+
</div>
49
+
</details>
50
+
{{ end }}
-30
appview/pages/templates/repo/fragments/reactionsPopUp.html
-30
appview/pages/templates/repo/fragments/reactionsPopUp.html
···
1
-
{{ define "repo/fragments/reactionsPopUp" }}
2
-
<details
3
-
id="reactionsPopUp"
4
-
class="relative inline-block"
5
-
>
6
-
<summary
7
-
class="flex justify-center items-center min-w-8 min-h-8 rounded border border-gray-200 dark:border-gray-700
8
-
hover:bg-gray-50
9
-
hover:border-gray-300
10
-
dark:hover:bg-gray-700
11
-
dark:hover:border-gray-600
12
-
cursor-pointer list-none"
13
-
>
14
-
{{ i "smile" "size-4" }}
15
-
</summary>
16
-
<div
17
-
class="absolute flex left-0 z-10 mt-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg"
18
-
>
19
-
{{ range $kind := . }}
20
-
<button
21
-
id="reactBtn-{{ $kind }}"
22
-
class="size-12 hover:bg-gray-100 dark:hover:bg-gray-700"
23
-
hx-on:click="this.parentElement.parentElement.removeAttribute('open')"
24
-
>
25
-
{{ $kind }}
26
-
</button>
27
-
{{ end }}
28
-
</div>
29
-
</details>
30
-
{{ end }}
+5
-21
appview/pages/templates/repo/issues/issue.html
+5
-21
appview/pages/templates/repo/issues/issue.html
···
35
35
{{ if .Issue.Body }}
36
36
<article id="body" class="mt-4 prose dark:prose-invert">{{ .Issue.Body | markdown }}</article>
37
37
{{ end }}
38
-
<div class="flex flex-wrap gap-2 items-stretch mt-4">
39
-
{{ template "issueReactions" . }}
38
+
<div class="mt-4">
39
+
{{ template "repo/fragments/reactions"
40
+
(dict "Reactions" .Reactions
41
+
"UserReacted" .UserReacted
42
+
"ThreadAt" .Issue.AtUri) }}
40
43
</div>
41
44
</section>
42
45
{{ end }}
···
106
109
{{ i "loader-circle" "size-3 animate-spin hidden group-[.htmx-request]:inline" }}
107
110
</a>
108
111
{{ end }}
109
-
110
-
{{ define "issueReactions" }}
111
-
<div class="flex items-center gap-2">
112
-
{{ template "repo/fragments/reactionsPopUp" .OrderedReactionKinds }}
113
-
{{ range $kind := .OrderedReactionKinds }}
114
-
{{ $reactionData := index $.Reactions $kind }}
115
-
{{
116
-
template "repo/fragments/reaction"
117
-
(dict
118
-
"Kind" $kind
119
-
"Count" $reactionData.Count
120
-
"IsReacted" (index $.UserReacted $kind)
121
-
"ThreadAt" $.Issue.AtUri
122
-
"Users" $reactionData.Users)
123
-
}}
124
-
{{ end }}
125
-
</div>
126
-
{{ end }}
127
-
128
112
129
113
{{ define "repoAfter" }}
130
114
<div class="flex flex-col gap-4 mt-4">
+5
-16
appview/pages/templates/repo/pulls/fragments/pullHeader.html
+5
-16
appview/pages/templates/repo/pulls/fragments/pullHeader.html
···
64
64
</article>
65
65
{{ end }}
66
66
67
-
{{ with .OrderedReactionKinds }}
68
-
<div class="flex items-center gap-2 mt-2">
69
-
{{ template "repo/fragments/reactionsPopUp" . }}
70
-
{{ range $kind := . }}
71
-
{{ $reactionData := index $.Reactions $kind }}
72
-
{{
73
-
template "repo/fragments/reaction"
74
-
(dict
75
-
"Kind" $kind
76
-
"Count" $reactionData.Count
77
-
"IsReacted" (index $.UserReacted $kind)
78
-
"ThreadAt" $.Pull.AtUri
79
-
"Users" $reactionData.Users)
80
-
}}
81
-
{{ end }}
67
+
<div class="mt-2">
68
+
{{ template "repo/fragments/reactions"
69
+
(dict "Reactions" .Reactions
70
+
"UserReacted" .UserReacted
71
+
"ThreadAt" .Pull.AtUri) }}
82
72
</div>
83
-
{{ end }}
84
73
</section>
85
74
86
75
+1
-1
appview/pulls/opengraph.go
+1
-1
appview/pulls/opengraph.go
···
242
242
dollyX := dollyBounds.Min.X + (dollyBounds.Dx() / 2) - (dollySize / 2)
243
243
dollyY := statsY + iconBaselineOffset - dollySize/2 + 25
244
244
dollyColor := color.RGBA{180, 180, 180, 255} // light gray
245
-
err = dollyArea.DrawDollySilhouette(dollyX, dollyY, dollySize, dollyColor)
245
+
err = dollyArea.DrawDolly(dollyX, dollyY, dollySize, dollyColor)
246
246
if err != nil {
247
247
log.Printf("dolly silhouette not available (this is ok): %v", err)
248
248
}
+2
-3
appview/pulls/pulls.go
+2
-3
appview/pulls/pulls.go
···
244
244
ResubmitCheck: resubmitResult,
245
245
Pipelines: m,
246
246
247
-
OrderedReactionKinds: models.OrderedReactionKinds,
248
-
Reactions: reactionMap,
249
-
UserReacted: userReactions,
247
+
Reactions: reactionMap,
248
+
UserReacted: userReactions,
250
249
251
250
LabelDefs: defs,
252
251
})
+1
-1
appview/repo/opengraph.go
+1
-1
appview/repo/opengraph.go
···
237
237
dollyX := dollyBounds.Min.X + (dollyBounds.Dx() / 2) - (dollySize / 2)
238
238
dollyY := statsY + iconBaselineOffset - dollySize/2 + 25
239
239
dollyColor := color.RGBA{180, 180, 180, 255} // light gray
240
-
err = dollyArea.DrawDollySilhouette(dollyX, dollyY, dollySize, dollyColor)
240
+
err = dollyArea.DrawDolly(dollyX, dollyY, dollySize, dollyColor)
241
241
if err != nil {
242
242
log.Printf("dolly silhouette not available (this is ok): %v", err)
243
243
}
-14
appview/repo/settings.go
-14
appview/repo/settings.go
···
22
22
indigoxrpc "github.com/bluesky-social/indigo/xrpc"
23
23
)
24
24
25
-
type tab = map[string]any
26
-
27
-
var (
28
-
// would be great to have ordered maps right about now
29
-
settingsTabs []tab = []tab{
30
-
{"Name": "general", "Icon": "sliders-horizontal"},
31
-
{"Name": "access", "Icon": "users"},
32
-
{"Name": "pipelines", "Icon": "layers-2"},
33
-
}
34
-
)
35
-
36
25
func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
37
26
l := rp.logger.With("handler", "SetDefaultBranch")
38
27
···
262
251
DefaultLabels: defaultLabels,
263
252
SubscribedLabels: subscribedLabels,
264
253
ShouldSubscribeAll: shouldSubscribeAll,
265
-
Tabs: settingsTabs,
266
254
Tab: "general",
267
255
})
268
256
}
···
308
296
rp.pages.RepoAccessSettings(w, pages.RepoAccessSettingsParams{
309
297
LoggedInUser: user,
310
298
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
311
-
Tabs: settingsTabs,
312
299
Tab: "access",
313
300
Collaborators: collaborators,
314
301
})
···
369
356
rp.pages.RepoPipelineSettings(w, pages.RepoPipelineSettingsParams{
370
357
LoggedInUser: user,
371
358
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
372
-
Tabs: settingsTabs,
373
359
Tab: "pipelines",
374
360
Spindles: spindles,
375
361
CurrentSpindle: f.Spindle,
-17
appview/settings/settings.go
-17
appview/settings/settings.go
···
35
35
Config *config.Config
36
36
}
37
37
38
-
type tab = map[string]any
39
-
40
-
var (
41
-
settingsTabs []tab = []tab{
42
-
{"Name": "profile", "Icon": "user"},
43
-
{"Name": "keys", "Icon": "key"},
44
-
{"Name": "emails", "Icon": "mail"},
45
-
{"Name": "notifications", "Icon": "bell"},
46
-
{"Name": "knots", "Icon": "volleyball"},
47
-
{"Name": "spindles", "Icon": "spool"},
48
-
}
49
-
)
50
-
51
38
func (s *Settings) Router() http.Handler {
52
39
r := chi.NewRouter()
53
40
···
85
72
86
73
s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{
87
74
LoggedInUser: user,
88
-
Tabs: settingsTabs,
89
75
Tab: "profile",
90
76
})
91
77
}
···
104
90
s.Pages.UserNotificationSettings(w, pages.UserNotificationSettingsParams{
105
91
LoggedInUser: user,
106
92
Preferences: prefs,
107
-
Tabs: settingsTabs,
108
93
Tab: "notifications",
109
94
})
110
95
}
···
146
131
s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{
147
132
LoggedInUser: user,
148
133
PubKeys: pubKeys,
149
-
Tabs: settingsTabs,
150
134
Tab: "keys",
151
135
})
152
136
}
···
161
145
s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{
162
146
LoggedInUser: user,
163
147
Emails: emails,
164
-
Tabs: settingsTabs,
165
148
Tab: "emails",
166
149
})
167
150
}
-15
appview/spindles/spindles.go
-15
appview/spindles/spindles.go
···
39
39
Logger *slog.Logger
40
40
}
41
41
42
-
type tab = map[string]any
43
-
44
-
var (
45
-
spindlesTabs []tab = []tab{
46
-
{"Name": "profile", "Icon": "user"},
47
-
{"Name": "keys", "Icon": "key"},
48
-
{"Name": "emails", "Icon": "mail"},
49
-
{"Name": "notifications", "Icon": "bell"},
50
-
{"Name": "knots", "Icon": "volleyball"},
51
-
{"Name": "spindles", "Icon": "spool"},
52
-
}
53
-
)
54
-
55
42
func (s *Spindles) Router() http.Handler {
56
43
r := chi.NewRouter()
57
44
···
83
70
s.Pages.Spindles(w, pages.SpindlesParams{
84
71
LoggedInUser: user,
85
72
Spindles: all,
86
-
Tabs: spindlesTabs,
87
73
Tab: "spindles",
88
74
})
89
75
}
···
143
129
Spindle: spindle,
144
130
Members: members,
145
131
Repos: repoMap,
146
-
Tabs: spindlesTabs,
147
132
Tab: "spindles",
148
133
})
149
134
}
+29
appview/state/manifest.go
+29
appview/state/manifest.go
···
1
+
package state
2
+
3
+
import (
4
+
"encoding/json"
5
+
"net/http"
6
+
)
7
+
8
+
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
9
+
// https://www.w3.org/TR/appmanifest/
10
+
var manifestData = map[string]any{
11
+
"name": "tangled",
12
+
"description": "tightly-knit social coding.",
13
+
"icons": []map[string]string{
14
+
{
15
+
"src": "/static/logos/dolly.svg",
16
+
"sizes": "144x144",
17
+
},
18
+
},
19
+
"start_url": "/",
20
+
"id": "https://tangled.org",
21
+
"display": "standalone",
22
+
"background_color": "#111827",
23
+
"theme_color": "#111827",
24
+
}
25
+
26
+
func (p *State) WebAppManifest(w http.ResponseWriter, r *http.Request) {
27
+
w.Header().Set("Content-Type", "application/manifest+json")
28
+
json.NewEncoder(w).Encode(manifestData)
29
+
}
+1
-3
appview/state/router.go
+1
-3
appview/state/router.go
···
32
32
s.pages,
33
33
)
34
34
35
-
router.Get("/favicon.svg", s.Favicon)
36
-
router.Get("/favicon.ico", s.Favicon)
37
-
router.Get("/pwa-manifest.json", s.PWAManifest)
35
+
router.Get("/pwa-manifest.json", s.WebAppManifest)
38
36
router.Get("/robots.txt", s.RobotsTxt)
39
37
40
38
userRouter := s.UserRouter(&middleware)
-36
appview/state/state.go
-36
appview/state/state.go
···
202
202
return s.db.Close()
203
203
}
204
204
205
-
func (s *State) Favicon(w http.ResponseWriter, r *http.Request) {
206
-
w.Header().Set("Content-Type", "image/svg+xml")
207
-
w.Header().Set("Cache-Control", "public, max-age=31536000") // one year
208
-
w.Header().Set("ETag", `"favicon-svg-v1"`)
209
-
210
-
if match := r.Header.Get("If-None-Match"); match == `"favicon-svg-v1"` {
211
-
w.WriteHeader(http.StatusNotModified)
212
-
return
213
-
}
214
-
215
-
s.pages.Favicon(w)
216
-
}
217
-
218
205
func (s *State) RobotsTxt(w http.ResponseWriter, r *http.Request) {
219
206
w.Header().Set("Content-Type", "text/plain")
220
207
w.Header().Set("Cache-Control", "public, max-age=86400") // one day
···
223
210
Allow: /
224
211
`
225
212
w.Write([]byte(robotsTxt))
226
-
}
227
-
228
-
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
229
-
const manifestJson = `{
230
-
"name": "tangled",
231
-
"description": "tightly-knit social coding.",
232
-
"icons": [
233
-
{
234
-
"src": "/favicon.svg",
235
-
"sizes": "144x144"
236
-
}
237
-
],
238
-
"start_url": "/",
239
-
"id": "org.tangled",
240
-
241
-
"display": "standalone",
242
-
"background_color": "#111827",
243
-
"theme_color": "#111827"
244
-
}`
245
-
246
-
func (p *State) PWAManifest(w http.ResponseWriter, r *http.Request) {
247
-
w.Header().Set("Content-Type", "application/json")
248
-
w.Write([]byte(manifestJson))
249
213
}
250
214
251
215
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
-1
cmd/cborgen/cborgen.go
-1
cmd/cborgen/cborgen.go
+182
cmd/dolly/main.go
+182
cmd/dolly/main.go
···
1
+
package main
2
+
3
+
import (
4
+
"bytes"
5
+
"flag"
6
+
"fmt"
7
+
"image"
8
+
"image/color"
9
+
"image/png"
10
+
"os"
11
+
"path/filepath"
12
+
"strconv"
13
+
"strings"
14
+
"text/template"
15
+
16
+
"github.com/srwiley/oksvg"
17
+
"github.com/srwiley/rasterx"
18
+
"golang.org/x/image/draw"
19
+
"tangled.org/core/appview/pages"
20
+
"tangled.org/core/ico"
21
+
)
22
+
23
+
func main() {
24
+
var (
25
+
size string
26
+
fillColor string
27
+
output string
28
+
)
29
+
30
+
flag.StringVar(&size, "size", "512x512", "Output size in format WIDTHxHEIGHT (e.g., 512x512)")
31
+
flag.StringVar(&fillColor, "color", "#000000", "Fill color in hex format (e.g., #FF5733)")
32
+
flag.StringVar(&output, "output", "dolly.svg", "Output file path (format detected from extension: .svg, .png, or .ico)")
33
+
flag.Parse()
34
+
35
+
width, height, err := parseSize(size)
36
+
if err != nil {
37
+
fmt.Fprintf(os.Stderr, "Error parsing size: %v\n", err)
38
+
os.Exit(1)
39
+
}
40
+
41
+
// Detect format from file extension
42
+
ext := strings.ToLower(filepath.Ext(output))
43
+
format := strings.TrimPrefix(ext, ".")
44
+
45
+
if format != "svg" && format != "png" && format != "ico" {
46
+
fmt.Fprintf(os.Stderr, "Invalid file extension: %s. Must be .svg, .png, or .ico\n", ext)
47
+
os.Exit(1)
48
+
}
49
+
50
+
if fillColor != "currentColor" && !isValidHexColor(fillColor) {
51
+
fmt.Fprintf(os.Stderr, "Invalid color format: %s. Use hex format like #FF5733\n", fillColor)
52
+
os.Exit(1)
53
+
}
54
+
55
+
svgData, err := dolly(fillColor)
56
+
if err != nil {
57
+
fmt.Fprintf(os.Stderr, "Error generating SVG: %v\n", err)
58
+
os.Exit(1)
59
+
}
60
+
61
+
// Create output directory if it doesn't exist
62
+
dir := filepath.Dir(output)
63
+
if dir != "" && dir != "." {
64
+
if err := os.MkdirAll(dir, 0755); err != nil {
65
+
fmt.Fprintf(os.Stderr, "Error creating output directory: %v\n", err)
66
+
os.Exit(1)
67
+
}
68
+
}
69
+
70
+
switch format {
71
+
case "svg":
72
+
err = saveSVG(svgData, output, width, height)
73
+
case "png":
74
+
err = savePNG(svgData, output, width, height)
75
+
case "ico":
76
+
err = saveICO(svgData, output, width, height)
77
+
}
78
+
79
+
if err != nil {
80
+
fmt.Fprintf(os.Stderr, "Error saving file: %v\n", err)
81
+
os.Exit(1)
82
+
}
83
+
84
+
fmt.Printf("Successfully generated %s (%dx%d)\n", output, width, height)
85
+
}
86
+
87
+
func dolly(hexColor string) ([]byte, error) {
88
+
tpl, err := template.New("dolly").
89
+
ParseFS(pages.Files, "templates/fragments/dolly/logo.html")
90
+
if err != nil {
91
+
return nil, err
92
+
}
93
+
94
+
var svgData bytes.Buffer
95
+
if err := tpl.ExecuteTemplate(&svgData, "fragments/dolly/logo", pages.DollyParams{
96
+
FillColor: hexColor,
97
+
}); err != nil {
98
+
return nil, err
99
+
}
100
+
101
+
return svgData.Bytes(), nil
102
+
}
103
+
104
+
func svgToImage(svgData []byte, w, h int) (image.Image, error) {
105
+
icon, err := oksvg.ReadIconStream(bytes.NewReader(svgData))
106
+
if err != nil {
107
+
return nil, fmt.Errorf("error parsing SVG: %v", err)
108
+
}
109
+
110
+
icon.SetTarget(0, 0, float64(w), float64(h))
111
+
rgba := image.NewRGBA(image.Rect(0, 0, w, h))
112
+
draw.Draw(rgba, rgba.Bounds(), &image.Uniform{color.Transparent}, image.Point{}, draw.Src)
113
+
scanner := rasterx.NewScannerGV(w, h, rgba, rgba.Bounds())
114
+
raster := rasterx.NewDasher(w, h, scanner)
115
+
icon.Draw(raster, 1.0)
116
+
117
+
return rgba, nil
118
+
}
119
+
120
+
func parseSize(size string) (int, int, error) {
121
+
parts := strings.Split(size, "x")
122
+
if len(parts) != 2 {
123
+
return 0, 0, fmt.Errorf("invalid size format, use WIDTHxHEIGHT")
124
+
}
125
+
126
+
width, err := strconv.Atoi(parts[0])
127
+
if err != nil {
128
+
return 0, 0, fmt.Errorf("invalid width: %v", err)
129
+
}
130
+
131
+
height, err := strconv.Atoi(parts[1])
132
+
if err != nil {
133
+
return 0, 0, fmt.Errorf("invalid height: %v", err)
134
+
}
135
+
136
+
if width <= 0 || height <= 0 {
137
+
return 0, 0, fmt.Errorf("width and height must be positive")
138
+
}
139
+
140
+
return width, height, nil
141
+
}
142
+
143
+
func isValidHexColor(hex string) bool {
144
+
if len(hex) != 7 || hex[0] != '#' {
145
+
return false
146
+
}
147
+
_, err := strconv.ParseUint(hex[1:], 16, 32)
148
+
return err == nil
149
+
}
150
+
151
+
func saveSVG(svgData []byte, filepath string, _, _ int) error {
152
+
return os.WriteFile(filepath, svgData, 0644)
153
+
}
154
+
155
+
func savePNG(svgData []byte, filepath string, width, height int) error {
156
+
img, err := svgToImage(svgData, width, height)
157
+
if err != nil {
158
+
return err
159
+
}
160
+
161
+
f, err := os.Create(filepath)
162
+
if err != nil {
163
+
return err
164
+
}
165
+
defer f.Close()
166
+
167
+
return png.Encode(f, img)
168
+
}
169
+
170
+
func saveICO(svgData []byte, filepath string, width, height int) error {
171
+
img, err := svgToImage(svgData, width, height)
172
+
if err != nil {
173
+
return err
174
+
}
175
+
176
+
icoData, err := ico.ImageToIco(img)
177
+
if err != nil {
178
+
return err
179
+
}
180
+
181
+
return os.WriteFile(filepath, icoData, 0644)
182
+
}
+6
docs/logo.html
+6
docs/logo.html
+7
-6
docs/template.html
+7
-6
docs/template.html
···
74
74
${ x.svg() }
75
75
$if(toc-title)$$toc-title$$else$Table of Contents$endif$
76
76
</button>
77
+
${ logo.html() }
77
78
${ search.html() }
78
79
${ table-of-contents:toc.html() }
79
80
</div>
···
88
89
class="hidden md:flex md:flex-col gap-4 fixed left-0 top-0 w-80 h-screen
89
90
bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700
90
91
p-4 z-50 overflow-y-auto">
92
+
${ logo.html() }
91
93
${ search.html() }
92
94
<div class="flex-1">
93
95
$if(toc-title)$
···
118
120
$endif$
119
121
$endif$
120
122
</header>
121
-
$endif$
122
-
123
-
$if(abstract)$
124
-
<article class="prose dark:prose-invert max-w-none">
125
-
$abstract$
126
-
</article>
123
+
$if(abstract)$
124
+
<article class="prose dark:prose-invert max-w-none">
125
+
$abstract$
126
+
</article>
127
+
$endif$
127
128
$endif$
128
129
129
130
<article class="prose dark:prose-invert max-w-none">
+17
-2
flake.nix
+17
-2
flake.nix
···
94
94
spindle = self.callPackage ./nix/pkgs/spindle.nix {};
95
95
knot-unwrapped = self.callPackage ./nix/pkgs/knot-unwrapped.nix {};
96
96
knot = self.callPackage ./nix/pkgs/knot.nix {};
97
+
dolly = self.callPackage ./nix/pkgs/dolly.nix {};
97
98
});
98
99
in {
99
100
overlays.default = final: prev: {
100
-
inherit (mkPackageSet final) lexgen goat sqlite-lib spindle knot-unwrapped knot appview docs;
101
+
inherit (mkPackageSet final) lexgen goat sqlite-lib spindle knot-unwrapped knot appview docs dolly;
101
102
};
102
103
103
104
packages = forAllSystems (system: let
···
106
107
staticPackages = mkPackageSet pkgs.pkgsStatic;
107
108
crossPackages = mkPackageSet pkgs.pkgsCross.gnu64.pkgsStatic;
108
109
in {
109
-
inherit (packages) appview appview-static-files lexgen goat spindle knot knot-unwrapped sqlite-lib docs;
110
+
inherit
111
+
(packages)
112
+
appview
113
+
appview-static-files
114
+
lexgen
115
+
goat
116
+
spindle
117
+
knot
118
+
knot-unwrapped
119
+
sqlite-lib
120
+
docs
121
+
dolly
122
+
;
110
123
111
124
pkgsStatic-appview = staticPackages.appview;
112
125
pkgsStatic-knot = staticPackages.knot;
113
126
pkgsStatic-knot-unwrapped = staticPackages.knot-unwrapped;
114
127
pkgsStatic-spindle = staticPackages.spindle;
115
128
pkgsStatic-sqlite-lib = staticPackages.sqlite-lib;
129
+
pkgsStatic-dolly = staticPackages.dolly;
116
130
117
131
pkgsCross-gnu64-pkgsStatic-appview = crossPackages.appview;
118
132
pkgsCross-gnu64-pkgsStatic-knot = crossPackages.knot;
119
133
pkgsCross-gnu64-pkgsStatic-knot-unwrapped = crossPackages.knot-unwrapped;
120
134
pkgsCross-gnu64-pkgsStatic-spindle = crossPackages.spindle;
135
+
pkgsCross-gnu64-pkgsStatic-dolly = crossPackages.dolly;
121
136
122
137
treefmt-wrapper = pkgs.treefmt.withConfig {
123
138
settings.formatter = {
+88
ico/ico.go
+88
ico/ico.go
···
1
+
package ico
2
+
3
+
import (
4
+
"bytes"
5
+
"encoding/binary"
6
+
"fmt"
7
+
"image"
8
+
"image/png"
9
+
)
10
+
11
+
type IconDir struct {
12
+
Reserved uint16 // must be 0
13
+
Type uint16 // 1 for ICO, 2 for CUR
14
+
Count uint16 // number of images
15
+
}
16
+
17
+
type IconDirEntry struct {
18
+
Width uint8 // 0 means 256
19
+
Height uint8 // 0 means 256
20
+
ColorCount uint8
21
+
Reserved uint8 // must be 0
22
+
ColorPlanes uint16 // 0 or 1
23
+
BitsPerPixel uint16
24
+
SizeInBytes uint32
25
+
Offset uint32
26
+
}
27
+
28
+
func ImageToIco(img image.Image) ([]byte, error) {
29
+
// encode image as png
30
+
var pngBuf bytes.Buffer
31
+
if err := png.Encode(&pngBuf, img); err != nil {
32
+
return nil, fmt.Errorf("failed to encode PNG: %w", err)
33
+
}
34
+
pngData := pngBuf.Bytes()
35
+
36
+
// get image dimensions
37
+
bounds := img.Bounds()
38
+
width := bounds.Dx()
39
+
height := bounds.Dy()
40
+
41
+
// prepare output buffer
42
+
var icoBuf bytes.Buffer
43
+
44
+
iconDir := IconDir{
45
+
Reserved: 0,
46
+
Type: 1, // ICO format
47
+
Count: 1, // One image
48
+
}
49
+
50
+
w := uint8(width)
51
+
h := uint8(height)
52
+
53
+
// width/height of 256 should be stored as 0
54
+
if width == 256 {
55
+
w = 0
56
+
}
57
+
if height == 256 {
58
+
h = 0
59
+
}
60
+
61
+
iconDirEntry := IconDirEntry{
62
+
Width: w,
63
+
Height: h,
64
+
ColorCount: 0, // 0 for PNG (32-bit)
65
+
Reserved: 0,
66
+
ColorPlanes: 1,
67
+
BitsPerPixel: 32, // PNG with alpha
68
+
SizeInBytes: uint32(len(pngData)),
69
+
Offset: 6 + 16, // Size of ICONDIR + ICONDIRENTRY
70
+
}
71
+
72
+
// write IconDir
73
+
if err := binary.Write(&icoBuf, binary.LittleEndian, iconDir); err != nil {
74
+
return nil, fmt.Errorf("failed to write ICONDIR: %w", err)
75
+
}
76
+
77
+
// write IconDirEntry
78
+
if err := binary.Write(&icoBuf, binary.LittleEndian, iconDirEntry); err != nil {
79
+
return nil, fmt.Errorf("failed to write ICONDIRENTRY: %w", err)
80
+
}
81
+
82
+
// write PNG data directly
83
+
if _, err := icoBuf.Write(pngData); err != nil {
84
+
return nil, fmt.Errorf("failed to write PNG data: %w", err)
85
+
}
86
+
87
+
return icoBuf.Bytes(), nil
88
+
}
-51
lexicons/comment/comment.json
-51
lexicons/comment/comment.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.tangled.comment",
4
-
"needsCbor": true,
5
-
"needsType": true,
6
-
"defs": {
7
-
"main": {
8
-
"type": "record",
9
-
"key": "tid",
10
-
"record": {
11
-
"type": "object",
12
-
"required": [
13
-
"subject",
14
-
"body",
15
-
"createdAt"
16
-
],
17
-
"properties": {
18
-
"subject": {
19
-
"type": "string",
20
-
"format": "at-uri"
21
-
},
22
-
"body": {
23
-
"type": "string"
24
-
},
25
-
"createdAt": {
26
-
"type": "string",
27
-
"format": "datetime"
28
-
},
29
-
"replyTo": {
30
-
"type": "string",
31
-
"format": "at-uri"
32
-
},
33
-
"mentions": {
34
-
"type": "array",
35
-
"items": {
36
-
"type": "string",
37
-
"format": "did"
38
-
}
39
-
},
40
-
"references": {
41
-
"type": "array",
42
-
"items": {
43
-
"type": "string",
44
-
"format": "at-uri"
45
-
}
46
-
}
47
-
}
48
-
}
49
-
}
50
-
}
51
-
}
+6
-1
nix/pkgs/appview-static-files.nix
+6
-1
nix/pkgs/appview-static-files.nix
···
8
8
actor-typeahead-src,
9
9
sqlite-lib,
10
10
tailwindcss,
11
+
dolly,
11
12
src,
12
13
}:
13
14
runCommandLocal "appview-static-files" {
···
17
18
(allow file-read* (subpath "/System/Library/OpenSSL"))
18
19
'';
19
20
} ''
20
-
mkdir -p $out/{fonts,icons} && cd $out
21
+
mkdir -p $out/{fonts,icons,logos} && cd $out
21
22
cp -f ${htmx-src} htmx.min.js
22
23
cp -f ${htmx-ws-src} htmx-ext-ws.min.js
23
24
cp -rf ${lucide-src}/*.svg icons/
···
26
27
cp -f ${inter-fonts-src}/InterVariable*.ttf fonts/
27
28
cp -f ${ibm-plex-mono-src}/fonts/complete/woff2/IBMPlexMono*.woff2 fonts/
28
29
cp -f ${actor-typeahead-src}/actor-typeahead.js .
30
+
31
+
${dolly}/bin/dolly -output logos/dolly.png -size 180x180
32
+
${dolly}/bin/dolly -output logos/dolly.ico -size 48x48
33
+
${dolly}/bin/dolly -output logos/dolly.svg -color currentColor
29
34
# tailwindcss -c $src/tailwind.config.js -i $src/input.css -o tw.css won't work
30
35
# for whatever reason (produces broken css), so we are doing this instead
31
36
cd ${src} && ${tailwindcss}/bin/tailwindcss -i input.css -o $out/tw.css
+4
nix/pkgs/docs.nix
+4
nix/pkgs/docs.nix
···
5
5
inter-fonts-src,
6
6
ibm-plex-mono-src,
7
7
lucide-src,
8
+
dolly,
8
9
src,
9
10
}:
10
11
runCommandLocal "docs" {} ''
···
17
18
18
19
# icons
19
20
cp -rf ${lucide-src}/*.svg working/
21
+
22
+
# logo
23
+
${dolly}/bin/dolly -output working/dolly.svg -color currentColor
20
24
21
25
# content - chunked
22
26
${pandoc}/bin/pandoc ${src}/docs/DOCS.md \
+21
nix/pkgs/dolly.nix
+21
nix/pkgs/dolly.nix
···
1
+
{
2
+
buildGoApplication,
3
+
modules,
4
+
src,
5
+
}:
6
+
buildGoApplication {
7
+
pname = "dolly";
8
+
version = "0.1.0";
9
+
inherit src modules;
10
+
11
+
# patch the static dir
12
+
postUnpack = ''
13
+
pushd source
14
+
mkdir -p appview/pages/static
15
+
touch appview/pages/static/x
16
+
popd
17
+
'';
18
+
19
+
doCheck = false;
20
+
subPackages = ["cmd/dolly"];
21
+
}