favorites.templ RawHistoryFolder

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.Tags)
23					}
24				}
25			</ul>
26		}
27	}
28	@Section("sv-tags-list", "Tags") {
29		@Meta(tagsSummary(data))
30		@TagCreateForm()
31		if len(data.Tags) == 0 {
32			@EmptyState("No tags")
33		} else {
34			<ul class="sv-list">
35				for _, tag := range data.Tags {
36					if tag != nil {
37						@TagItem(tag)
38					}
39				}
40			</ul>
41		}
42	}
43}
44
45templ FavoritesControls(data favoritesRenderData) {
46	<form class="sv-card-form" data-sv-action="favorite.create">
47		<label><span>URL</span><input name="url" type="url" placeholder="https://example.com" required/></label>
48		<label><span>Title</span><input name="title" type="text" placeholder="Optional title"/></label>
49		@FavoriteCreateTagPicker(data.Tags)
50		<button class="sv-button" type="submit">@ButtonIconLabel(iconPlus, "Add favorite")</button>
51	</form>
52	<div class="sv-filter-panel">
53		<div class="sv-button-row">
54			<span class="sv-meta">Tags</span>
55			<button type="button" class={ favoriteFilterClass(favoriteAllFilterActive(data)) } aria-pressed={ fmt.Sprintf("%t", favoriteAllFilterActive(data)) } data-sv-action="favorite.clearFilters">All</button>
56			<button type="button" class={ favoriteFilterClass(favoriteUntaggedFilterActive(data)) } aria-pressed={ fmt.Sprintf("%t", favoriteUntaggedFilterActive(data)) } data-sv-action="favorite.filterUntagged">Untagged</button>
57			for _, tag := range data.Tags {
58				if tag != nil {
59					<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) }>
60						<span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>{ tag.Name }
61					</button>
62				}
63			}
64		</div>
65	</div>
66	<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>
67}
68
69templ FavoriteCreateTagPicker(tags []*entity.Tag) {
70	if len(tags) > 0 {
71		<fieldset class="sv-tag-picker">
72			<legend>Tags</legend>
73			<div class="sv-tag-controls">
74				for _, tag := range tags {
75					if tag != nil {
76						<label class="sv-tag-toggle">
77							<input type="checkbox" name="tags" value={ fmt.Sprintf("%d", tag.ID) }/>
78							<span><span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>{ tag.Name }</span>
79						</label>
80					}
81				}
82			</div>
83		</fieldset>
84	}
85}
86
87templ FavoriteItem(favorite *entity.Favorite, tags []*entity.Tag) {
88	<li class="sv-list-row">
89		<article class="sv-favorite-item" data-sv-favorite-row data-favorite-id={ fmt.Sprintf("%d", favorite.ID) }>
90			<div class="sv-favorite-header">
91				<div>
92					<a class="sv-link sv-favorite-open" href={ templ.SafeURL(sanitizeHref(favorite.URL)) }>{ favoriteItemLabel(favorite) }</a>
93					<p class="sv-history-url">{ favorite.URL }</p>
94					if favoriteMetaText(favorite) != "" {
95						<p class="sv-meta">{ favoriteMetaText(favorite) }</p>
96					}
97				</div>
98				<div class="sv-history-row-actions">
99					<a class="sv-button sv-button-secondary" href={ templ.SafeURL(sanitizeHref(favorite.URL)) }>@ButtonIconLabel(iconExternal, "Open")</a>
100					<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>
101				</div>
102			</div>
103			@FavoriteEditForm(favorite)
104			@FavoriteTagControls(favorite, tags)
105		</article>
106	</li>
107}
108
109templ FavoriteEditForm(favorite *entity.Favorite) {
110	<form class="sv-inline-form" data-sv-action="favorite.update">
111		<input type="hidden" name="id" value={ fmt.Sprintf("%d", favorite.ID) }/>
112		<label><span>Title</span><input name="title" type="text" value={ favorite.Title }/></label>
113		<label><span>Favicon</span><input name="favicon_url" type="url" value={ favorite.FaviconURL }/></label>
114		@ShortcutSelect(favorite.ShortcutKey)
115		<button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
116	</form>
117}
118
119templ FavoriteTagControls(favorite *entity.Favorite, tags []*entity.Tag) {
120	if len(tags) > 0 {
121		<div class="sv-tag-controls">
122			<span class="sv-meta">Tags</span>
123			for _, tag := range tags {
124				if tag != nil {
125					<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) }>
126						<span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>{ favoriteTagButtonLabel(favorite, tag) }
127					</button>
128				}
129			}
130		</div>
131	}
132}
133
134templ TagCreateForm() {
135	<form class="sv-inline-form" data-sv-action="tag.create">
136		<label><span>Name</span><input name="name" type="text" required/></label>
137		<label><span>Color</span><input name="color" type="text" placeholder="#808080"/></label>
138		<button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconTag, "Create tag")</button>
139	</form>
140}
141
142templ TagItem(tag *entity.Tag) {
143	<li class="sv-list-row">
144		<form class="sv-inline-form" data-sv-action="tag.update">
145			<input type="hidden" name="id" value={ fmt.Sprintf("%d", tag.ID) }/>
146			<span class="sv-tag-dot" style={ "background:" + safeTagColor(tag.Color) }></span>
147			<label><span>Name</span><input name="name" type="text" value={ tag.Name } required/></label>
148			<label><span>Color</span><input name="color" type="text" value={ tag.Color }/></label>
149			<button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
150			<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>
151		</form>
152	</li>
153}
154
155templ ShortcutSelect(selected *int) {
156	<label><span>Shortcut</span><select name="shortcut_key">
157		<option value="">None</option>
158		for i := 1; i <= 9; i++ {
159			<option value={ fmt.Sprintf("%d", i) } selected?={ shortcutSelected(selected, i) }>{ fmt.Sprintf("%d", i) }</option>
160		}
161	</select></label>
162}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close