tangled
alpha
login
or
join now
finxol.io
/
bookmarker
0
fork
atom
A very simple bookmarking webapp
bookmarker.finxol.deno.net/
0
fork
atom
overview
issues
pulls
pipelines
feat: improve new bookmark page
finxol.io
1 month ago
8a870c65
e50e5c63
verified
This commit was signed with the committer's
known signature
.
finxol.io
SSH Key Fingerprint:
SHA256:olFE3asYdoBMScuJOt60UxXdJ0RFdGv5kVKrdOtIcPI=
1/1
deploy.yaml
success
8s
+209
-25
4 changed files
expand all
collapse all
unified
split
src
routes
index.tsx
new.css
new.tsx
root.css
+1
-1
src/routes/index.tsx
···
147
title="Delete bookmark"
148
onClick={() =>
149
deleteBookmark.mutate(
150
-
bookmark.id,
151
)}
152
disabled={deleteBookmark.isPending}
153
>
···
147
title="Delete bookmark"
148
onClick={() =>
149
deleteBookmark.mutate(
150
+
String(bookmark.id),
151
)}
152
disabled={deleteBookmark.isPending}
153
>
+148
src/routes/new.css
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
.new-page {
2
+
padding: calc(var(--spacing) * 2);
3
+
max-width: 520px;
4
+
margin: 0 auto;
5
+
width: 100%;
6
+
}
7
+
8
+
.back-link {
9
+
display: inline-flex;
10
+
align-items: center;
11
+
gap: calc(var(--spacing) * 0.5);
12
+
font-size: 0.85rem;
13
+
font-weight: 500;
14
+
color: oklch(from var(--primary-text) l c h / 0.5);
15
+
text-decoration: none;
16
+
margin-bottom: calc(var(--spacing) * 3);
17
+
transition: color 0.15s ease;
18
+
19
+
.back-link:hover {
20
+
color: var(--primary-text);
21
+
}
22
+
}
23
+
24
+
.new-card {
25
+
background: oklch(from var(--primary-text) l c h / 0.1);
26
+
border: 1px solid oklch(from var(--primary-text) l c h / 0.1);
27
+
border-radius: var(--radius);
28
+
padding: calc(var(--spacing) * 3);
29
+
30
+
h2 {
31
+
font-size: 1.25rem;
32
+
font-weight: 700;
33
+
margin-bottom: calc(var(--spacing) * 0.5);
34
+
}
35
+
36
+
.new-subtitle {
37
+
font-size: 0.875rem;
38
+
color: oklch(from var(--primary-text) l c h / 0.7);
39
+
margin-bottom: calc(var(--spacing) * 2.5);
40
+
text-wrap: balance;
41
+
text-wrap: pretty;
42
+
}
43
+
}
44
+
45
+
.new-form {
46
+
display: flex;
47
+
flex-direction: column;
48
+
gap: calc(var(--spacing) * 2);
49
+
}
50
+
51
+
.input-group {
52
+
display: flex;
53
+
flex-direction: column;
54
+
gap: calc(var(--spacing) * 0.5);
55
+
56
+
label {
57
+
font-size: 0.8rem;
58
+
font-weight: 600;
59
+
color: oklch(from var(--primary-text) l c h / 0.7);
60
+
}
61
+
}
62
+
63
+
.input-wrapper {
64
+
position: relative;
65
+
display: flex;
66
+
align-items: center;
67
+
68
+
.input-icon {
69
+
position: absolute;
70
+
left: calc(var(--spacing) * 1);
71
+
color: oklch(from var(--primary-text) l c h / 0.3);
72
+
pointer-events: none;
73
+
}
74
+
75
+
input {
76
+
width: 100%;
77
+
padding: calc(var(--spacing) * 1) calc(var(--spacing) * 1);
78
+
padding-left: calc(var(--spacing) * 3);
79
+
border: 1px solid oklch(from var(--primary-text) l c h / 0.15);
80
+
border-radius: var(--radius);
81
+
font-size: 0.9rem;
82
+
color: var(--primary-text);
83
+
background: transparent;
84
+
outline: none;
85
+
transition:
86
+
border-color 0.15s ease,
87
+
box-shadow 0.15s ease;
88
+
89
+
&::placeholder {
90
+
color: oklch(from var(--primary-text) l c h / 0.3);
91
+
}
92
+
93
+
&:focus {
94
+
border-color: var(--primary);
95
+
box-shadow: 0 0 0 3px oklch(from var(--primary) l c h / 0.12);
96
+
}
97
+
98
+
&:disabled {
99
+
opacity: 0.6;
100
+
cursor: not-allowed;
101
+
}
102
+
}
103
+
}
104
+
105
+
.submit-button {
106
+
display: inline-flex;
107
+
align-items: center;
108
+
justify-content: center;
109
+
gap: calc(var(--spacing) * 0.5);
110
+
padding: calc(var(--spacing) * 1) calc(var(--spacing) * 2);
111
+
background-color: var(--primary);
112
+
color: white;
113
+
border: none;
114
+
border-radius: var(--radius);
115
+
font-weight: 600;
116
+
font-size: 0.875rem;
117
+
cursor: pointer;
118
+
transition: opacity 0.15s ease;
119
+
}
120
+
121
+
.submit-button:hover:not(:disabled) {
122
+
opacity: 0.85;
123
+
}
124
+
125
+
.submit-button:disabled {
126
+
opacity: 0.5;
127
+
cursor: not-allowed;
128
+
}
129
+
130
+
.error-message {
131
+
margin-top: calc(var(--spacing) * 1.5);
132
+
padding: calc(var(--spacing) * 1);
133
+
background: oklch(0.65 0.25 25 / 0.08);
134
+
border: 1px solid oklch(0.65 0.25 25 / 0.2);
135
+
border-radius: var(--radius);
136
+
color: oklch(0.5 0.2 25);
137
+
font-size: 0.85rem;
138
+
}
139
+
140
+
.spinner {
141
+
animation: spin 1s linear infinite;
142
+
}
143
+
144
+
@keyframes spin {
145
+
to {
146
+
transform: rotate(360deg);
147
+
}
148
+
}
+58
-22
src/routes/new.tsx
···
1
import { createFileRoute, useNavigate } from "@tanstack/solid-router"
2
import { useMutation, useQueryClient } from "@tanstack/solid-query"
3
-
import { createSignal } from "solid-js"
4
import { client } from "../apiclient.ts"
0
0
0
5
6
export const Route = createFileRoute("/new")({
7
component: RouteComponent,
···
42
}
43
44
return (
45
-
<div>
46
-
<h2>Add a new bookmark</h2>
47
-
<form onSubmit={handleSubmit}>
48
-
<label for="url">URL</label>
49
-
<input
50
-
id="url"
51
-
type="url"
52
-
required
53
-
placeholder="https://example.com"
54
-
value={url()}
55
-
onInput={(e) => setUrl(e.currentTarget.value)}
56
-
disabled={addBookmark.isPending}
57
-
/>
58
-
<button type="submit" disabled={addBookmark.isPending}>
59
-
{addBookmark.isPending ? "Adding..." : "Add bookmark"}
60
-
</button>
61
-
</form>
62
-
{addBookmark.isError && (
63
-
<p style="color: red;">
64
-
{addBookmark.error?.message ?? "Failed to add bookmark"}
65
</p>
66
-
)}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
67
</div>
68
)
69
}
···
1
import { createFileRoute, useNavigate } from "@tanstack/solid-router"
2
import { useMutation, useQueryClient } from "@tanstack/solid-query"
3
+
import { createSignal, Show } from "solid-js"
4
import { client } from "../apiclient.ts"
5
+
import { ArrowLeftIcon, LinkIcon, LoaderIcon } from "lucide-solid"
6
+
import { Link } from "@tanstack/solid-router"
7
+
import "./new.css"
8
9
export const Route = createFileRoute("/new")({
10
component: RouteComponent,
···
45
}
46
47
return (
48
+
<div class="new-page">
49
+
<Link to="/" class="back-link">
50
+
<ArrowLeftIcon size={16} />
51
+
Back to bookmarks
52
+
</Link>
53
+
54
+
<div class="new-card">
55
+
<h2>Add a new bookmark</h2>
56
+
<p class="new-subtitle">
57
+
Paste a URL and we'll grab the title, description, and image
58
+
for you.
0
0
0
0
0
0
0
0
0
59
</p>
60
+
61
+
<form onSubmit={handleSubmit} class="new-form">
62
+
<div class="input-group">
63
+
<label for="url">URL</label>
64
+
<div class="input-wrapper">
65
+
<LinkIcon size={16} class="input-icon" />
66
+
<input
67
+
id="url"
68
+
type="url"
69
+
required
70
+
placeholder="https://example.com"
71
+
value={url()}
72
+
onInput={(e) => setUrl(e.currentTarget.value)}
73
+
disabled={addBookmark.isPending}
74
+
/>
75
+
</div>
76
+
</div>
77
+
78
+
<button
79
+
type="submit"
80
+
class="submit-button"
81
+
disabled={addBookmark.isPending || !url().trim()}
82
+
>
83
+
<Show
84
+
when={!addBookmark.isPending}
85
+
fallback={
86
+
<>
87
+
<LoaderIcon size={16} class="spinner" />
88
+
Saving...
89
+
</>
90
+
}
91
+
>
92
+
Save bookmark
93
+
</Show>
94
+
</button>
95
+
</form>
96
+
97
+
<Show when={addBookmark.isError}>
98
+
<div class="error-message">
99
+
{addBookmark.error?.message ?? "Failed to add bookmark"}
100
+
</div>
101
+
</Show>
102
+
</div>
103
</div>
104
)
105
}
+2
-2
src/routes/root.css
···
20
--spacing: 0.75rem;
21
--radius: 0.5rem;
22
23
-
@media screen and (prefers-color-scheme: dark) {
24
--primary: var(--fuchsia-900);
25
-
--primary-text: var(--fuchsia-300);
26
--secondary: var(--fuchsia-900);
27
--secondary-text: var(--teal-300);
28
--accent: var(--orange-900);
···
20
--spacing: 0.75rem;
21
--radius: 0.5rem;
22
23
+
@media screen and (prefers-color-scheme: light) {
24
--primary: var(--fuchsia-900);
25
+
--primary-text: var(--fuchsia-200);
26
--secondary: var(--fuchsia-900);
27
--secondary-text: var(--teal-300);
28
--accent: var(--orange-900);