1package systemviews
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/bnema/dumber/internal/domain/entity"
8)
9
10const defaultTagColor = "#808080"
11
12type favoritesRenderData struct {
13 Favorites []*entity.Favorite
14 Folders []*entity.Folder
15 Tags []*entity.Tag
16 FolderFilter *entity.FolderID
17 TagFilter *entity.TagID
18 Notice string
19 Error string
20}
21
22func favoritesHTML(data favoritesRenderData) string {
23 return mustRenderComponent(FavoritesView(data))
24}
25
26func favoritesDocumentTitle(data favoritesRenderData) string {
27 count := len(data.Favorites)
28 if data.FolderFilter != nil || data.TagFilter != nil {
29 if count == 1 {
30 return "Favorites — 1 filtered bookmark"
31 }
32 return fmt.Sprintf("Favorites — %d filtered bookmarks", count)
33 }
34 if count == 1 {
35 return "Favorites — 1 bookmark"
36 }
37 return fmt.Sprintf("Favorites — %d bookmarks", count)
38}
39
40func favoritesSummary(data favoritesRenderData) string {
41 return fmt.Sprintf("%d favorites · %d folders · %d tags", len(data.Favorites), len(data.Folders), len(data.Tags))
42}
43
44func foldersSummary(data favoritesRenderData) string {
45 return fmt.Sprintf("%d folders", len(data.Folders))
46}
47
48func tagsSummary(data favoritesRenderData) string {
49 return fmt.Sprintf("%d tags", len(data.Tags))
50}
51
52func favoriteFilterClass(active bool) string {
53 classes := "sv-button sv-button-secondary"
54 if active {
55 classes += " sv-button-active"
56 }
57 return classes
58}
59
60func favoriteFolderFilterActive(data favoritesRenderData, folderID entity.FolderID) bool {
61 return data.FolderFilter != nil && *data.FolderFilter == folderID
62}
63
64func favoriteAllFilterActive(data favoritesRenderData) bool {
65 return data.FolderFilter == nil && data.TagFilter == nil
66}
67
68func favoriteTagFilterActive(data favoritesRenderData, tagID entity.TagID) bool {
69 return data.TagFilter != nil && *data.TagFilter == tagID
70}
71
72func favoriteItemLabel(favorite *entity.Favorite) string {
73 if favorite == nil {
74 return ""
75 }
76 label := strings.TrimSpace(favorite.Title)
77 if label == "" {
78 label = favorite.URL
79 }
80 return label
81}
82
83func favoriteMetaText(favorite *entity.Favorite, folders []*entity.Folder) string {
84 if favorite == nil {
85 return ""
86 }
87 parts := make([]string, 0, 2)
88 if favorite.ShortcutKey != nil {
89 parts = append(parts, fmt.Sprintf("Shortcut %d", *favorite.ShortcutKey))
90 }
91 if name := folderNameForID(folders, favorite.FolderID); name != "" {
92 parts = append(parts, "Folder "+name)
93 }
94 return strings.Join(parts, " · ")
95}
96
97func favoriteTagButtonAction(favorite *entity.Favorite, tag *entity.Tag) string {
98 if favorite != nil && tag != nil && favorite.HasTag(tag.ID) {
99 return tagActionRemove
100 }
101 return tagActionAssign
102}
103
104func favoriteTagButtonLabel(favorite *entity.Favorite, tag *entity.Tag) string {
105 if tag == nil {
106 return ""
107 }
108 if favorite != nil && favorite.HasTag(tag.ID) {
109 return "✓ " + tag.Name
110 }
111 return "+ " + tag.Name
112}
113
114func favoriteTagButtonClass(favorite *entity.Favorite, tag *entity.Tag) string {
115 classes := "sv-button sv-button-secondary sv-tag-button"
116 if favorite != nil && tag != nil && favorite.HasTag(tag.ID) {
117 classes += " sv-button-active sv-tag-button-active"
118 }
119 return classes
120}
121
122func safeTagColor(raw string) string {
123 value := strings.TrimSpace(raw)
124 if len(value) != 4 && len(value) != 7 {
125 return defaultTagColor
126 }
127 if value[0] != '#' {
128 return defaultTagColor
129 }
130 for _, ch := range value[1:] {
131 if (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') {
132 continue
133 }
134 return defaultTagColor
135 }
136 return value
137}
138
139func folderDisplayName(folder *entity.Folder) string {
140 if folder == nil {
141 return ""
142 }
143 if strings.TrimSpace(folder.Icon) != "" {
144 return folder.Icon + " " + folder.Name
145 }
146 return folder.Name
147}
148
149func folderNameForID(folders []*entity.Folder, id *entity.FolderID) string {
150 if id == nil {
151 return "Unfiled"
152 }
153 for _, folder := range folders {
154 if folder != nil && folder.ID == *id {
155 return folderDisplayName(folder)
156 }
157 }
158 return "Unknown"
159}
160
161func folderSelected(selected *entity.FolderID, folderID entity.FolderID) bool {
162 return selected != nil && *selected == folderID
163}
164
165func shortcutSelected(selected *int, value int) bool {
166 return selected != nil && *selected == value
167}
168
169func filterFavorites(favorites []*entity.Favorite, folderID *entity.FolderID, tagID *entity.TagID) []*entity.Favorite {
170 if folderID == nil && tagID == nil {
171 return favorites
172 }
173 out := make([]*entity.Favorite, 0, len(favorites))
174 for _, favorite := range favorites {
175 if favorite == nil {
176 continue
177 }
178 if folderID != nil {
179 if *folderID == 0 {
180 if favorite.FolderID != nil {
181 continue
182 }
183 } else if favorite.FolderID == nil || *favorite.FolderID != *folderID {
184 continue
185 }
186 }
187 if tagID != nil && !favorite.HasTag(*tagID) {
188 continue
189 }
190 out = append(out, favorite)
191 }
192 return out
193}