diff --git a/.github/workflows/deploy_gh_pages.yml b/.github/workflows/deploy_gh_pages.yml index 31ec787..6b04f49 100644 --- a/.github/workflows/deploy_gh_pages.yml +++ b/.github/workflows/deploy_gh_pages.yml @@ -24,6 +24,7 @@ jobs: - name: build env: BASE_PATH: '/${{ github.event.repository.name }}' + PUBLIC_HANDLE: 'flo-bit.dev' run: | npm run build diff --git a/src/lib/UI/Button.svelte b/src/lib/UI/Button.svelte deleted file mode 100644 index 63a6a24..0000000 --- a/src/lib/UI/Button.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/src/lib/UI/LoginModal.svelte b/src/lib/UI/LoginModal.svelte deleted file mode 100644 index 9f6bb69..0000000 --- a/src/lib/UI/LoginModal.svelte +++ /dev/null @@ -1,118 +0,0 @@ - - - - -{#if loginModalState.visible} - -{/if} diff --git a/src/lib/Website.svelte b/src/lib/Website.svelte new file mode 100644 index 0000000..1629871 --- /dev/null +++ b/src/lib/Website.svelte @@ -0,0 +1,62 @@ + + +
+ + {#snippet child(data)} + + <MarkdownText class="mt-8" key="content" placeholder="Write something about yourself..." {data} /> + {/snippet} + </SingleRecord> + + <div class="relative flex flex-col gap-4 mt-8"> + <List collection="link.flo-bit.dev"> + {#snippet item(data, deleteItem)} + <div class="relative mt-4 flex flex-col gap-2"> + <div class="pointer-events-none z-10 font-bold"> + <PlainText key="label" {data} placeholder="Write a label..." /> + </div> + <div class="pointer-events-none z-10"> + <PlainText key="description" {data} placeholder="Write a description..." /> + </div> + + {#if hasContext('isEditing')} + <Button class="absolute top-0 right-0 z-10" size="icon" variant="ghost" onclick={deleteItem}> + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="1.5" + stroke="currentColor" + class="size-6" + > + <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /> + </svg> + <span class="sr-only">delete</span> + </Button> + {:else} + <a + href={data.url} + target="_blank" + class="hover:bg-base-200/50 absolute -inset-2 rounded-2xl" + > + <span class="sr-only"> + {data.label} + </span></a + > + {/if} + </div> + {/snippet} + + {#snippet addItem(add)} + <Button class="absolute -bottom-16 left-0" onclick={add}>Add link</Button> + {/snippet} + </List> + </div> +</div> diff --git a/src/lib/oauth/index.ts b/src/lib/oauth/index.ts index 05c0fb9..59711c2 100644 --- a/src/lib/oauth/index.ts +++ b/src/lib/oauth/index.ts @@ -1,6 +1,4 @@ import { client, login, logout, initClient } from './auth.svelte'; -import LoginModal, { loginModalState } from '../UI/LoginModal.svelte'; -import Button from '../UI/Button.svelte'; import { metadata } from './const'; -export { metadata, client, login, logout, initClient, LoginModal, Button, loginModalState }; +export { metadata, client, login, logout, initClient }; diff --git a/src/lib/website/Link.svelte b/src/lib/website/Link.svelte deleted file mode 100644 index e5b3c83..0000000 --- a/src/lib/website/Link.svelte +++ /dev/null @@ -1,22 +0,0 @@ -<script lang="ts"> - import { hasContext } from 'svelte'; - import PlainText from './PlainText.svelte'; - - let { data, deleteItem } = $props(); -</script> - -<div class="relative mt-4 flex flex-col gap-2"> - <div class="pointer-events-none z-10 font-bold"> - <PlainText key="label" {data} placeholder="Write a label..." /> - </div> - <div class="pointer-events-none z-10"> - <PlainText key="description" {data} placeholder="Write a description..." /> - </div> - - {#if hasContext('isEditing')} - <button class="absolute top-0 right-0 z-10" onclick={deleteItem}>Delete</button> - {:else} - <a href={data.url} target="_blank" class="hover:bg-base-200/50 absolute -inset-2 rounded-2xl" - ></a> - {/if} -</div> diff --git a/src/lib/website/MarkdownText.svelte b/src/lib/website/MarkdownText.svelte deleted file mode 100644 index 89a7ff0..0000000 --- a/src/lib/website/MarkdownText.svelte +++ /dev/null @@ -1,52 +0,0 @@ -<script lang="ts"> - import { Prose } from '@fuxui/base'; - import { marked } from 'marked'; - import { hasContext } from 'svelte'; - import type { Collection } from './data'; - import { data as recordData } from './data'; - - type ArrayCollections = { - [K in Collection]: (typeof recordData)[K] extends readonly any[] ? K : never; - }[Collection]; - - type ElementType<C extends Collection> = (typeof recordData)[C] extends readonly (infer U)[] - ? U - : any; - - type Props = - | { - collection: ArrayCollections; - rkey?: ElementType<ArrayCollections>; - data: Record<string, any>; - key: string; - placeholder?: string; - defaultContent?: string; - } - | { - collection: Exclude<Collection, ArrayCollections>; - rkey: ElementType<Exclude<Collection, ArrayCollections>>; - data: Record<string, any>; - key: string; - placeholder?: string; - defaultContent?: string; - }; - - let { - collection, - rkey = 'self', - data, - key = 'content', - placeholder, - defaultContent - }: Props = $props(); -</script> - -{#if hasContext('isEditing')} - {#await import('./MarkdownTextEditor.svelte') then { default: MarkdownTextEditor }} - <MarkdownTextEditor {collection} {rkey} {key} {placeholder} {defaultContent} /> - {/await} -{:else} - <Prose> - {@html marked.parse(data[collection]?.[rkey]?.value?.[key] ?? defaultContent ?? ('' as string))} - </Prose> -{/if} diff --git a/src/lib/website/PlainTextItem.svelte b/src/lib/website/PlainTextItem.svelte deleted file mode 100644 index a9ada9d..0000000 --- a/src/lib/website/PlainTextItem.svelte +++ /dev/null @@ -1,48 +0,0 @@ -<script lang="ts"> - import { getRecord } from '$lib/oauth/atproto'; - import { getContext } from 'svelte'; - import SingleRecord from './SingleRecord.svelte'; - - let { - collection, - rkey, - key, - data, - placeholder, - defaultContent - }: { - collection: string; - rkey: string; - key: string; - data: Record<string, any>; - placeholder?: string; - defaultContent?: string; - } = $props(); - - let did = getContext('did') as string; -</script> - -{#if getContext('isEditing')} - {#await getRecord({ did, collection, rkey }) then record} - <SingleRecord data={record}> - {#snippet child(recordData)} - {#await import('./PlainTextEditor.svelte') then { default: PlainTextEditor }} - <PlainTextEditor {key} data={recordData} {placeholder} {defaultContent} /> - {/await} - {/snippet} - </SingleRecord> - {:catch error} - <SingleRecord data={{ - uri: 'at://' + did + '/' + collection + '/' + rkey, - value: {} - }}> - {#snippet child(recordData)} - {#await import('./PlainTextEditor.svelte') then { default: PlainTextEditor }} - <PlainTextEditor {key} data={recordData} {placeholder} {defaultContent} /> - {/await} - {/snippet} - </SingleRecord> - {/await} -{:else} - <div>{data?.[collection]?.[rkey]?.value?.[key] || defaultContent || placeholder}</div> -{/if} diff --git a/src/lib/website/Test.svelte b/src/lib/website/Test.svelte deleted file mode 100644 index 68183c4..0000000 --- a/src/lib/website/Test.svelte +++ /dev/null @@ -1,37 +0,0 @@ -<script lang="ts"> - import { Input } from "@fuxui/base"; - import { getContext } from "svelte"; - import type { UpdateFunction, UpdateRecordFunction } from "./context"; - import PlainTextEditor from "./PlainTextEditor.svelte"; - - let { data } = $props(); - - console.log(data); - - let label = $state(data.label); - let description = $state(data.description); - - let updatedLabel = $state(false); - let updatedDescription = $state(false); - const update = async () => { - const updated: Record<string, any> = {}; - if(updatedLabel) { - updated.label = label; - } - if(updatedDescription) { - updated.description = description; - } - return updated; - }; - - const updateFunctions = getContext('updateRecord') as UpdateRecordFunction[]; - - if(updateFunctions) { - updateFunctions.push(update); - } -</script> - -<div class="flex flex-col gap-2"> - <PlainTextEditor class="font-bold" bind:value={label} onchange={() => updatedLabel = true} /> - <PlainTextEditor bind:value={description} onchange={() => updatedDescription = true} /> -</div> \ No newline at end of file diff --git a/src/lib/website/Website.svelte b/src/lib/website/Website.svelte deleted file mode 100644 index df61ea2..0000000 --- a/src/lib/website/Website.svelte +++ /dev/null @@ -1,60 +0,0 @@ -<script> - import { Input } from '@fuxui/base'; - import List from './List.svelte'; - import MarkdownText from './MarkdownText.svelte'; - import SingleRecord from './SingleRecord.svelte'; - import Test from './Test.svelte'; - import PlainText from './PlainText.svelte'; - import { setContext } from 'svelte'; - import Link from './Link.svelte'; - import PlainTextItem from './PlainTextItem.svelte'; - - let { data } = $props(); - - setContext('data', data); -</script> - -<div class="mx-auto max-w-2xl py-16"> - <!-- <MarkdownText collection="dev.flo-bit.about" key="about" {data} /> --> - - <!-- <MarkdownText collection="link.flo-bit.dev" rkey="test" key="content" {data} /> --> - <!-- - <MarkdownText - collection="dev.flo-bit.test" - rkey="self" - key="test" - {data} - placeholder="Write something about yourself..." - defaultContent="Hello there" - /> --> - - <PlainTextItem collection="dev.flo-bit.about" rkey="test" key="about" {data} placeholder="Write something about yourself..." /> - - <div class="relative flex flex-col gap-16"> - <List collection="link.flo-bit.dev"> - {#snippet item(data, deleteItem)} - <Link {data} {deleteItem} /> - {/snippet} - - {#snippet addItem(add)} - <button class="absolute left-0 -bottom-12" onclick={add}>Add</button> - {/snippet} - </List> - </div> - <!-- - <div class="relative flex flex-col gap-4 mt-8"> - <List collection="link.flo-bit.test"> - {#snippet item(data, deleteItem)} - <Link {data} {deleteItem} /> - {/snippet} - - {#snippet addItem(add)} - <button class="absolute right-0 bottom-0" onclick={add}>Add</button> - {/snippet} - - {#snippet empty()} - <div>Empty list</div> - {/snippet} - </List> - </div> --> -</div> diff --git a/src/lib/website/SingleRecord.svelte b/src/lib/website/components/EditSingleRecord.svelte similarity index 61% rename from src/lib/website/SingleRecord.svelte rename to src/lib/website/components/EditSingleRecord.svelte index 4324f76..46e5d99 100644 --- a/src/lib/website/SingleRecord.svelte +++ b/src/lib/website/components/EditSingleRecord.svelte @@ -1,7 +1,6 @@ <script lang="ts"> - import { getContext, setContext, type Snippet } from 'svelte'; - import { parseUri } from './data'; - import type { UpdateFunction, UpdateRecordFunction } from './context'; + import { onDestroy, onMount, setContext, type Snippet } from 'svelte'; + import { getUpdateFunctions, parseUri, type UpdateRecordFunction } from '../data'; import { putRecord } from '$lib/oauth/atproto'; let { @@ -13,10 +12,9 @@ } = $props(); let updateRecordFunctions: UpdateRecordFunction[] = $state([]); - setContext('updateRecord', updateRecordFunctions); - - const updateFunctions = getContext('updateFunctions') as UpdateFunction[]; + setContext('updateRecordFunctions', updateRecordFunctions); + const updateFunctions = getUpdateFunctions(); const update = async () => { const updated: Record<string, any> = {}; @@ -28,8 +26,12 @@ } } - if (Object.keys(updated).length > 0) { + if (Object.keys(updated).length > 0 || !data.cid) { + if(!data.value.createdAt) { + data.value.createdAt = new Date().toISOString(); + } data.value.updatedAt = new Date().toISOString(); + const { collection, rkey } = parseUri(data.uri); await putRecord({ collection, rkey, record: data.value }); return true; @@ -38,9 +40,12 @@ return false; }; - if (updateFunctions) { + onMount(() => { updateFunctions.push(update); - } + }); + onDestroy(() => { + updateFunctions.splice(updateFunctions.indexOf(update), 1); + }); </script> {@render child(data.value)} diff --git a/src/lib/website/EditingList.svelte b/src/lib/website/components/EditingList.svelte similarity index 88% rename from src/lib/website/EditingList.svelte rename to src/lib/website/components/EditingList.svelte index feddb93..0cc2ce4 100644 --- a/src/lib/website/EditingList.svelte +++ b/src/lib/website/components/EditingList.svelte @@ -1,10 +1,10 @@ <script lang="ts"> import { getContext, type Snippet } from 'svelte'; - import SingleRecord from './SingleRecord.svelte'; + import SingleRecord from './EditSingleRecord.svelte'; import { TID } from '@atproto/common-web'; - import type { Record } from '@atproto/api/dist/client/types/com/atproto/lexicon/schema'; + import type { Record } from '@atproto/api/dist/client/types/com/atproto/repo/listRecords'; import { deleteRecord } from '$lib/oauth/atproto'; - import { parseUri } from './data'; + import { parseUri } from '../data'; let { records, @@ -23,8 +23,6 @@ let allRecords = $state(records); const did = getContext('did') as string; - - $inspect(allRecords); </script> {#if allRecords.length === 0 && empty} @@ -49,10 +47,10 @@ {/if} {@render addItem?.(() => { - console.log('clicked'); const rkey = TID.nextStr(); allRecords.push({ + cid: '', uri: 'at://' + did + '/' + collection + '/' + rkey, value: {} }); diff --git a/src/lib/website/List.svelte b/src/lib/website/components/List.svelte similarity index 74% rename from src/lib/website/List.svelte rename to src/lib/website/components/List.svelte index 98da7f2..796e1ca 100644 --- a/src/lib/website/List.svelte +++ b/src/lib/website/components/List.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import { getContext, hasContext, type Snippet } from 'svelte'; - import type { Collection } from './data'; + import { getData, type ListCollections } from '../data'; import { listRecords } from '$lib/oauth/atproto'; import EditingList from './EditingList.svelte'; @@ -10,13 +10,13 @@ addItem, empty }: { - collection: Collection; + collection: ListCollections; item: Snippet<[any, any]>; addItem: Snippet<[any]>; empty?: Snippet; } = $props(); - let data = getContext('data') as Record<string, unknown>; + const data = getData(); const did = getContext('did') as string; </script> @@ -26,12 +26,10 @@ {:catch error} {error} {/await} +{:else if !data[collection] || (data[collection] as unknown[]).length === 0} + {@render empty?.()} {:else} {#each data[collection] as itemData} {@render item(itemData.value, () => {})} {/each} - - {#if (!data[collection] || (data[collection] as any[]).length === 0) && empty} - {@render empty()} - {/if} {/if} diff --git a/src/lib/website/components/MarkdownItem.svelte b/src/lib/website/components/MarkdownItem.svelte new file mode 100644 index 0000000..0a5a0a9 --- /dev/null +++ b/src/lib/website/components/MarkdownItem.svelte @@ -0,0 +1,26 @@ +<script lang="ts"> + import { type ElementType, type IndividualCollections } from '../data'; + import SingleRecord from './SingleRecord.svelte'; + import MarkdownText from './MarkdownText.svelte'; + + let { + collection, + rkey, + key, + placeholder, + defaultContent + }: { + collection: IndividualCollections; + rkey: ElementType<IndividualCollections>; + key: string; + placeholder?: string; + defaultContent?: string; + } = $props(); +</script> + + +<SingleRecord {collection} {rkey}> + {#snippet child(data)} + <MarkdownText {key} {placeholder} {defaultContent} {data} /> + {/snippet} +</SingleRecord> \ No newline at end of file diff --git a/src/lib/website/components/MarkdownText.svelte b/src/lib/website/components/MarkdownText.svelte new file mode 100644 index 0000000..da90113 --- /dev/null +++ b/src/lib/website/components/MarkdownText.svelte @@ -0,0 +1,29 @@ +<script lang="ts"> + import { Prose } from '@fuxui/base'; + import { marked } from 'marked'; + import { getContext } from 'svelte'; + + let { + key, + data, + placeholder, + defaultContent, + class: className + }: { + key: string; + data: Record<string, any>; + placeholder?: string; + defaultContent?: string; + class?: string; + } = $props(); +</script> + +{#if getContext('isEditing')} + {#await import('./MarkdownTextEditor.svelte') then { default: MarkdownTextEditor }} + <MarkdownTextEditor class={className} {key} {data} {placeholder} {defaultContent} /> + {/await} +{:else} + <Prose class={className}> + {@html marked.parse(data[key] ?? defaultContent ?? ('' as string))} + </Prose> +{/if} diff --git a/src/lib/website/MarkdownTextEditor.svelte b/src/lib/website/components/MarkdownTextEditor.svelte similarity index 67% rename from src/lib/website/MarkdownTextEditor.svelte rename to src/lib/website/components/MarkdownTextEditor.svelte index 9d6f7b1..88526e4 100644 --- a/src/lib/website/MarkdownTextEditor.svelte +++ b/src/lib/website/components/MarkdownTextEditor.svelte @@ -1,18 +1,16 @@ <script lang="ts"> - import { getContext, onDestroy, onMount } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; import { Editor, type Content, type Extensions } from '@tiptap/core'; import StarterKit from '@tiptap/starter-kit'; import Image from '@tiptap/extension-image'; import Placeholder from '@tiptap/extension-placeholder'; import Link from '@tiptap/extension-link'; import { Prose } from '@fuxui/base'; - import { getRecord, putRecord } from '$lib/oauth/atproto'; import { marked } from 'marked'; import { generateJSON } from '@tiptap/core'; - import type { UpdateFunction } from './context'; import TurndownService from 'turndown'; - import { client } from '$lib/oauth'; import { RichTextLink } from './extensions/RichTextLink'; + import { getUpdateRecordFunctions } from '../data'; let element: HTMLElement | undefined = $state(); let editor: Editor | null = $state(null); @@ -20,35 +18,29 @@ let loaded = $state(false); let edited = $state(false); - - let record: Awaited<ReturnType<typeof getRecord>>; - - let { collection, rkey, key, defaultContent, placeholder }: { - collection: string; - rkey: string; + + let { + key, + data, + class: className, + placeholder = '', + defaultContent = '' + }: { key: string; - defaultContent?: string; + data: Record<string, any>; + class?: string; placeholder?: string; + defaultContent?: string; } = $props(); - const updateFunctions = getContext('updateFunctions') as UpdateFunction[]; - - const did = getContext('did') as string; + const updateFunctions = getUpdateRecordFunctions(); const update = async () => { - console.log('update'); - - if (!edited) { - console.log('not updating, because not edited'); - return false; - } + if (!edited || !editor) return {}; - const html = editor?.getHTML(); + edited = false; - if (!html) { - console.log('not updating, because no html'); - return false; - } + const html = editor.getHTML(); var turndownService = new TurndownService({ headingStyle: 'atx', @@ -56,24 +48,11 @@ }); const markdown = turndownService.turndown(html); - console.log(markdown); - - if (!client.profile || client.profile.did !== did) { - console.log('not updating, because not authorized'); - return false; - } + edited = false; - await putRecord({ - collection, - rkey, - record: { - ...record?.value, - [key]: markdown, - updatedAt: new Date().toISOString() - } - }); - - return true; + return { + [key]: markdown + }; }; onMount(async () => { @@ -82,11 +61,7 @@ let json: Content = defaultContent ?? ''; try { - record = await getRecord({ did, collection, rkey }); - - console.log(record); - // parse to html - let html = await marked.parse(record.value[key] as string); + let html = await marked.parse(data[key] as string); // parse to json json = generateJSON(html, [ @@ -148,7 +123,7 @@ }); </script> -<Prose> +<Prose class={className}> <div bind:this={element}></div> </Prose> diff --git a/src/lib/website/PlainText.svelte b/src/lib/website/components/PlainText.svelte similarity index 62% rename from src/lib/website/PlainText.svelte rename to src/lib/website/components/PlainText.svelte index 71e6e64..a492f1c 100644 --- a/src/lib/website/PlainText.svelte +++ b/src/lib/website/components/PlainText.svelte @@ -5,19 +5,21 @@ key, data, placeholder, - defaultContent + defaultContent, + class: className }: { key: string; data: Record<string, any>; placeholder?: string; defaultContent?: string; + class?: string; } = $props(); </script> {#if getContext('isEditing')} {#await import('./PlainTextEditor.svelte') then { default: PlainTextEditor }} - <PlainTextEditor {key} {data} {placeholder} {defaultContent} /> + <PlainTextEditor class={className} {key} {data} {placeholder} {defaultContent} /> {/await} {:else} - <div>{data[key] || defaultContent || placeholder}</div> + <div class={className}>{data[key] || defaultContent || placeholder}</div> {/if} diff --git a/src/lib/website/PlainTextEditor.svelte b/src/lib/website/components/PlainTextEditor.svelte similarity index 67% rename from src/lib/website/PlainTextEditor.svelte rename to src/lib/website/components/PlainTextEditor.svelte index a0067de..800cb27 100644 --- a/src/lib/website/PlainTextEditor.svelte +++ b/src/lib/website/components/PlainTextEditor.svelte @@ -1,21 +1,18 @@ <script lang="ts"> - import { getContext, onDestroy, onMount } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; import { Editor, type Extensions } from '@tiptap/core'; - import StarterKit from '@tiptap/starter-kit'; - import type { UpdateRecordFunction } from './context'; import Placeholder from '@tiptap/extension-placeholder'; - import Paragraph from '@tiptap/extension-paragraph' - import Document from '@tiptap/extension-document' - import Text from '@tiptap/extension-text' + import Paragraph from '@tiptap/extension-paragraph'; + import Document from '@tiptap/extension-document'; + import Text from '@tiptap/extension-text'; + import { getUpdateRecordFunctions } from '../data'; let element: HTMLElement | undefined = $state(); let editor: Editor | null = $state(null); let edited = $state(false); - const updateFunctions = getContext('updateRecord') as UpdateRecordFunction[]; - - let updated = $state(false); + const updateFunctions = getUpdateRecordFunctions(); let { key, @@ -32,14 +29,13 @@ } = $props(); const update = async () => { - if(updated) { - updated = false; - console.log('update', key, editor?.getText()); - return { - [key]: editor?.getText() ?? '' - } - } - return {}; + if (!edited || !editor) return {}; + + edited = false; + + return { + [key]: editor.getText() + }; }; onMount(async () => { @@ -47,13 +43,9 @@ updateFunctions.push(update); - let extensions: Extensions = [ - Document.configure(), - Paragraph.configure(), - Text.configure(), - ]; + let extensions: Extensions = [Document.configure(), Paragraph.configure(), Text.configure()]; - if(placeholder) { + if (placeholder) { extensions.push( Placeholder.configure({ placeholder: placeholder @@ -69,8 +61,6 @@ }, onUpdate: () => { edited = true; - - updated = true; }, content: data[key] ?? defaultContent, @@ -93,7 +83,7 @@ </script> <div class={className} bind:this={element}></div> - + <style> :global(.tiptap p.is-editor-empty:first-child::before) { color: var(--color-base-800); diff --git a/src/lib/website/components/PlainTextItem.svelte b/src/lib/website/components/PlainTextItem.svelte new file mode 100644 index 0000000..874fa87 --- /dev/null +++ b/src/lib/website/components/PlainTextItem.svelte @@ -0,0 +1,26 @@ +<script lang="ts"> + import { type ElementType, type IndividualCollections } from '../data'; + import SingleRecord from './SingleRecord.svelte'; + import PlainText from './PlainText.svelte'; + + let { + collection, + rkey, + key, + placeholder, + defaultContent + }: { + collection: IndividualCollections; + rkey: ElementType<IndividualCollections>; + key: string; + placeholder?: string; + defaultContent?: string; + } = $props(); +</script> + + +<SingleRecord {collection} {rkey}> + {#snippet child(data)} + <PlainText {key} {placeholder} {defaultContent} {data} /> + {/snippet} +</SingleRecord> \ No newline at end of file diff --git a/src/lib/website/components/SingleRecord.svelte b/src/lib/website/components/SingleRecord.svelte new file mode 100644 index 0000000..a3eab1b --- /dev/null +++ b/src/lib/website/components/SingleRecord.svelte @@ -0,0 +1,42 @@ +<script lang="ts"> + import { getRecord } from '$lib/oauth/atproto'; + import { getContext, type Snippet } from 'svelte'; + import EditSingleRecord from './EditSingleRecord.svelte'; + import { getData, type ElementType, type IndividualCollections } from '../data'; + + let { + collection, + rkey, + child + }: { + collection: IndividualCollections; + rkey: ElementType<IndividualCollections>; + child: Snippet<[any]>; + } = $props(); + + const data = getData(); + const did = getContext('did') as string; +</script> + +{#if getContext('isEditing')} + {#await getRecord({ did, collection, rkey }) then record} + <EditSingleRecord data={record}> + {#snippet child(recordData)} + {@render child(recordData)} + {/snippet} + </EditSingleRecord> + {:catch error} + <EditSingleRecord + data={{ + uri: 'at://' + did + '/' + collection + '/' + rkey, + value: {} + }} + > + {#snippet child(recordData)} + {@render child(recordData)} + {/snippet} + </EditSingleRecord> + {/await} +{:else} + {@render child(data?.[collection]?.[rkey]?.value)} +{/if} diff --git a/src/lib/website/extensions/RichTextLink.ts b/src/lib/website/components/extensions/RichTextLink.ts similarity index 100% rename from src/lib/website/extensions/RichTextLink.ts rename to src/lib/website/components/extensions/RichTextLink.ts diff --git a/src/lib/website/components/index.ts b/src/lib/website/components/index.ts new file mode 100644 index 0000000..8a8ad83 --- /dev/null +++ b/src/lib/website/components/index.ts @@ -0,0 +1,2 @@ +export { default as List } from './List.svelte'; +export { default as PlainTextItem } from './PlainTextItem.svelte'; diff --git a/src/lib/website/context.ts b/src/lib/website/context.ts deleted file mode 100644 index d3f374b..0000000 --- a/src/lib/website/context.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type UpdateFunction = () => boolean | Promise<boolean>; - -export type UpdateRecordFunction = () => Record<string, any> | Promise<Record<string, any>>; \ No newline at end of file diff --git a/src/lib/website/data.ts b/src/lib/website/data.ts index 1af6082..a29ffd1 100644 --- a/src/lib/website/data.ts +++ b/src/lib/website/data.ts @@ -1,23 +1,76 @@ +import { getRecord, listRecords, resolveHandle } from '$lib/oauth/atproto'; +import type { Record as ListRecord } from '@atproto/api/dist/client/types/com/atproto/repo/listRecords'; +import { getContext } from 'svelte'; +import { PUBLIC_HANDLE } from '$env/static/public'; + // collections and records we want to grab export const data = { - 'dev.flo-bit.about': ['self', 'test'], - 'dev.flo-bit.test': ['self'], + 'dev.flo-bit.about': ['test'], 'link.flo-bit.dev': 'all' } as const; export const handle = 'flo-bit.dev'; export type Collection = keyof typeof data; -export type Record = (typeof data)[Collection]; - -export type CollectionRecord<C extends Collection> = - // does data[C] extend a readonly array? - (typeof data)[C] extends readonly (infer U)[] - ? U // yes → U is the element type (e.g. "self") - : any; // no → value must have been "all" export function parseUri(uri: string) { // at://did:plc:257wekqxg4hyapkq6k47igmp/link.flo-bit.dev/3lnblfznvhr2a const [did, collection, rkey] = uri.split('/').slice(2); return { did, collection, rkey }; -} \ No newline at end of file +} + +export type IndividualCollections = { + [K in Collection]: (typeof data)[K] extends readonly unknown[] ? K : never; +}[Collection]; + +export type ListCollections = Exclude<Collection, IndividualCollections>; + +export type ElementType<C extends Collection> = (typeof data)[C] extends readonly (infer U)[] + ? U + : unknown; + +export type DownloadedData = { [C in IndividualCollections]: Record<string, unknown> } & { + [C in ListCollections]: ListRecord[]; +}; + +export function getData(): DownloadedData { + return getContext('data'); +} + +export async function loadData() { + const did = await resolveHandle({ handle: PUBLIC_HANDLE }); + + const downloadedData = {} as DownloadedData; + + for (const collection of Object.keys(data) as Collection[]) { + const cfg = data[collection]; + + try { + if (Array.isArray(cfg)) { + for (const rkey of cfg) { + const record = await getRecord({ did, collection, rkey }); + downloadedData[collection as IndividualCollections] ??= {} as Record<string, unknown>; + downloadedData[collection as IndividualCollections][rkey] = record; + } + } else if (cfg === 'all') { + const records = await listRecords({ did, collection }); + downloadedData[collection as ListCollections] = records; + } + } catch (error) { + console.error(error); + } + } + return { did, data: downloadedData }; +} + +export type UpdateFunction = () => boolean | Promise<boolean>; + +export type UpdateRecordFunction = () => Record<string, unknown> | Promise<Record<string, unknown>>; + +export function getUpdateFunctions(): UpdateFunction[] { + return getContext('updateFunctions'); +} + +export function getUpdateRecordFunctions(): UpdateRecordFunction[] { + return getContext('updateRecordFunctions'); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f3db058..65865cd 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,7 +2,7 @@ import '../app.css'; import { onMount } from 'svelte'; - import { initClient, LoginModal } from '$lib/oauth'; + import { initClient } from '$lib/oauth'; let { children } = $props(); @@ -12,5 +12,3 @@ </script> {@render children()} - -<LoginModal /> diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 4ad9b42..d4fb552 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,37 +1,5 @@ -import { getRecord, listRecords, resolveHandle } from '$lib/oauth/atproto'; -import { data, type Collection, type CollectionRecord } from '$lib/website/data'; - -import { PUBLIC_HANDLE } from '$env/static/public'; - -export type DownloadedData = { - [C in Collection]: (typeof data)[C] extends readonly (infer U)[] - ? Record<U, unknown> - : Record<string, unknown>; -}; +import { loadData } from '$lib/website/data'; export async function load() { - const did = await resolveHandle({ handle: PUBLIC_HANDLE }); - - const downloadedData = {} as DownloadedData; - - for (const collection of Object.keys(data) as Collection[]) { - const cfg = data[collection]; - - try { - if (Array.isArray(cfg)) { - for (const rkey of cfg as readonly CollectionRecord<typeof collection>[]) { - const record = await getRecord({ did, collection, rkey }); - downloadedData[collection] ??= {} as Record<string, unknown>; - downloadedData[collection][rkey] = record; - } - } else if (cfg === 'all') { - const records = await listRecords({ did, collection }); - downloadedData[collection] = records; - } - } catch (error) { - console.error(error); - } - } - - return downloadedData; + return await loadData(); } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 01de663..e56bb34 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,10 +1,11 @@ <script> - import Website from "$lib/website/Website.svelte"; + import Website from "$lib/Website.svelte"; import { setContext } from "svelte"; - setContext('did', 'did:plc:257wekqxg4hyapkq6k47igmp'); - let { data } = $props(); + + setContext('did', data.did); + setContext('data', data.data); </script> -<Website {data} /> +<Website /> diff --git a/src/routes/edit/+page.server.ts b/src/routes/edit/+page.server.ts new file mode 100644 index 0000000..b8d2071 --- /dev/null +++ b/src/routes/edit/+page.server.ts @@ -0,0 +1,8 @@ +import { PUBLIC_HANDLE } from '$env/static/public'; +import { resolveHandle } from '$lib/oauth/atproto'; + +export async function load() { + const did = await resolveHandle({ handle: PUBLIC_HANDLE }); + + return { did }; +} diff --git a/src/routes/edit/+page.svelte b/src/routes/edit/+page.svelte index a206474..c7d523b 100644 --- a/src/routes/edit/+page.svelte +++ b/src/routes/edit/+page.svelte @@ -1,23 +1,21 @@ <script lang="ts"> - import type { UpdateFunction } from '$lib/website/context.js'; - import Website from '$lib/website/Website.svelte'; + import Website from '$lib/Website.svelte'; import { setContext } from 'svelte'; import { BlueskyLogin } from '@fuxui/social'; import { client, login } from '$lib/oauth/auth.svelte.js'; import { Button } from '@fuxui/base'; - - setContext('isEditing', true); - - setContext('did', 'did:plc:257wekqxg4hyapkq6k47igmp'); + import type { UpdateFunction } from '$lib/website/data.js'; let updateFunctions: UpdateFunction[] = $state([]); - setContext('updateFunctions', updateFunctions); - let { data } = $props(); + + setContext('isEditing', true); + setContext('did', data.did); + setContext('updateFunctions', updateFunctions); </script> -<Website {data} /> +<Website /> <Button class="absolute right-2 bottom-2" @@ -31,7 +29,7 @@ </Button> {#if !client.isInitializing && !client.isLoggedIn} - <div class="absolute right-0 bottom-0"> + <div class="absolute left-2 bottom-2"> <BlueskyLogin login={async (handle) => { await login(handle); diff --git a/svelte.config.js b/svelte.config.js index 4b8510e..d757fec 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -10,7 +10,7 @@ const config = { kit: { adapter: adapter(), paths: { - base: process.env.NODE_ENV === 'development' ? '' : '/svelte-atproto-client-oauth' + base: process.env.NODE_ENV === 'development' ? '' : '/editable-website-test' } } };