this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+529 -869
api
appview
issues
knots
ogcard
pages
templates
brand
fragments
layouts
repo
fragments
issues
pulls
fragments
settings
fragments
user
settings
fragments
pulls
repo
settings
spindles
state
cmd
cborgen
dolly
docs
ico
lexicons
comment
nix
-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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 {{ 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 {{ 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 -2
appview/pages/templates/repo/settings/fragments/sidebar.html
··· 1 1 {{ define "repo/settings/fragments/sidebar" }} 2 2 {{ $active := .Tab }} 3 - {{ $tabs := .Tabs }} 4 3 <div class="sticky top-2 grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 shadow-inner"> 5 4 {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} 6 5 {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} 7 - {{ range $tabs }} 6 + {{ range const.RepoSettingsTabs }} 8 7 <a href="/{{ $.RepoInfo.FullName }}/settings?tab={{.Name}}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 9 8 <div class="flex gap-3 items-center p-2 {{ if eq .Name $active }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 10 9 {{ i .Icon "size-4" }}
+2 -3
appview/pages/templates/user/settings/fragments/sidebar.html
··· 1 1 {{ define "user/settings/fragments/sidebar" }} 2 2 {{ $active := .Tab }} 3 - {{ $tabs := .Tabs }} 4 3 <div class="sticky top-2 grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 shadow-inner"> 5 4 {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} 6 5 {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} 7 - {{ range $tabs }} 6 + {{ range const.UserSettingsTabs }} 8 7 <a href="/settings/{{.Name}}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 9 8 <div class="flex gap-3 items-center p-2 {{ if eq .Name $active }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 10 9 {{ i .Icon "size-4" }} ··· 13 12 </a> 14 13 {{ end }} 15 14 </div> 16 - {{ end }} 15 + {{ end }}
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 15 15 "api/tangled/cbor_gen.go", 16 16 "tangled", 17 17 tangled.ActorProfile{}, 18 - tangled.Comment{}, 19 18 tangled.FeedReaction{}, 20 19 tangled.FeedStar{}, 21 20 tangled.GitRefUpdate{},
+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
··· 1 + <div class="flex items-center gap-2 w-fit mx-auto"> 2 + <span class="w-16 h-16 [&>svg]:w-full [&>svg]:h-full text-black dark:text-white"> 3 + ${ dolly.svg() } 4 + </span> 5 + <span class="font-bold text-4xl not-italic text-black dark:text-white">tangled</span> 6 + </div>
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 + }