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: add logic and styles
finxol.io
1 month ago
63c04e93
4d1711ca
verified
This commit was signed with the committer's
known signature
.
finxol.io
SSH Key Fingerprint:
SHA256:olFE3asYdoBMScuJOt60UxXdJ0RFdGv5kVKrdOtIcPI=
+213
-17
3 changed files
expand all
collapse all
unified
split
src
components
BookmarkEditModal.css
BookmarkEditModal.tsx
routes
index.tsx
+84
-3
src/components/BookmarkEditModal.css
reviewed
···
4
4
margin: auto;
5
5
width: min(90vw, 32rem);
6
6
max-height: min(80vh, 600px);
7
7
-
padding: 1.5rem;
7
7
+
padding: calc(var(--spacing) * 3);
8
8
border: none;
9
9
-
border-radius: 0.75rem;
9
9
+
border-radius: var(--radius);
10
10
background: var(--page-bg);
11
11
color: var(--primary-text);
12
12
flex-direction: column;
13
13
-
gap: 1rem;
13
13
+
gap: calc(var(--spacing) * 1);
14
14
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
15
15
16
16
&:popover-open {
···
21
21
background: rgba(0, 0, 0, 0.5);
22
22
backdrop-filter: blur(4px);
23
23
}
24
24
+
25
25
+
& > .button-ghost {
26
26
+
align-self: flex-end;
27
27
+
}
28
28
+
29
29
+
& > div.title {
30
30
+
display: flex;
31
31
+
align-items: center;
32
32
+
justify-content: space-between;
33
33
+
margin-bottom: calc(var(--spacing) * 0.5);
34
34
+
35
35
+
h2 {
36
36
+
font-size: 1.25rem;
37
37
+
font-weight: 700;
38
38
+
}
39
39
+
}
40
40
+
}
41
41
+
42
42
+
.edit-form {
43
43
+
display: flex;
44
44
+
flex-direction: column;
45
45
+
gap: calc(var(--spacing) * 2);
46
46
+
47
47
+
.input-group {
48
48
+
display: flex;
49
49
+
flex-direction: column;
50
50
+
gap: calc(var(--spacing) * 0.5);
51
51
+
52
52
+
label {
53
53
+
font-size: 0.8rem;
54
54
+
font-weight: 600;
55
55
+
color: oklch(from var(--primary-text) l c h / 0.7);
56
56
+
}
57
57
+
58
58
+
input,
59
59
+
textarea {
60
60
+
width: 100%;
61
61
+
padding: calc(var(--spacing) * 1);
62
62
+
border: 1px solid oklch(from var(--primary-text) l c h / 0.15);
63
63
+
border-radius: var(--radius);
64
64
+
font-size: 0.9rem;
65
65
+
font-family: inherit;
66
66
+
color: var(--primary-text);
67
67
+
background: transparent;
68
68
+
outline: none;
69
69
+
transition: all 0.15s ease;
70
70
+
71
71
+
&::placeholder {
72
72
+
color: oklch(from var(--primary-text) l c h / 0.3);
73
73
+
}
74
74
+
75
75
+
&:focus {
76
76
+
border-color: var(--primary);
77
77
+
box-shadow: 0 0 0 3px oklch(from var(--primary) l c h / 0.12);
78
78
+
}
79
79
+
80
80
+
&:disabled {
81
81
+
opacity: 0.6;
82
82
+
cursor: not-allowed;
83
83
+
}
84
84
+
}
85
85
+
86
86
+
textarea {
87
87
+
resize: vertical;
88
88
+
min-height: 5rem;
89
89
+
}
90
90
+
}
91
91
+
92
92
+
button[type="submit"] {
93
93
+
align-self: flex-end;
94
94
+
}
95
95
+
}
96
96
+
97
97
+
.bookmark-edit-modal .error-message {
98
98
+
margin-top: calc(var(--spacing) * 0.5);
99
99
+
padding: calc(var(--spacing) * 1);
100
100
+
background: oklch(0.65 0.25 25 / 0.08);
101
101
+
border: 1px solid oklch(0.65 0.25 25 / 0.2);
102
102
+
border-radius: var(--radius);
103
103
+
color: oklch(0.5 0.2 25);
104
104
+
font-size: 0.85rem;
24
105
}
+125
-13
src/components/BookmarkEditModal.tsx
reviewed
···
1
1
-
import { PencilIcon, XIcon } from "lucide-solid"
1
1
+
import { LoaderIcon, PencilIcon, XIcon } from "lucide-solid"
2
2
+
import { useMutation, useQueryClient } from "@tanstack/solid-query"
3
3
+
import { createSignal, Show } from "solid-js"
4
4
+
import { client } from "../apiclient.ts"
5
5
+
import { Bookmark } from "../../server/utils/bookmarks.ts"
2
6
import "./BookmarkEditModal.css"
7
7
+
import "./ui/button.css"
8
8
+
9
9
+
interface BookmarkEditModalProps {
10
10
+
bookmark: Bookmark & { id: string }
11
11
+
}
12
12
+
13
13
+
export function BookmarkEditModal(props: BookmarkEditModalProps) {
14
14
+
const queryClient = useQueryClient()
15
15
+
const [title, setTitle] = createSignal(props.bookmark.title)
16
16
+
const [description, setDescription] = createSignal(
17
17
+
props.bookmark.description,
18
18
+
)
3
19
4
4
-
export function BookmarkEditModal(props: { id: string }) {
20
20
+
const editBookmark = useMutation(() => ({
21
21
+
mutationFn: async () => {
22
22
+
const res = await client.api.v1.bookmarks[":id"].$put({
23
23
+
param: { id: props.bookmark.id },
24
24
+
json: {
25
25
+
title: title().trim(),
26
26
+
description: description().trim(),
27
27
+
},
28
28
+
})
29
29
+
if (!res.ok) {
30
30
+
const data = await res.json()
31
31
+
throw new Error(
32
32
+
"error" in data ? data.error : "Failed to update bookmark",
33
33
+
)
34
34
+
}
35
35
+
return await res.json()
36
36
+
},
37
37
+
onSuccess: async () => {
38
38
+
await queryClient.invalidateQueries({
39
39
+
queryKey: [client.api.v1.bookmarks.all.$url().pathname],
40
40
+
})
41
41
+
// Close the modal
42
42
+
const dialog = document.getElementById(
43
43
+
`edit-${props.bookmark.id}`,
44
44
+
) as HTMLDialogElement | null
45
45
+
dialog?.requestClose()
46
46
+
},
47
47
+
}))
48
48
+
49
49
+
const handleSubmit = (e: SubmitEvent) => {
50
50
+
e.preventDefault()
51
51
+
const titleValue = title().trim()
52
52
+
if (titleValue) {
53
53
+
editBookmark.mutate()
54
54
+
}
55
55
+
}
56
56
+
5
57
return (
6
58
<>
7
59
<button
8
60
type="button"
9
61
command="show-modal"
10
10
-
commandfor={`edit-${props.id}`}
62
62
+
commandfor={`edit-${props.bookmark.id}`}
11
63
class="bookmark-edit button-icon button-ghost"
12
64
>
13
65
<PencilIcon size={16} />
14
66
</button>
15
15
-
<dialog id={`edit-${props.id}`} class="bookmark-edit-modal">
16
16
-
<button
17
17
-
type="button"
18
18
-
commandfor={`edit-${props.id}`}
19
19
-
command="close"
20
20
-
class="button-ghost button-icon"
21
21
-
>
22
22
-
<XIcon size={16} />
23
23
-
</button>
24
24
-
Edit
67
67
+
<dialog
68
68
+
id={`edit-${props.bookmark.id}`}
69
69
+
class="bookmark-edit-modal"
70
70
+
>
71
71
+
<div class="title">
72
72
+
<h2>Edit bookmark</h2>
73
73
+
74
74
+
<button
75
75
+
type="button"
76
76
+
commandfor={`edit-${props.bookmark.id}`}
77
77
+
command="close"
78
78
+
class="button-ghost button-icon"
79
79
+
>
80
80
+
<XIcon size={16} />
81
81
+
</button>
82
82
+
</div>
83
83
+
84
84
+
<form onSubmit={handleSubmit} class="edit-form">
85
85
+
<div class="input-group">
86
86
+
<label for={`title-${props.bookmark.id}`}>Title</label>
87
87
+
<input
88
88
+
id={`title-${props.bookmark.id}`}
89
89
+
type="text"
90
90
+
required
91
91
+
value={title()}
92
92
+
onInput={(e) => setTitle(e.currentTarget.value)}
93
93
+
disabled={editBookmark.isPending}
94
94
+
maxlength="200"
95
95
+
/>
96
96
+
</div>
97
97
+
98
98
+
<div class="input-group">
99
99
+
<label for={`description-${props.bookmark.id}`}>
100
100
+
Description
101
101
+
</label>
102
102
+
<textarea
103
103
+
id={`description-${props.bookmark.id}`}
104
104
+
value={description()}
105
105
+
onInput={(e) =>
106
106
+
setDescription(e.currentTarget.value)}
107
107
+
disabled={editBookmark.isPending}
108
108
+
maxlength="500"
109
109
+
/>
110
110
+
</div>
111
111
+
112
112
+
<button
113
113
+
type="submit"
114
114
+
class="button"
115
115
+
disabled={editBookmark.isPending || !title()?.trim()}
116
116
+
>
117
117
+
<Show
118
118
+
when={!editBookmark.isPending}
119
119
+
fallback={
120
120
+
<>
121
121
+
<LoaderIcon size={16} class="spinner" />
122
122
+
Saving...
123
123
+
</>
124
124
+
}
125
125
+
>
126
126
+
Save changes
127
127
+
</Show>
128
128
+
</button>
129
129
+
</form>
130
130
+
131
131
+
<Show when={editBookmark.isError}>
132
132
+
<div class="error-message">
133
133
+
{editBookmark.error?.message ??
134
134
+
"Failed to update bookmark"}
135
135
+
</div>
136
136
+
</Show>
25
137
</dialog>
26
138
</>
27
139
)
+4
-1
src/routes/index.tsx
reviewed
···
155
155
<ExternalLinkIcon size={16} />
156
156
</a>
157
157
<BookmarkEditModal
158
158
-
id={String(bookmark.id)}
158
158
+
bookmark={{
159
159
+
...bookmark,
160
160
+
id: String(bookmark.id),
161
161
+
}}
159
162
/>
160
163
<button
161
164
type="button"