diff --git a/src/Thread.tsx b/src/Thread.tsx
new file mode 100644
index 0000000..55f34ed
--- /dev/null
+++ b/src/Thread.tsx
@@ -0,0 +1,159 @@
+import { For, ParentProps, Show, VoidProps, createEffect, createResource, createSignal, onCleanup, onMount } from "solid-js";
+import "./App.scss";
+import { Client, Room, Thread, Timeline } from "sdk";
+import { Message, TextBlock, ThreadsItem } from "./Room";
+// import { computePosition, shift, offset, autoUpdate } from "@floating-ui/dom";
+// import { Portal } from "solid-js/web";
+import { Time } from "./Atoms";
+import { ThreadTimeline } from "sdk/dist/src/timeline";
+import { debounce } from "@solid-primitives/scheduled";
+import { Editor } from "./Editor";
+// import { createVirtualizer } from "@tanstack/solid-virtual";
+
+export function ThreadView(props: VoidProps<{ thread: Thread }>) {
+ let textareaEl: HTMLTextAreaElement;
+
+ function handleKeyDown(e: KeyboardEvent) {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ props.thread.room.sendEvent("m.message", {
+ text: [{ body: textareaEl.value }],
+ "m.relations": [{ rel_type: "m.thread", event_id: props.thread.id }],
+ });
+ textareaEl.value = "";
+ }
+ }
+
+ const [timeline, { mutate }] = createResource(() => props.thread, async (thread) => {
+ return thread.room.timelines.forThread(thread, "end");
+ }, {
+ storage: (init) => createSignal(init, { equals: false }),
+ });
+
+ let scrollEl: HTMLDivElement;
+ let isAutoscrolling = false;
+ const [isPaginating, setIsPaginating] = createSignal(false);
+ const [isAtBeginning, setIsAtBeginning] = createSignal(false);
+
+ const refresh = () => {
+ mutate(timeline());
+ if (isAutoscrolling) scrollEl.scrollBy(0, 999999);
+ };
+
+ let oldTimeline: ThreadTimeline | undefined;
+ createEffect(() => {
+ oldTimeline?.off("timelineUpdate", refresh);
+ oldTimeline?.off("timelineAppend", refresh);
+ timeline()?.on("timelineUpdate", refresh);
+ timeline()?.on("timelineAppend", refresh);
+ oldTimeline = timeline();
+ setIsAtBeginning(timeline()?.isAtBeginning || false);
+ }, timeline);
+ onCleanup(() => {
+ oldTimeline?.off("timelineUpdate", refresh);
+ oldTimeline?.off("timelineAppend", refresh);
+ });
+
+ const AUTOSCROLL_MARGIN = 5;
+ const PAGINATE_MARGIN = 500;
+
+ const paginate = async () => {
+ isAutoscrolling = scrollEl.scrollTop + scrollEl.offsetHeight > scrollEl.scrollHeight - AUTOSCROLL_MARGIN;
+
+ if (scrollEl.scrollTop < PAGINATE_MARGIN && !isPaginating() && !isAtBeginning()) {
+ const scrollRefEl = scrollEl.querySelector(".message") as HTMLElement | undefined;
+ const oldOffsetTop = (scrollRefEl?.offsetTop || 0);
+ setIsPaginating(true);
+ await timeline()?.paginate("b", 10);
+ const newOffsetTop = scrollRefEl?.offsetTop || 0;
+ refresh();
+ scrollEl.scrollBy(0, newOffsetTop - oldOffsetTop);
+ // scrollEl.scrollTo(0, oldOffsetTop);
+ setIsPaginating(false);
+ if (timeline()?.isAtBeginning) setIsAtBeginning(true);
+ }
+ };
+
+ const handleScroll = debounce(paginate, 100);
+ const events = () => timeline()?.getEvents().filter(ev => ev.type === "m.message");
+ // const eventsLength = () => events()?.length || 0;
+
+ // const virt = createVirtualizer({
+ // count: 100,
+ // estimateSize: () => 50,
+ // getScrollElement: () => scrollEl,
+ // });
+
+ // const items = () => virt.getVirtualItems();
+
+ //
+ // {items().map(it => {
+ // return
+ // {it.index}
+ //
;
+ // })}
+ //
+
+ return (
+
+ );
+}
+
+function ThreadInfo(props: VoidProps<{ thread: Thread, showHeader: boolean }>) {
+ const event = () => props.thread.baseEvent;
+ let headerEl: HTMLHeadingElement;
+ const [stuck, setStuck] = createSignal(false);
+
+ // FIXME: this feels hacky, and has a flash of "unstuck" style + 1px offsets occasionally
+
+ onMount(() => {
+ const observer = new IntersectionObserver(([e]) => setStuck(!e.isIntersecting), {
+ threshold: 1,
+ root: headerEl.parentElement,
+ });
+
+ observer.observe(headerEl);
+ onCleanup(() => observer.unobserve(headerEl));
+ });
+
+ return (
+ <>
+ {props.showHeader &&
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/tsconfig.json b/tsconfig.json
index db334f8..c71679c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -18,8 +18,6 @@
/* Linting */
"strict": true,
"strictNullChecks": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],