1package systemviews
2
3import (
4 "fmt"
5 "github.com/bnema/dumber/internal/domain/entity"
6)
7
8templ FavoritesView(data favoritesRenderData) {
9 @Alert("success", data.Notice)
10 @Alert("error", data.Error)
11 @Section("sv-favorites-controls", "Manage favorites") {
12 @FavoritesControls(data)
13 }
14 @Section("sv-favorites-list", "Favorites") {
15 @Meta(favoritesSummary(data))
16 if len(data.Favorites) == 0 {
17 @EmptyState("No favorites")
18 } else {
19 <ul class="sv-list">
20 for _, favorite := range data.Favorites {
21 if favorite != nil {
22 @FavoriteItem(favorite, data.Folders, data.Tags)
23 }
24 }
25 </ul>
26 }
27 }
28 @Section("sv-folders-list", "Folders") {
29 @Meta(foldersSummary(data))
30 @FolderCreateForm()
31 if len(data.Folders) == 0 {
32 @EmptyState("No folders")
33 } else {
34 <ul class="sv-list">
35 for _, folder := range data.Folders {
36 if folder != nil {
37 @FolderItem(folder)
38 }
39 }
40 </ul>
41 }
42 }
43 @Section("sv-tags-list", "Tags") {
44 @Meta(tagsSummary(data))
45 @TagCreateForm()
46 if len(data.Tags) == 0 {
47 @EmptyState("No tags")
48 } else {
49 <ul class="sv-list">
50 for _, tag := range data.Tags {
51 if tag != nil {
52 @TagItem(tag)
53 }
54 }
55 </ul>
56 }
57 }
58}
59
60templ FavoritesControls(data favoritesRenderData) {
61 <form class="sv-card-form" data-sv-action="favorite.create">
62 <label><span>URL</span><input name="url" type="url" placeholder="https://example.com" required/></label>
63 <label><span>Title</span><input name="title" type="text" placeholder="Optional title"/></label>
64 @FolderSelect("folder_id", data.Folders, nil, "Folder")
65 @FavoriteCreateTagPicker(data.Tags)
66 <button class="sv-button" type="submit">@ButtonIconLabel(iconPlus, "Add favorite")</button>
67 </form>
68 <div class="sv-filter-panel">
69 <div class="sv-button-row">
70 <span class="sv-meta">Folders</span>
71 <button type="button" class={ favoriteFilterClass(favoriteAllFilterActive(data)) } aria-pressed={ fmt.Sprintf("%t", favoriteAllFilterActive(data)) } data-sv-action="favorite.clearFilters">All</button>
72 <button type="button" class={ favoriteFilterClass(favoriteFolderFilterActive(data, entity.FolderID(0))) } aria-pressed={ fmt.Sprintf("%t", favoriteFolderFilterActive(data, entity.FolderID(0))) } data-sv-action="favorite.filterFolder" data-folder-id="root">Unfiled</button>
73 for _, folder := range data.Folders {
74 if folder != nil {
75 <button type="button" class={ favoriteFilterClass(favoriteFolderFilterActive(data, folder.ID)) } aria-pressed={ fmt.Sprintf("%t", favoriteFolderFilterActive(data, folder.ID)) } data-sv-action="favorite.filterFolder" data-folder-id={ fmt.Sprintf("%d", folder.ID) }>{ folderDisplayName(folder) }</button>
76 }
77 }
78 </div>
79 <div class="sv-button-row">
80 <span class="sv-meta">Tags</span>
81 for _, tag := range data.Tags {
82 if tag != nil {
83 <button type="button" class={ favoriteFilterClass(favoriteTagFilterActive(data, tag.ID)) } aria-pressed={ fmt.Sprintf("%t", favoriteTagFilterActive(data, tag.ID)) } data-sv-action="favorite.filterTag" data-tag-id={ fmt.Sprintf("%d", tag.ID) }>
84 <span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>{ tag.Name }
85 </button>
86 }
87 }
88 </div>
89 </div>
90 <p class="sv-meta sv-key-hints">Keys: <kbd>j</kbd>/<kbd>k</kbd> move, <kbd>Enter</kbd> open, <kbd>e</kbd> edit title, <kbd>d</kbd> delete focused favorite.</p>
91}
92
93templ FavoriteCreateTagPicker(tags []*entity.Tag) {
94 if len(tags) > 0 {
95 <fieldset class="sv-tag-picker">
96 <legend>Tags</legend>
97 <div class="sv-tag-controls">
98 for _, tag := range tags {
99 if tag != nil {
100 <label class="sv-tag-toggle">
101 <input type="checkbox" name="tags" value={ fmt.Sprintf("%d", tag.ID) }/>
102 <span><span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>{ tag.Name }</span>
103 </label>
104 }
105 }
106 </div>
107 </fieldset>
108 }
109}
110
111templ FavoriteItem(favorite *entity.Favorite, folders []*entity.Folder, tags []*entity.Tag) {
112 <li class="sv-list-row">
113 <article class="sv-favorite-item" data-sv-favorite-row data-favorite-id={ fmt.Sprintf("%d", favorite.ID) }>
114 <div class="sv-favorite-header">
115 <div>
116 <a class="sv-link sv-favorite-open" href={ templ.SafeURL(sanitizeHref(favorite.URL)) }>{ favoriteItemLabel(favorite) }</a>
117 <p class="sv-history-url">{ favorite.URL }</p>
118 if favoriteMetaText(favorite, folders) != "" {
119 <p class="sv-meta">{ favoriteMetaText(favorite, folders) }</p>
120 }
121 </div>
122 <div class="sv-history-row-actions">
123 <a class="sv-button sv-button-secondary" href={ templ.SafeURL(sanitizeHref(favorite.URL)) }>@ButtonIconLabel(iconExternal, "Open")</a>
124 <button type="button" class="sv-button sv-button-secondary sv-button-danger sv-button-icon-only" data-sv-action="favorite.delete" data-id={ fmt.Sprintf("%d", favorite.ID) } data-sv-confirm="Delete this favorite?" aria-label="Delete this favorite">@IconOnly(iconTrash, "Delete this favorite")</button>
125 </div>
126 </div>
127 @FavoriteEditForm(favorite, folders)
128 @FavoriteTagControls(favorite, tags)
129 </article>
130 </li>
131}
132
133templ FavoriteEditForm(favorite *entity.Favorite, folders []*entity.Folder) {
134 <form class="sv-inline-form" data-sv-action="favorite.update">
135 <input type="hidden" name="id" value={ fmt.Sprintf("%d", favorite.ID) }/>
136 <label><span>Title</span><input name="title" type="text" value={ favorite.Title }/></label>
137 <label><span>Favicon</span><input name="favicon_url" type="url" value={ favorite.FaviconURL }/></label>
138 @FolderSelect("folder_id", folders, favorite.FolderID, "Folder")
139 @ShortcutSelect(favorite.ShortcutKey)
140 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
141 </form>
142}
143
144templ FavoriteTagControls(favorite *entity.Favorite, tags []*entity.Tag) {
145 if len(tags) > 0 {
146 <div class="sv-tag-controls">
147 <span class="sv-meta">Tags</span>
148 for _, tag := range tags {
149 if tag != nil {
150 <button type="button" class={ favoriteTagButtonClass(favorite, tag) } data-sv-action={ favoriteTagButtonAction(favorite, tag) } data-favorite-id={ fmt.Sprintf("%d", favorite.ID) } data-tag-id={ fmt.Sprintf("%d", tag.ID) }>
151 <span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>{ favoriteTagButtonLabel(favorite, tag) }
152 </button>
153 }
154 }
155 </div>
156 }
157}
158
159templ FolderCreateForm() {
160 <form class="sv-inline-form" data-sv-action="folder.create">
161 <label><span>Name</span><input name="name" type="text" required/></label>
162 <label><span>Icon</span><input name="icon" type="text" placeholder="Optional"/></label>
163 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconFolder, "Create folder")</button>
164 </form>
165}
166
167templ FolderItem(folder *entity.Folder) {
168 <li class="sv-list-row">
169 <form class="sv-inline-form" data-sv-action="folder.update">
170 <input type="hidden" name="id" value={ fmt.Sprintf("%d", folder.ID) }/>
171 <label><span>Name</span><input name="name" type="text" value={ folder.Name } required/></label>
172 <label><span>Icon</span><input name="icon" type="text" value={ folder.Icon }/></label>
173 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
174 <button type="button" class="sv-button sv-button-secondary sv-button-danger sv-button-icon-only" data-sv-action="folder.delete" data-id={ fmt.Sprintf("%d", folder.ID) } data-sv-confirm="Delete this folder? Favorites will move out of it." aria-label="Delete this folder">@IconOnly(iconTrash, "Delete this folder")</button>
175 </form>
176 </li>
177}
178
179templ TagCreateForm() {
180 <form class="sv-inline-form" data-sv-action="tag.create">
181 <label><span>Name</span><input name="name" type="text" required/></label>
182 <label><span>Color</span><input name="color" type="text" placeholder="#808080"/></label>
183 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconTag, "Create tag")</button>
184 </form>
185}
186
187templ TagItem(tag *entity.Tag) {
188 <li class="sv-list-row">
189 <form class="sv-inline-form" data-sv-action="tag.update">
190 <input type="hidden" name="id" value={ fmt.Sprintf("%d", tag.ID) }/>
191 <span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>
192 <label><span>Name</span><input name="name" type="text" value={ tag.Name } required/></label>
193 <label><span>Color</span><input name="color" type="text" value={ tag.Color }/></label>
194 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
195 <button type="button" class="sv-button sv-button-secondary sv-button-danger sv-button-icon-only" data-sv-action="tag.delete" data-id={ fmt.Sprintf("%d", tag.ID) } data-sv-confirm="Delete this tag?" aria-label="Delete this tag">@IconOnly(iconTrash, "Delete this tag")</button>
196 </form>
197 </li>
198}
199
200templ FolderSelect(name string, folders []*entity.Folder, selected *entity.FolderID, label string) {
201 <label><span>{ label }</span><select name={ name }>
202 <option value="">Unfiled</option>
203 for _, folder := range folders {
204 if folder != nil {
205 <option value={ fmt.Sprintf("%d", folder.ID) } selected?={ folderSelected(selected, folder.ID) }>{ folderDisplayName(folder) }</option>
206 }
207 }
208 </select></label>
209}
210
211templ ShortcutSelect(selected *int) {
212 <label><span>Shortcut</span><select name="shortcut_key">
213 <option value="">None</option>
214 for i := 1; i <= 9; i++ {
215 <option value={ fmt.Sprintf("%d", i) } selected?={ shortcutSelected(selected, i) }>{ fmt.Sprintf("%d", i) }</option>
216 }
217 </select></label>
218}