Skip to content

Commit 77fe312

Browse files
committed
Links WORK
1 parent d47f43a commit 77fe312

File tree

10 files changed

+124
-23
lines changed

10 files changed

+124
-23
lines changed

Diff for: components.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ declare module 'vue' {
2020
'NewBlink.ce': typeof import('./src/components/custom-elements/NewBlink.ce.vue')['default']
2121
'OmitVanillaCss.ce': typeof import('./src/components/custom-elements/OmitVanillaCss.ce.vue')['default']
2222
'OnekoKitty.ce': typeof import('./src/components/custom-elements/OnekoKitty.ce.vue')['default']
23+
'RingLink.ce': typeof import('./src/components/custom-elements/RingLink.ce.vue')['default']
2324
RouterLink: typeof import('vue-router')['RouterLink']
2425
RouterView: typeof import('vue-router')['RouterView']
2526
SignInGate: typeof import('./src/components/SignInGate.vue')['default']

Diff for: src/components/SignInGate.vue

+27-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,28 @@ defineProps<{
1111
}>();
1212
1313
const handle = ref('');
14+
const hash = ref('');
1415
const open = ref(false);
1516
17+
let finishAuthentication: () => void = () => {};
18+
let cancelAuthentication: () => void = () => {};
19+
20+
const codePasteOpen = ref(false);
21+
1622
watchImmediate(user, user => {
1723
handle.value = user?.handle ?? '';
1824
});
1925
26+
async function authenticate() {
27+
authenticateIfNecessary(handle.value, () => {
28+
return new Promise((resolve, reject) => {
29+
finishAuthentication = () => resolve(hash.value);
30+
cancelAuthentication = reject;
31+
codePasteOpen.value = true;
32+
});
33+
});
34+
}
35+
2036
</script>
2137
<template>
2238
<VaButton :class="signInButtonClass" v-if="!user" @click="open = true">{{ signInText ?? 'Sign in' }}</VaButton>
@@ -26,9 +42,19 @@ watchImmediate(user, user => {
2642
v-model="open"
2743
ok-text="Sign In"
2844
cancel-text="Cancel"
29-
@ok="authenticateIfNecessary(handle).finally(() => open = false)"
45+
@ok="authenticate()"
3046
>
3147
<VaInput v-model="handle" label="@handle" placeholder="e.g. you.bsky.social" />
3248
</VaModal>
49+
<VaModal
50+
v-model="codePasteOpen"
51+
ok-text="Complete Sign In"
52+
cancel-text="Cancel"
53+
@ok="finishAuthentication()"
54+
@cancel="cancelAuthentication()"
55+
@click-outside="cancelAuthentication()"
56+
>
57+
<VaInput v-model="hash" label="Input displayed code" />
58+
</VaModal>
3359

3460
</template>

Diff for: src/components/custom-elements/RingLink.ce.vue

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<script setup lang="ts">
2+
import { getRing } from '@/lib/atproto/atweb-unauthed';
3+
import { resolveRoute } from '@/lib/vue-utils';
4+
import { AtUri } from '@atproto/syntax';
5+
import { ref } from 'vue';
6+
7+
const { direction = 'previous', ringUri, self } = defineProps<{
8+
direction?: 'previous' | 'next';
9+
ringUri: string;
10+
self: string;
11+
}>();
12+
13+
const ringAtUri = new AtUri(ringUri);
14+
15+
const ring = await getRing(ringAtUri.host, ringAtUri.rkey);
16+
17+
const realMembers = ring.members.filter(e => e.isMember);
18+
const memberIndex = realMembers.findIndex(member => member.membership.host === self);
19+
20+
const prevMemberLink = ref<string>();
21+
const nextMemberLink = ref<string>();
22+
if (memberIndex != -1) {
23+
const prevMember = ring.members[wrapAround(memberIndex - 1, realMembers.length)];
24+
const nextMember = ring.members[wrapAround(memberIndex + 1, realMembers.length)];
25+
prevMemberLink.value = resolveRoute(`/page/${prevMember.membership.host}/${prevMember.mainPage?.rkey ?? '!!! FAILED !!!.mdx'}`);
26+
nextMemberLink.value = resolveRoute(`/page/${nextMember.membership.host}/${nextMember.mainPage?.rkey ?? '!!! FAILED !!!.mdx'}`);
27+
}
28+
29+
function wrapAround(i: number, max: number) {
30+
if (i >= max) i %= max;
31+
else if (i < 0) i += max;
32+
return i;
33+
}
34+
35+
</script>
36+
37+
<template>
38+
<a class="va-link" v-bind="$attrs" :href="direction === 'previous' ? prevMemberLink : nextMemberLink">
39+
<slot></slot>
40+
</a>
41+
</template>

Diff for: src/lib/atproto/atweb-unauthed.ts

+1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ async function *getMembershipStatuses(owner: string, members: IoGithubAtwebRing.
264264

265265
yield {
266266
membership: uri,
267+
mainPage: result.value?.mainPage ? new AtUri(result.value.mainPage) : undefined,
267268
isMember,
268269
isOwner,
269270
};

Diff for: src/lib/atproto/oauth.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ configureOAuth({
1212
export class AtpOauthClient {
1313
constructor() {}
1414

15-
async authenticate(handle: string) {
15+
async authenticate(handle: string, showInputCodeModal: () => Promise<string>) {
1616
const { identity, metadata } = await resolveFromIdentity(handle);
1717

1818
// passing `identity` is optional,
@@ -32,8 +32,8 @@ export class AtpOauthClient {
3232
window.open(authUrl, '_blank', 'noopener,noreferrer');
3333

3434
// TODO setup a redirect instead
35-
const hash = prompt('Input the code that was displayed on the page');
36-
if (!hash) throw new Error('User cancelled authentication');
35+
36+
const hash = await showInputCodeModal();
3737

3838
// `createAuthorizationUrl` asks for the server to redirect here with the
3939
// parameters assigned in the hash, not the search string.

Diff for: src/lib/atproto/signed-in-user.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ watchImmediate([account, agents], ([account, agents]) => {
5555
const oauthClient = new AtpOauthClient();
5656
export async function authenticateIfNecessary(
5757
handle: string,
58-
refreshOnly = false,
58+
refreshOnlyOrShowInputCodeModal: boolean | (() => Promise<string>),
5959
) {
6060
if (!agents.value || !account.value || account.value.handle !== handle) {
6161
let session: Session | undefined;
@@ -70,9 +70,9 @@ export async function authenticateIfNecessary(
7070

7171
console.log('seession', session);
7272

73-
if (refreshOnly && !session) return false;
73+
if (refreshOnlyOrShowInputCodeModal === true && !session) return false;
7474

75-
session ??= await oauthClient.authenticate(handle);
75+
session ??= await oauthClient.authenticate(handle, refreshOnlyOrShowInputCodeModal as () => Promise<string>);
7676

7777
const oauthAgent = new OAuthUserAgent(session);
7878

Diff for: src/lib/markdown/components.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import NewBlink from '@/components/custom-elements/NewBlink.ce.vue';
2121
import LesbiBadge from '@/components/custom-elements/LesbiBadge.ce.vue';
2222
import EightyEightThirtyOneBadge from '@/components/custom-elements/EightyEightThirtyOneBadge.ce.vue';
2323
import UserbarBadge from '@/components/custom-elements/UserbarBadge.ce.vue';
24+
import RingLink from '@/components/custom-elements/RingLink.ce.vue';
2425

2526
type Props = Record<string, any>;
2627

@@ -75,5 +76,6 @@ export const components: MDXComponents = {
7576
LesbiBadge: LesbiBadge,
7677
Badge: EightyEightThirtyOneBadge,
7778
Userbar: UserbarBadge,
79+
RingLink: RingLink,
7880
...(extraComponents as unknown as MDXComponents)
7981
};

Diff for: src/lib/vue-utils.ts

+9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
type Reactive,
1515
} from 'vue';
1616
import type { ReactiveMarker } from '@vue/reactivity/dist/reactivity.d.ts';
17+
import router from '@/router';
18+
import type { RouteLocationAsPath, RouteLocationAsRelative, RouteLocationAsString } from 'vue-router';
1719

1820
export function getSlotChildrenText(
1921
children: Slot | VNode[] | VNodeArrayChildren | undefined,
@@ -112,3 +114,10 @@ export async function watchImmediateAsync(
112114

113115
return watcher;
114116
}
117+
118+
export function resolveRoute(route: RouteLocationAsString | RouteLocationAsRelative | RouteLocationAsPath) {
119+
// https://stackoverflow.com/a/70989279
120+
const resolved = router.resolve(route);
121+
const absoluteURL = new URL(resolved.href, window.location.origin).href;
122+
return absoluteURL;
123+
}

Diff for: src/pages/invited/[inviterDid]/[ringAndInviteRkey]/index.vue

+25-9
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,40 @@ const { didDocument } = await getDidAndPds(route.params.inviterDid);
1212
const inviterHandle = didDocument.alsoKnownAs?.[0].replace('at://', '') ?? route.params.inviterDid;
1313
1414
const mainPage = ref('');
15+
const inviteAccepted = ref<boolean | string>(false);
1516
1617
async function acceptInvite() {
1718
const ring = await getRing(route.params.inviterDid, route.params.ringAndInviteRkey);
1819
if (!ring.members.some(member => member.membership.host === user.value!.did))
1920
return; // invite was rescinded
2021
2122
await user.value!.client.acceptInvite(route.params.inviterDid, route.params.ringAndInviteRkey, mainPage.value);
23+
24+
inviteAccepted.value = ring.name;
2225
}
2326
</script>
2427

2528
<template>
26-
<SignInGate :sign-in-text="`Sign in to view invite from @${inviterHandle}`">
27-
<div>
28-
Accept this invite from @{{ inviterHandle }}?
29-
</div>
30-
<div>
31-
<VaInput v-model="mainPage" label="My Member Page" placeholder="index.mdx"/>
32-
<VaButton @click="acceptInvite()" style="vertical-align: text-top; margin-top: -1px">OK</VaButton>
33-
</div>
34-
</SignInGate>
29+
<div class="main">
30+
<SignInGate :sign-in-text="`Sign in to view invite from @${inviterHandle}`">
31+
<div v-if="!inviteAccepted">
32+
<div>
33+
Accept this invite from @{{ inviterHandle }}?
34+
</div>
35+
<div>
36+
<VaInput v-model="mainPage" label="My Member Page" placeholder="index.mdx" />
37+
<VaButton @click="acceptInvite()" style="vertical-align: text-top; margin-top: -1px">OK</VaButton>
38+
</div>
39+
</div>
40+
<div v-else>
41+
Welcome to {{ inviteAccepted }}!
42+
</div>
43+
</SignInGate>
44+
</div>
3545
</template>
46+
47+
<style lang="scss" scoped>
48+
.main {
49+
padding: 0 1rem;
50+
}
51+
</style>

Diff for: src/pages/rings.vue

+12-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import router from '@/router';
99
import type { IoGithubAtwebRing } from '@atcute/client/lexicons';
1010
import { now as tidNow } from '@atcute/tid';
1111
import { AtUri } from '@atproto/syntax';
12-
import { computedAsync, useBrowserLocation, type ElementOf } from '@vueuse/core';
12+
import { computedAsync, useBrowserLocation, useClipboard, type ElementOf } from '@vueuse/core';
1313
import { computed, ref, shallowRef, watch, type Ref } from 'vue';
1414
import { useRoute } from 'vue-router';
1515
import type { RouteLocationAsPath, RouteLocationAsRelative, RouteLocationAsString, RouteLocationNormalizedLoadedGeneric } from 'vue-router';
@@ -160,7 +160,7 @@ async function addMember(ring: Ring, handle: string) {
160160
161161
await user.value!.client.updateRingMembers(
162162
ring.uri.rkey,
163-
[...ring.members, AtUri.make(did, 'io.github.atweb.ringMembership', membershipTid)],
163+
[...ring.members, AtUri.make(did, 'io.github.atweb.ringMembership', ring.uri.rkey)],
164164
ring.cid
165165
);
166166
@@ -182,11 +182,13 @@ async function reloadRings() {
182182
}
183183
}
184184
185-
function resolveRoute(route: RouteLocationAsString | RouteLocationAsRelative | RouteLocationAsPath) {
186-
// https://stackoverflow.com/a/70989279
187-
const resolved = router.resolve(route);
188-
const absoluteURL = new URL(resolved.href, window.location.origin).href;
189-
return absoluteURL;
185+
const { copy } = useClipboard();
186+
function copyRingMdx(ring: Ring) {
187+
copy(
188+
`<RingLink direction="previous" ring-uri="${ring.uri.toString()}" self="${user.value!.did}">prev</RingLink>\n` +
189+
`${ring.name}\n` +
190+
`<RingLink direction="next" ring-uri="${ring.uri.toString()}" self="${user.value!.did}">next</RingLink>`
191+
);
190192
}
191193
</script>
192194

@@ -263,6 +265,9 @@ function resolveRoute(route: RouteLocationAsString | RouteLocationAsRelative | R
263265
<p>( <code>{{ selectedRing.uri }}</code> )</p>
264266
<p><b>{{ selectedRing.members?.length ?? 0 }}</b> member(s)</p>
265267
<p>Managed by <a class="va-link" :href="`https://bsky.app/profile/${selectedRing.uri.host}`">@{{ getMemberName(selectedRing.uri) }}</a></p>
268+
269+
<VaButton @click="copyRingMdx(selectedRing)">Copy ring MDX code</VaButton>
270+
266271
<div v-for="member in selectedRing.members" :key="member.membership.toString()" class="ring-member-card va-gutter-2">
267272
<div class="va-gutter-2">
268273
<bluesky-profile-card :actor="getMemberName(member.membership)" allow-unauthenticated="true">

0 commit comments

Comments
 (0)