);
//hydrateRoot(document.getElementById("root")!, );
diff --git a/shared-landing/browser/landing-shared.tsx b/shared-landing/browser/landing-shared.tsx
index 0d88831..a83cb85 100644
--- a/shared-landing/browser/landing-shared.tsx
+++ b/shared-landing/browser/landing-shared.tsx
@@ -64,7 +64,7 @@ function Header({
Skylite {capitaltitle} Server{" "}
{instancehost && (
- ({/*instancehost*/"alpha"})
+ ({/*instancehost*/ "alpha"})
)}
@@ -269,8 +269,7 @@ function InstanceInfo({
return (
<>
- About {instanceConfig.name} (
- {window.location.hostname})
+ About {instanceConfig.name} ({config?.host})
{instanceConfig.description}
{config && (
@@ -571,8 +570,32 @@ function RegistrationForm({
);
}
-export function App({ type }: { type: "index" | "view" }) {
+export function App({
+ type,
+ initialData,
+}: {
+ type: "index" | "view";
+ initialData?: {
+ config: {
+ inviteOnly: boolean;
+ //port: number;
+ did: string;
+ host: string;
+ indexPriority?: string[];
+ };
+ users: {
+ did: string;
+ role: string;
+ registrationdate: string;
+ onboardingstatus: string;
+ pfp?: string;
+ displayname: string;
+ handle: string;
+ }[];
+ };
+}) {
const { agent, loginStatus, loading } = useAuth();
+ console.log("hasinitialdata?",initialData ? true : false);
const [users, setUsers] = useState<
{
did: string;
@@ -583,8 +606,9 @@ export function App({ type }: { type: "index" | "view" }) {
displayname: string;
handle: string;
}[]
- >([]);
- const [usersLoading, setUsersLoading] = useState(true);
+ >(initialData?.users ?? []);
+ const [usersLoading, setUsersLoading] = useState(!initialData?.users?.length);
+
const isIndex = type === "index";
const capitaltitle = isIndex ? "Index" : "View";
@@ -598,9 +622,10 @@ export function App({ type }: { type: "index" | "view" }) {
indexPriority?: string[];
}
| undefined
- >();
- const [configloading, setconfigloading] = useState(true);
+ >(initialData?.config ?? undefined);
+ const [configloading, setconfigloading] = useState(!initialData?.config);
useEffect(() => {
+ if (config) return
async function fetchConfig() {
try {
const response = await fetch("/_unspecced/config");
@@ -618,6 +643,7 @@ export function App({ type }: { type: "index" | "view" }) {
}, []);
useEffect(() => {
+ if (users.length) return
async function fetchUsers() {
try {
const response = await fetch("/_unspecced/users");
@@ -693,3 +719,34 @@ export function App({ type }: { type: "index" | "view" }) {
);
}
+
+export function Root({
+ type,
+ initialData,
+}: {
+ type: "index" | "view";
+ initialData?: {
+ config: {
+ inviteOnly: boolean;
+ //port: number;
+ did: string;
+ host: string;
+ indexPriority?: string[];
+ };
+ users: {
+ did: string;
+ role: string;
+ registrationdate: string;
+ onboardingstatus: string;
+ pfp?: string;
+ displayname: string;
+ handle: string;
+ }[];
+ };
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/shared-landing/browser/landing-view.tsx b/shared-landing/browser/landing-view.tsx
index a815ba0..abe7dcb 100644
--- a/shared-landing/browser/landing-view.tsx
+++ b/shared-landing/browser/landing-view.tsx
@@ -2,11 +2,13 @@ import React, { useState } from "https://esm.sh/react@19.1.1";
import { createRoot, hydrateRoot } from "https://esm.sh/react-dom@19.1.1/client";
import * as ATPAPI from "https://esm.sh/@atproto/api";
import { AuthProvider } from "./passauthprovider.tsx";
-import { App } from "./landing-shared.tsx";
+import { Root } from "./landing-shared.tsx";
+
+const initialDataEl = document.getElementById("initial-data");
+const initialData = initialDataEl ? JSON.parse(initialDataEl.textContent!) : undefined;
+// rule of thumb this is the only place where we can do browser-only code
createRoot(document.getElementById("root")!).render(
-
-
-
+
);
//hydrateRoot(document.getElementById("root")!, );
diff --git a/shared-landing/build.ts b/shared-landing/build.ts
index dccc2ff..4a4cc01 100644
--- a/shared-landing/build.ts
+++ b/shared-landing/build.ts
@@ -5,9 +5,35 @@ import tailwindconfig from "../tailwind.config.ts";
import tailwindcss from "https://esm.sh/tailwindcss@3";
import postcss from "https://esm.sh/postcss@8";
import autoprefixer from "https://esm.sh/autoprefixer@10";
+import { renderToString } from "https://esm.sh/react-dom@19.1.1/server";
+import { createElement } from "https://esm.sh/react@19.1.1";
+import { Root } from "./browser/landing-shared.tsx";
// helper build function
-async function build(entry: string) {
+async function build({
+ entry,
+ initialData,
+}: {
+ entry: "index" | "view";
+ initialData?: {
+ config: {
+ inviteOnly: boolean;
+ //port: number;
+ did: string;
+ host: string;
+ indexPriority?: string[];
+ };
+ users: {
+ did: string;
+ role: string;
+ registrationdate: string;
+ onboardingstatus: string;
+ pfp?: string;
+ displayname: string;
+ handle: string;
+ }[];
+ };
+}) {
const template = await Deno.readTextFile(
`./shared-landing/template-${entry}.html`
);
@@ -27,7 +53,7 @@ async function build(entry: string) {
loader: { ".tsx": "tsx" },
jsxFactory: "React.createElement",
jsxFragment: "React.Fragment",
- platform: "browser",
+ platform: "neutral", //"browser",
external: ["react/jsx-runtime", "react/compiler-runtime"],
plugins: [
cache({ importmap, directory: "./cache" }),
@@ -56,26 +82,88 @@ async function build(entry: string) {
const html = template.replace(
"",
`
- `
+
+
+ `
+ );
+ // const html = template.replace(
+ // "",
+ // `
+ // `
+ // );
+ const ssr = renderToString(
+ createElement(
+ () =>
+ Root({
+ type: entry,
+ initialData
+ }),
+ null
+ )
);
+ const ssrhtml = html.replace("", `${ssr}`);
- return { js, html, css: cssResult.css };
+ return { js, html: ssrhtml, css: cssResult.css };
}
// public compile function
-export async function compile(target: "index" | "view") {
- return await build(target);
+export async function compile({
+ target,
+ initialData
+}: {
+ target: "index" | "view";
+ initialData?: {
+ config: {
+ inviteOnly: boolean;
+ //port: number;
+ did: string;
+ host: string;
+ indexPriority?: string[];
+ };
+ users: {
+ did: string;
+ role: string;
+ registrationdate: string;
+ onboardingstatus: string;
+ pfp?: string;
+ displayname: string;
+ handle: string;
+ }[];
+ };
+}) {
+ return await build({entry: target, initialData});
}
// watch loop
-export async function devWatch(
+export async function devWatch({target,initialData,onBuild}:{
target: "index" | "view",
+ initialData?: {
+ config: {
+ inviteOnly: boolean;
+ //port: number;
+ did: string;
+ host: string;
+ indexPriority?: string[];
+ };
+ users: {
+ did: string;
+ role: string;
+ registrationdate: string;
+ onboardingstatus: string;
+ pfp?: string;
+ displayname: string;
+ handle: string;
+ }[];
+ },
onBuild: (data: { js: string; html: string; css: string }) => void
+ }
) {
for await (const event of Deno.watchFs(".")) {
if (event.paths.some((p) => p.endsWith(".tsx"))) {
console.log("Rebuilding bundle…");
- const data = await compile(target);
+ const data = await compile({target,initialData});
onBuild(data);
}
}
@@ -84,13 +172,13 @@ export async function devWatch(
async function hashString(content: string): Promise {
const encoder = new TextEncoder();
const data = encoder.encode(content);
-
+
// SHA-256 hash
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
-
+
// Convert buffer to hex string
return Array.from(new Uint8Array(hashBuffer))
- .map(b => b.toString(16).padStart(2, "0"))
+ .map((b) => b.toString(16).padStart(2, "0"))
.join("")
.slice(0, 8); // optional: shorten hash for filenames
-}
+}
\ No newline at end of file
diff --git a/shared-landing/template-index.html b/shared-landing/template-index.html
index 5e02490..3f54e79 100644
--- a/shared-landing/template-index.html
+++ b/shared-landing/template-index.html
@@ -9,6 +9,6 @@
-
+