tangled
alpha
login
or
join now
futur.blue
/
pdsls
forked from
pds.ls/pdsls
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
record menu
handle.invalid
6 months ago
5d495d60
ba872c83
+60
-57
14 changed files
expand all
collapse all
unified
split
src
components
account.tsx
button.tsx
create.tsx
dropdown.tsx
editor.tsx
search.tsx
text-input.tsx
tooltip.tsx
layout.tsx
views
collection.tsx
labels.tsx
record.tsx
repo.tsx
stream.tsx
+2
-2
src/components/account.tsx
···
68
68
return (
69
69
<>
70
70
<Modal open={openManager()} onClose={() => setOpenManager(false)}>
71
71
-
<div class="dark:bg-dark-800/70 dark:shadow-dark-900/80 absolute top-12 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-200/70 p-4 text-neutral-900 shadow-md backdrop-blur-xs transition-opacity duration-300 dark:border-neutral-700 dark:text-neutral-200 starting:opacity-0">
71
71
+
<div class="dark:bg-dark-800/70 dark:shadow-dark-800 absolute top-12 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-200/70 p-4 text-neutral-900 shadow-md backdrop-blur-xs transition-opacity duration-300 dark:border-neutral-700 dark:text-neutral-200 starting:opacity-0">
72
72
<div class="mb-2 flex items-center gap-1 font-semibold">
73
73
<span class="iconify lucide--user-round"></span>
74
74
<span>Manage accounts</span>
···
113
113
class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
114
114
>
115
115
{agent() && avatar() ?
116
116
-
<img src={avatar()} class="dark:shadow-dark-900/80 size-5 rounded-full shadow-sm" />
116
116
+
<img src={avatar()} class="dark:shadow-dark-800 size-5 rounded-full shadow-sm" />
117
117
: <span class="iconify lucide--circle-user-round text-xl"></span>}
118
118
</button>
119
119
</>
+1
-1
src/components/button.tsx
···
12
12
type="button"
13
13
class={
14
14
props.class ??
15
15
-
"dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 dark:active:bg-dark-100 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-semibold shadow-sm hover:bg-neutral-50 active:bg-neutral-50"
15
15
+
"dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-800 dark:active:bg-dark-100 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-semibold shadow-sm hover:bg-neutral-50 active:bg-neutral-50"
16
16
}
17
17
onClick={props.onClick}
18
18
>
+3
-3
src/components/create.tsx
···
164
164
return (
165
165
<>
166
166
<Modal open={openDialog()} onClose={() => setOpenDialog(false)} closeOnClick={false}>
167
167
-
<div class="dark:bg-dark-800/70 dark:shadow-dark-900/80 absolute top-12 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-200/70 p-2 text-neutral-900 shadow-md backdrop-blur-xs transition-opacity duration-300 sm:w-xl sm:p-4 lg:w-[48rem] dark:border-neutral-700 dark:text-neutral-200 starting:opacity-0">
167
167
+
<div class="dark:bg-dark-800/70 dark:shadow-dark-800 absolute top-12 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-200/70 p-2 text-neutral-900 shadow-md backdrop-blur-xs transition-opacity duration-300 sm:w-xl sm:p-4 lg:w-[48rem] dark:border-neutral-700 dark:text-neutral-200 starting:opacity-0">
168
168
<div class="mb-2 flex w-full justify-between">
169
169
<div class="flex items-center gap-1 font-semibold">
170
170
<span class="iconify lucide--square-pen"></span>
···
202
202
<select
203
203
name="validate"
204
204
id="validate"
205
205
-
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 rounded-lg bg-white px-1 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
205
205
+
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-800 rounded-lg bg-white px-1 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
206
206
>
207
207
<option value="unset">Unset</option>
208
208
<option value="true">True</option>
···
211
211
</div>
212
212
<div class="flex items-center gap-2">
213
213
<Show when={!uploading()}>
214
214
-
<div class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-900/80 dark:active:bg-dark-100 flex rounded-lg bg-white text-xs font-semibold shadow-sm hover:bg-neutral-50 active:bg-neutral-50">
214
214
+
<div class="dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-800 dark:active:bg-dark-100 flex rounded-lg bg-white text-xs font-semibold shadow-sm hover:bg-neutral-50 active:bg-neutral-50">
215
215
<input type="file" id="blob" hidden onChange={() => uploadBlob()} />
216
216
<label class="flex items-center gap-1 px-2 py-1.5" for="blob">
217
217
<span class="iconify lucide--upload text-sm"></span>
+12
-8
src/components/dropdown.tsx
···
24
24
return <MenuContext.Provider value={value}>{props.children}</MenuContext.Provider>;
25
25
};
26
26
27
27
-
export const CopyMenu = (props: { copyContent: string; label: string }) => {
27
27
+
export const CopyMenu = (props: { copyContent: string; label: string; icon?: string }) => {
28
28
const ctx = useContext(MenuContext);
29
29
30
30
return (
···
33
33
addToClipboard(props.copyContent);
34
34
ctx?.setShowMenu(false);
35
35
}}
36
36
-
class="flex rounded-lg p-1 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
36
36
+
class="flex items-center gap-1.5 rounded-lg p-1 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
37
37
>
38
38
-
{props.label}
38
38
+
<Show when={props.icon}>
39
39
+
<span class={"iconify shrink-0 " + props.icon}></span>
40
40
+
</Show>
41
41
+
<span class="whitespace-nowrap">{props.label}</span>
39
42
</button>
40
43
);
41
44
};
42
45
43
43
-
export const NavMenu = (props: { href: string; label: string; icon: string }) => {
46
46
+
export const NavMenu = (props: { href: string; label: string; icon: string; newTab?: boolean }) => {
44
47
const ctx = useContext(MenuContext);
45
48
46
49
return (
47
50
<A
48
51
href={props.href}
49
52
onClick={() => ctx?.setShowMenu(false)}
50
50
-
class="flex items-center gap-1 rounded-lg p-1 hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
53
53
+
class="flex items-center gap-1.5 rounded-lg p-1 hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
54
54
+
target={props.newTab ? "_blank" : undefined}
51
55
>
52
52
-
<span class={"iconify " + props.icon}></span>
53
53
-
<span>{props.label}</span>
56
56
+
<span class={"iconify shrink-0 " + props.icon}></span>
57
57
+
<span class="whitespace-nowrap">{props.label}</span>
54
58
</A>
55
59
);
56
60
};
···
89
93
<div
90
94
ref={setMenu}
91
95
class={
92
92
-
"dark:bg-dark-300 absolute right-0 z-20 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 shadow-md dark:border-neutral-700 " +
96
96
+
"dark:bg-dark-300 dark:shadow-dark-800 absolute right-0 z-20 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 shadow-md dark:border-neutral-700 " +
93
97
props.menuClass
94
98
}
95
99
>
+1
-1
src/components/editor.tsx
···
54
54
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", themeEvent),
55
55
);
56
56
57
57
-
return <div ref={editorDiv} class="dark:shadow-dark-900/80 shadow-sm"></div>;
57
57
+
return <div ref={editorDiv} class="dark:shadow-dark-800 shadow-sm"></div>;
58
58
};
59
59
60
60
export { Editor };
+1
-1
src/components/search.tsx
···
55
55
PDS URL, AT URI, or handle
56
56
</label>
57
57
<div class="flex w-full items-center gap-2">
58
58
-
<div class="dark:bg-dark-100 dark:shadow-dark-900/80 flex grow items-center gap-2 rounded-lg bg-white px-2 py-1 shadow-sm focus-within:outline-[1.5px] focus-within:outline-neutral-900 dark:focus-within:outline-neutral-200">
58
58
+
<div class="dark:bg-dark-100 dark:shadow-dark-800 flex grow items-center gap-2 rounded-lg bg-white px-2 py-1 shadow-sm focus-within:outline-[1.5px] focus-within:outline-neutral-900 dark:focus-within:outline-neutral-200">
59
59
<input
60
60
type="text"
61
61
spellcheck={false}
+1
-1
src/components/text-input.tsx
···
25
25
disabled={props.disabled}
26
26
required={props.required}
27
27
class={
28
28
-
"dark:bg-dark-100 dark:shadow-dark-900/80 rounded-lg bg-white px-2 py-1 shadow-sm placeholder:text-sm focus:outline-[1.5px] focus:outline-neutral-900 dark:focus:outline-neutral-200 " +
28
28
+
"dark:bg-dark-100 dark:shadow-dark-800 rounded-lg bg-white px-2 py-1 shadow-sm placeholder:text-sm focus:outline-[1.5px] focus:outline-neutral-900 dark:focus:outline-neutral-200 " +
29
29
props.class
30
30
}
31
31
onInput={props.onInput}
+1
-1
src/components/tooltip.tsx
···
8
8
<Show when={!isTouchDevice}>
9
9
<span
10
10
style={`transform: translate(-50%, 28px)`}
11
11
-
class={`dark:shadow-dark-900/80 pointer-events-none absolute left-[50%] z-10 hidden min-w-fit rounded border-[0.5px] border-neutral-300 bg-white p-1 text-center font-sans text-xs whitespace-nowrap text-neutral-900 shadow-md select-none group-hover/tooltip:inline first-letter:capitalize dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-200`}
11
11
+
class={`dark:shadow-dark-800 pointer-events-none absolute left-[50%] z-10 hidden min-w-fit rounded border-[0.5px] border-neutral-300 bg-white p-1 text-center font-sans text-xs whitespace-nowrap text-neutral-900 shadow-md select-none group-hover/tooltip:inline first-letter:capitalize dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-200`}
12
12
>
13
13
{props.text}
14
14
</span>
+1
-1
src/layout.tsx
···
98
98
</div>
99
99
<Show when={notif().show}>
100
100
<button
101
101
-
class="dark:shadow-dark-900/80 dark:bg-dark-100/70 fixed bottom-10 z-50 flex items-center rounded-lg border-[0.5px] border-neutral-300 bg-white/70 p-2 shadow-md backdrop-blur-xs dark:border-neutral-700"
101
101
+
class="dark:shadow-dark-800 dark:bg-dark-100/70 fixed bottom-10 z-50 flex items-center rounded-lg border-[0.5px] border-neutral-300 bg-white/70 p-2 shadow-md backdrop-blur-xs dark:border-neutral-700"
102
102
onClick={() => setNotif({ show: false })}
103
103
>
104
104
<span class={`iconify ${notif().icon} mr-1`}></span>
+1
-1
src/views/collection.tsx
···
52
52
<Show when={hover()}>
53
53
<span
54
54
ref={previewRef}
55
55
-
class={`dark:bg-dark-500/70 dark:shadow-dark-900/80 pointer-events-none absolute left-[50%] z-25 block max-h-[20rem] w-max max-w-sm -translate-x-1/2 overflow-hidden rounded-lg border-[0.5px] border-neutral-300 bg-neutral-100/70 p-2 text-xs whitespace-pre-wrap shadow-md backdrop-blur-xs sm:max-h-[28rem] lg:max-w-lg dark:border-neutral-700 ${isOverflowing(previewHeight()) ? "bottom-7" : "top-7"}`}
55
55
+
class={`dark:bg-dark-500/70 dark:shadow-dark-800 pointer-events-none absolute left-[50%] z-25 block max-h-[20rem] w-max max-w-sm -translate-x-1/2 overflow-hidden rounded-lg border-[0.5px] border-neutral-300 bg-neutral-100/70 p-2 text-xs whitespace-pre-wrap shadow-md backdrop-blur-xs sm:max-h-[28rem] lg:max-w-lg dark:border-neutral-700 ${isOverflowing(previewHeight()) ? "bottom-7" : "top-7"}`}
56
56
>
57
57
<JSONValue
58
58
data={props.record.record.value as JSONType}
+1
-1
src/views/labels.tsx
···
73
73
spellcheck={false}
74
74
rows={3}
75
75
value={searchParams.uriPatterns ?? "*"}
76
76
-
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 mb-1 grow rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
76
76
+
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-800 mb-1 grow rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
77
77
/>
78
78
<div class="flex justify-center">
79
79
<Show when={!response.loading}>
+32
-33
src/views/record.tsx
···
6
6
import { Backlinks } from "../components/backlinks.jsx";
7
7
import { Button } from "../components/button.jsx";
8
8
import { RecordEditor } from "../components/create.jsx";
9
9
+
import { CopyMenu, DropdownMenu, MenuProvider, NavMenu } from "../components/dropdown.jsx";
9
10
import { JSONValue } from "../components/json.jsx";
10
11
import { agent } from "../components/login.jsx";
11
12
import { Modal } from "../components/modal.jsx";
···
13
14
import Tooltip from "../components/tooltip.jsx";
14
15
import { setNotif } from "../layout.jsx";
15
16
import { didDocCache, resolvePDS } from "../utils/api.js";
16
16
-
import { addToClipboard } from "../utils/copy.js";
17
17
import { AtUri, uriTemplates } from "../utils/templates.js";
18
18
import { lexicons } from "../utils/types/lexicons.js";
19
19
import { verifyRecord } from "../utils/verify.js";
···
113
113
return (
114
114
<Show when={record()} keyed>
115
115
<div class="flex w-full flex-col items-center">
116
116
-
<div class="dark:shadow-dark-900/80 dark:bg-dark-300 my-3 flex w-[22rem] justify-between rounded-lg bg-neutral-50 px-2 py-1.5 shadow-sm sm:w-[24rem]">
116
116
+
<div class="dark:shadow-dark-800 dark:bg-dark-300 my-3 flex w-[22rem] justify-between rounded-lg bg-neutral-50 px-2 py-1.5 shadow-sm sm:w-[24rem]">
117
117
<div class="flex gap-3 text-sm">
118
118
<A
119
119
classList={{
···
150
150
</button>
151
151
</Tooltip>
152
152
<Modal open={openDelete()} onClose={() => setOpenDelete(false)}>
153
153
-
<div class="dark:bg-dark-800/70 dark:shadow-dark-900/80 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-200/70 p-4 text-neutral-900 shadow-md backdrop-blur-xs transition-opacity duration-300 dark:border-neutral-700 dark:text-neutral-200 starting:opacity-0">
153
153
+
<div class="dark:bg-dark-800/70 dark:shadow-dark-800 absolute top-70 left-[50%] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-200/70 p-4 text-neutral-900 shadow-md backdrop-blur-xs transition-opacity duration-300 dark:border-neutral-700 dark:text-neutral-200 starting:opacity-0">
154
154
<h2 class="mb-2 font-semibold">Delete this record?</h2>
155
155
<div class="flex justify-end gap-2">
156
156
<Button onClick={() => setOpenDelete(false)}>Cancel</Button>
157
157
<Button
158
158
onClick={deleteRecord}
159
159
-
class="dark:shadow-dark-900/80 rounded-lg bg-red-500 px-2 py-1.5 text-xs font-semibold text-neutral-200 shadow-sm hover:bg-red-400 active:bg-red-400"
159
159
+
class="dark:shadow-dark-800 rounded-lg bg-red-500 px-2 py-1.5 text-xs font-semibold text-neutral-200 shadow-sm hover:bg-red-400 active:bg-red-400"
160
160
>
161
161
Delete
162
162
</Button>
···
164
164
</div>
165
165
</Modal>
166
166
</Show>
167
167
-
<Tooltip text="Copy record">
168
168
-
<button
169
169
-
class="flex items-center rounded-sm p-1 hover:bg-neutral-200 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
170
170
-
onclick={() => addToClipboard(JSON.stringify(record()?.value, null, 2))}
167
167
+
<MenuProvider>
168
168
+
<DropdownMenu
169
169
+
icon="lucide--ellipsis-vertical "
170
170
+
buttonClass="rounded-sm p-1"
171
171
+
menuClass="top-8 p-2 text-sm"
171
172
>
172
172
-
<span class="iconify lucide--copy"></span>
173
173
-
</button>
174
174
-
</Tooltip>
175
175
-
<Show when={externalLink()}>
176
176
-
{(externalLink) => (
177
177
-
<Tooltip text={`Open on ${externalLink().label}`}>
178
178
-
<a
179
179
-
class="flex items-center rounded-sm p-1 hover:bg-neutral-200 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
180
180
-
target="_blank"
181
181
-
href={externalLink()?.link}
182
182
-
>
183
183
-
<span class={`iconify ${externalLink().icon ?? "lucide--app-window"}`}></span>
184
184
-
</a>
185
185
-
</Tooltip>
186
186
-
)}
187
187
-
</Show>
188
188
-
<Tooltip text="Record on PDS">
189
189
-
<a
190
190
-
class="flex items-center rounded-sm p-1 hover:bg-neutral-200 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
191
191
-
href={`https://${pds()}/xrpc/com.atproto.repo.getRecord?repo=${params.repo}&collection=${params.collection}&rkey=${params.rkey}`}
192
192
-
target="_blank"
193
193
-
>
194
194
-
<span class="iconify lucide--external-link"></span>
195
195
-
</a>
196
196
-
</Tooltip>
173
173
+
<CopyMenu
174
174
+
copyContent={JSON.stringify(record()?.value, null, 2)}
175
175
+
label="Copy record"
176
176
+
icon="lucide--copy"
177
177
+
/>
178
178
+
<Show when={externalLink()}>
179
179
+
{(externalLink) => (
180
180
+
<NavMenu
181
181
+
href={externalLink()?.link}
182
182
+
icon={`${externalLink().icon ?? "lucide--app-window"}`}
183
183
+
label={`Open on ${externalLink().label}`}
184
184
+
newTab
185
185
+
/>
186
186
+
)}
187
187
+
</Show>
188
188
+
<NavMenu
189
189
+
href={`https://${pds()}/xrpc/com.atproto.repo.getRecord?repo=${params.repo}&collection=${params.collection}&rkey=${params.rkey}`}
190
190
+
icon="lucide--external-link"
191
191
+
label="Record on PDS"
192
192
+
newTab
193
193
+
/>
194
194
+
</DropdownMenu>
195
195
+
</MenuProvider>
197
196
</div>
198
197
</div>
199
198
<Show when={!location.hash || location.hash === "#record"}>
+1
-1
src/views/repo.tsx
···
109
109
<div class="flex items-center justify-between">
110
110
<div class="flex items-center gap-1">
111
111
<div class="iconify lucide--filter" />
112
112
-
<div class="dark:shadow-dark-900/80 dark:bg-dark-300 flex w-fit items-center rounded-full bg-white shadow-sm">
112
112
+
<div class="dark:shadow-dark-800 dark:bg-dark-300 flex w-fit items-center rounded-full bg-white shadow-sm">
113
113
<FilterButton icon="iconify lucide--at-sign" event="handle" />
114
114
<FilterButton icon="iconify lucide--key-round" event="rotation_key" />
115
115
<FilterButton icon="iconify lucide--hard-drive" event="service" />
+2
-2
src/views/stream.tsx
···
194
194
spellcheck={false}
195
195
placeholder="Comma-separated list of collections"
196
196
value={searchParams.collections ?? ""}
197
197
-
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 w-[16rem] rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
197
197
+
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-800 w-[16rem] rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
198
198
/>
199
199
</label>
200
200
</Show>
···
206
206
spellcheck={false}
207
207
placeholder="Comma-separated list of DIDs"
208
208
value={searchParams.dids ?? ""}
209
209
-
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900/80 w-[16rem] rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
209
209
+
class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-800 w-[16rem] rounded-lg bg-white px-2 py-1 shadow-sm focus:outline-neutral-900 dark:focus:outline-neutral-200"
210
210
/>
211
211
</label>
212
212
</Show>