composing toots #21
					 6 changed files with 363 additions and 73 deletions
				
			
		| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
.bottomSheet {
 | 
					.bottomSheet {
 | 
				
			||||||
  composes: surface from "material.module.css";
 | 
					  composes: surface from "./material.module.css";
 | 
				
			||||||
 | 
					  composes: cardGutSkip from "./cards.module.css";
 | 
				
			||||||
 | 
					  composes: cardNoPad from "./cards.module.css";
 | 
				
			||||||
  border: none;
 | 
					  border: none;
 | 
				
			||||||
  position: absolute;
 | 
					  position: absolute;
 | 
				
			||||||
  left: 50%;
 | 
					  left: 50%;
 | 
				
			||||||
| 
						 | 
					@ -47,4 +49,10 @@
 | 
				
			||||||
      opacity: 0;
 | 
					      opacity: 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &.bottom {
 | 
				
			||||||
 | 
					    top: unset;
 | 
				
			||||||
 | 
					    transform: translateX(-50%);
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,9 +13,12 @@ import {
 | 
				
			||||||
} from "solid-js";
 | 
					} from "solid-js";
 | 
				
			||||||
import styles from "./BottomSheet.module.css";
 | 
					import styles from "./BottomSheet.module.css";
 | 
				
			||||||
import { useHeroSignal } from "../platform/anim";
 | 
					import { useHeroSignal } from "../platform/anim";
 | 
				
			||||||
 | 
					import { makeEventListener } from "@solid-primitives/event-listener";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type BottomSheetProps = {
 | 
					export type BottomSheetProps = {
 | 
				
			||||||
  open?: boolean;
 | 
					  open?: boolean;
 | 
				
			||||||
 | 
					  bottomUp?: boolean;
 | 
				
			||||||
 | 
					  onClose?(reason: "backdrop"): void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const HERO = Symbol("BottomSheet Hero Symbol");
 | 
					export const HERO = Symbol("BottomSheet Hero Symbol");
 | 
				
			||||||
| 
						 | 
					@ -123,8 +126,28 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMount(() => {
 | 
				
			||||||
 | 
					    makeEventListener(element, "click", (event) => {
 | 
				
			||||||
 | 
					      const rect = element.getBoundingClientRect();
 | 
				
			||||||
 | 
					      const isInDialog =
 | 
				
			||||||
 | 
					        rect.top <= event.clientY &&
 | 
				
			||||||
 | 
					        event.clientY <= rect.top + rect.height &&
 | 
				
			||||||
 | 
					        rect.left <= event.clientX &&
 | 
				
			||||||
 | 
					        event.clientX <= rect.left + rect.width;
 | 
				
			||||||
 | 
					      if (!isInDialog) {
 | 
				
			||||||
 | 
					        props.onClose?.("backdrop");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <dialog class={styles.bottomSheet} ref={element!}>
 | 
					    <dialog
 | 
				
			||||||
 | 
					      classList={{
 | 
				
			||||||
 | 
					        [styles.bottomSheet]: true,
 | 
				
			||||||
 | 
					        [styles.bottom]: props.bottomUp,
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      ref={element!}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
      {ochildren() ?? cache()}
 | 
					      {ochildren() ?? cache()}
 | 
				
			||||||
    </dialog>
 | 
					    </dialog>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ import { css } from "solid-styled";
 | 
				
			||||||
interface ScaffoldProps {
 | 
					interface ScaffoldProps {
 | 
				
			||||||
  topbar?: JSX.Element;
 | 
					  topbar?: JSX.Element;
 | 
				
			||||||
  fab?: JSX.Element;
 | 
					  fab?: JSX.Element;
 | 
				
			||||||
 | 
					  bottom?: JSX.Element;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Scaffold: ParentComponent<ScaffoldProps> = (props) => {
 | 
					const Scaffold: ParentComponent<ScaffoldProps> = (props) => {
 | 
				
			||||||
| 
						 | 
					@ -36,6 +37,16 @@ const Scaffold: ParentComponent<ScaffoldProps> = (props) => {
 | 
				
			||||||
      right: 40px;
 | 
					      right: 40px;
 | 
				
			||||||
      z-index: var(--tutu-zidx-nav, auto);
 | 
					      z-index: var(--tutu-zidx-nav, auto);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .bottom-dock {
 | 
				
			||||||
 | 
					      position: sticky;
 | 
				
			||||||
 | 
					      bottom: 0;
 | 
				
			||||||
 | 
					      left: 0;
 | 
				
			||||||
 | 
					      right: 0;
 | 
				
			||||||
 | 
					      z-index: var(--tutu-zidx-nav, auto);
 | 
				
			||||||
 | 
					      padding-bottom: var(--safe-area-inset-bottom, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  `;
 | 
					  `;
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
| 
						 | 
					@ -48,6 +59,9 @@ const Scaffold: ParentComponent<ScaffoldProps> = (props) => {
 | 
				
			||||||
        <div class="fab-dock">{props.fab}</div>
 | 
					        <div class="fab-dock">{props.fab}</div>
 | 
				
			||||||
      </Show>
 | 
					      </Show>
 | 
				
			||||||
      <div class="scaffold-content">{props.children}</div>
 | 
					      <div class="scaffold-content">{props.children}</div>
 | 
				
			||||||
 | 
					      <Show when={props.bottom}>
 | 
				
			||||||
 | 
					        <div class="bottom-dock">{props.bottom}</div>
 | 
				
			||||||
 | 
					      </Show>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										236
									
								
								src/timelines/ReplyEditor.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								src/timelines/ReplyEditor.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,236 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  createSignal,
 | 
				
			||||||
 | 
					  createUniqueId,
 | 
				
			||||||
 | 
					  onMount,
 | 
				
			||||||
 | 
					  type Component,
 | 
				
			||||||
 | 
					  type Setter,
 | 
				
			||||||
 | 
					} from "solid-js";
 | 
				
			||||||
 | 
					import Scaffold from "../material/Scaffold";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Avatar,
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  IconButton,
 | 
				
			||||||
 | 
					  List,
 | 
				
			||||||
 | 
					  ListItemButton,
 | 
				
			||||||
 | 
					  ListItemIcon,
 | 
				
			||||||
 | 
					  ListItemSecondaryAction,
 | 
				
			||||||
 | 
					  ListItemText,
 | 
				
			||||||
 | 
					  Radio,
 | 
				
			||||||
 | 
					  Switch,
 | 
				
			||||||
 | 
					  Divider,
 | 
				
			||||||
 | 
					} from "@suid/material";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ArrowDropDown,
 | 
				
			||||||
 | 
					  Public as PublicIcon,
 | 
				
			||||||
 | 
					  Send,
 | 
				
			||||||
 | 
					  People as PeopleIcon,
 | 
				
			||||||
 | 
					  ThreeP as ThreePIcon,
 | 
				
			||||||
 | 
					  ListAlt as ListAltIcon,
 | 
				
			||||||
 | 
					} from "@suid/icons-material";
 | 
				
			||||||
 | 
					import type { Account } from "../accounts/stores";
 | 
				
			||||||
 | 
					import tootComposers from "./TootComposer.module.css";
 | 
				
			||||||
 | 
					import { makeEventListener } from "@solid-primitives/event-listener";
 | 
				
			||||||
 | 
					import BottomSheet from "../material/BottomSheet";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TootVisibility = "public" | "unlisted" | "private" | "direct";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TootVisibilityPickerDialog: Component<{
 | 
				
			||||||
 | 
					  open?: boolean;
 | 
				
			||||||
 | 
					  onClose: () => void;
 | 
				
			||||||
 | 
					  visibility: TootVisibility;
 | 
				
			||||||
 | 
					  onVisibilityChange: (value: TootVisibility) => void;
 | 
				
			||||||
 | 
					}> = (props) => {
 | 
				
			||||||
 | 
					  type Kind = "public" | "private" | "direct";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const kind = () =>
 | 
				
			||||||
 | 
					    props.visibility === "public" || props.visibility === "unlisted"
 | 
				
			||||||
 | 
					      ? "public"
 | 
				
			||||||
 | 
					      : props.visibility;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const setKind = (nv: Kind) => {
 | 
				
			||||||
 | 
					    if (nv == "public") {
 | 
				
			||||||
 | 
					      props.onVisibilityChange(discoverable() ? "public" : "unlisted");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      props.onVisibilityChange(nv);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const discoverable = () => {
 | 
				
			||||||
 | 
					    return props.visibility === "public";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const setDiscoverable = (setter: (v: boolean) => boolean) => {
 | 
				
			||||||
 | 
					    const nval = setter(discoverable())
 | 
				
			||||||
 | 
					    props.onVisibilityChange(nval ? "public" : "unlisted"); // trigger change
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <BottomSheet open={props.open} onClose={props.onClose} bottomUp>
 | 
				
			||||||
 | 
					      <Scaffold
 | 
				
			||||||
 | 
					        bottom={
 | 
				
			||||||
 | 
					          <div
 | 
				
			||||||
 | 
					            style={{
 | 
				
			||||||
 | 
					              "border-top": "1px solid #ddd",
 | 
				
			||||||
 | 
					              background: "var(--tutu-color-surface)",
 | 
				
			||||||
 | 
					              padding: "8px 16px",
 | 
				
			||||||
 | 
					              width: "100%",
 | 
				
			||||||
 | 
					              "text-align": "end",
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <Button onClick={props.onClose}>Confirm</Button>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <List>
 | 
				
			||||||
 | 
					          <ListItemButton onClick={[setKind, "public"]}>
 | 
				
			||||||
 | 
					            <ListItemIcon>
 | 
				
			||||||
 | 
					              <PublicIcon />
 | 
				
			||||||
 | 
					            </ListItemIcon>
 | 
				
			||||||
 | 
					            <ListItemText
 | 
				
			||||||
 | 
					              primary="Public"
 | 
				
			||||||
 | 
					              secondary="Everyone can see this toot"
 | 
				
			||||||
 | 
					            ></ListItemText>
 | 
				
			||||||
 | 
					            <ListItemSecondaryAction>
 | 
				
			||||||
 | 
					              <Radio checked={kind() == "public"}></Radio>
 | 
				
			||||||
 | 
					            </ListItemSecondaryAction>
 | 
				
			||||||
 | 
					          </ListItemButton>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <ListItemButton
 | 
				
			||||||
 | 
					            sx={{ paddingLeft: "40px" }}
 | 
				
			||||||
 | 
					            disabled={kind() !== "public"}
 | 
				
			||||||
 | 
					            onClick={() => setDiscoverable((x) => !x)}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <ListItemIcon>
 | 
				
			||||||
 | 
					              <ListAltIcon />
 | 
				
			||||||
 | 
					            </ListItemIcon>
 | 
				
			||||||
 | 
					            <ListItemText
 | 
				
			||||||
 | 
					              primary="Discoverable"
 | 
				
			||||||
 | 
					              secondary="The others can discover it on the exploration."
 | 
				
			||||||
 | 
					            ></ListItemText>
 | 
				
			||||||
 | 
					            <ListItemSecondaryAction>
 | 
				
			||||||
 | 
					              <Switch
 | 
				
			||||||
 | 
					                checked={discoverable()}
 | 
				
			||||||
 | 
					                disabled={kind() !== "public"}
 | 
				
			||||||
 | 
					              ></Switch>
 | 
				
			||||||
 | 
					            </ListItemSecondaryAction>
 | 
				
			||||||
 | 
					          </ListItemButton>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <Divider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <ListItemButton onClick={[setKind, "private"]}>
 | 
				
			||||||
 | 
					            <ListItemIcon>
 | 
				
			||||||
 | 
					              <PeopleIcon />
 | 
				
			||||||
 | 
					            </ListItemIcon>
 | 
				
			||||||
 | 
					            <ListItemText
 | 
				
			||||||
 | 
					              primary="Only Followers"
 | 
				
			||||||
 | 
					              secondary="Visibile for followers only"
 | 
				
			||||||
 | 
					            ></ListItemText>
 | 
				
			||||||
 | 
					            <ListItemSecondaryAction>
 | 
				
			||||||
 | 
					              <Radio checked={kind() == "private"}></Radio>
 | 
				
			||||||
 | 
					            </ListItemSecondaryAction>
 | 
				
			||||||
 | 
					          </ListItemButton>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <Divider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <ListItemButton onClick={[setKind, "direct"]}>
 | 
				
			||||||
 | 
					            <ListItemIcon>
 | 
				
			||||||
 | 
					              <ThreePIcon />
 | 
				
			||||||
 | 
					            </ListItemIcon>
 | 
				
			||||||
 | 
					            <ListItemText
 | 
				
			||||||
 | 
					              primary="Only Mentions"
 | 
				
			||||||
 | 
					              secondary="Visible for mentioned users only"
 | 
				
			||||||
 | 
					            ></ListItemText>
 | 
				
			||||||
 | 
					            <ListItemSecondaryAction>
 | 
				
			||||||
 | 
					              <Radio checked={kind() == "direct"}></Radio>
 | 
				
			||||||
 | 
					            </ListItemSecondaryAction>
 | 
				
			||||||
 | 
					          </ListItemButton>
 | 
				
			||||||
 | 
					        </List>
 | 
				
			||||||
 | 
					      </Scaffold>
 | 
				
			||||||
 | 
					    </BottomSheet>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ReplyEditor: Component<{
 | 
				
			||||||
 | 
					  profile: Account;
 | 
				
			||||||
 | 
					  replyToDisplayName: string;
 | 
				
			||||||
 | 
					}> = (props) => {
 | 
				
			||||||
 | 
					  let inputRef: HTMLTextAreaElement;
 | 
				
			||||||
 | 
					  const buttonId = createUniqueId();
 | 
				
			||||||
 | 
					  const menuId = createUniqueId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [typing, setTyping] = createSignal(false);
 | 
				
			||||||
 | 
					  const [visibility, setVisibility] = createSignal<TootVisibility>("public");
 | 
				
			||||||
 | 
					  const [permPicker, setPermPicker] = createSignal(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMount(() => {
 | 
				
			||||||
 | 
					    makeEventListener(inputRef, "focus", () => setTyping(true));
 | 
				
			||||||
 | 
					    makeEventListener(inputRef, "blur", () => setTyping(false));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const containerStyle = () =>
 | 
				
			||||||
 | 
					    typing()
 | 
				
			||||||
 | 
					      ? {
 | 
				
			||||||
 | 
					          position: "sticky" as const,
 | 
				
			||||||
 | 
					          top: "var(--scaffold-topbar-height, 0)",
 | 
				
			||||||
 | 
					          bottom: "var(--safe-area-inset-bottom, 0)",
 | 
				
			||||||
 | 
					          "z-index": 1,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const visibilityText = () => {
 | 
				
			||||||
 | 
					    switch (visibility()) {
 | 
				
			||||||
 | 
					      case "public":
 | 
				
			||||||
 | 
					        return "Discoverable";
 | 
				
			||||||
 | 
					      case "unlisted":
 | 
				
			||||||
 | 
					        return "Public";
 | 
				
			||||||
 | 
					      case "private":
 | 
				
			||||||
 | 
					        return "Only Followers";
 | 
				
			||||||
 | 
					      case "direct":
 | 
				
			||||||
 | 
					        return "Only Mentions";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      class={tootComposers.composer}
 | 
				
			||||||
 | 
					      style={containerStyle()}
 | 
				
			||||||
 | 
					      onClick={() => setTyping(true)}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <div class={tootComposers.replyInput}>
 | 
				
			||||||
 | 
					        <Avatar src={props.profile.inf?.avatar} />
 | 
				
			||||||
 | 
					        <textarea
 | 
				
			||||||
 | 
					          ref={inputRef!}
 | 
				
			||||||
 | 
					          placeholder={`Reply to ${props.replyToDisplayName}...`}
 | 
				
			||||||
 | 
					          style={{ width: "100%", border: "none" }}
 | 
				
			||||||
 | 
					        ></textarea>
 | 
				
			||||||
 | 
					        <IconButton>
 | 
				
			||||||
 | 
					          <Send />
 | 
				
			||||||
 | 
					        </IconButton>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        style={{
 | 
				
			||||||
 | 
					          display: "flex",
 | 
				
			||||||
 | 
					          "justify-content": "flex-end",
 | 
				
			||||||
 | 
					          "margin-top": "8px",
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <Button onClick={[setPermPicker, true]} id={buttonId}>
 | 
				
			||||||
 | 
					          {visibilityText()}
 | 
				
			||||||
 | 
					          <ArrowDropDown />
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <TootVisibilityPickerDialog
 | 
				
			||||||
 | 
					        open={permPicker()}
 | 
				
			||||||
 | 
					        onClose={() => setPermPicker(false)}
 | 
				
			||||||
 | 
					        visibility={visibility()}
 | 
				
			||||||
 | 
					        onVisibilityChange={setVisibility}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ReplyEditor;
 | 
				
			||||||
| 
						 | 
					@ -8,12 +8,16 @@ import {
 | 
				
			||||||
  type Component,
 | 
					  type Component,
 | 
				
			||||||
} from "solid-js";
 | 
					} from "solid-js";
 | 
				
			||||||
import Scaffold from "../material/Scaffold";
 | 
					import Scaffold from "../material/Scaffold";
 | 
				
			||||||
import { AppBar, Avatar, CircularProgress, IconButton, Toolbar } from "@suid/material";
 | 
					import {
 | 
				
			||||||
 | 
					  AppBar,
 | 
				
			||||||
 | 
					  CircularProgress,
 | 
				
			||||||
 | 
					  IconButton,
 | 
				
			||||||
 | 
					  Toolbar,
 | 
				
			||||||
 | 
					} from "@suid/material";
 | 
				
			||||||
import { Title } from "../material/typography";
 | 
					import { Title } from "../material/typography";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ArrowBack as BackIcon,
 | 
					  ArrowBack as BackIcon,
 | 
				
			||||||
  Close as CloseIcon,
 | 
					  Close as CloseIcon,
 | 
				
			||||||
  Send,
 | 
					 | 
				
			||||||
} from "@suid/icons-material";
 | 
					} from "@suid/icons-material";
 | 
				
			||||||
import { createUnauthorizedClient, useSessions } from "../masto/clients";
 | 
					import { createUnauthorizedClient, useSessions } from "../masto/clients";
 | 
				
			||||||
import { resolveCustomEmoji } from "../masto/toot";
 | 
					import { resolveCustomEmoji } from "../masto/toot";
 | 
				
			||||||
| 
						 | 
					@ -22,6 +26,8 @@ import type { mastodon } from "masto";
 | 
				
			||||||
import cards from "../material/cards.module.css";
 | 
					import cards from "../material/cards.module.css";
 | 
				
			||||||
import { css } from "solid-styled";
 | 
					import { css } from "solid-styled";
 | 
				
			||||||
import { vibrate } from "../platform/hardware";
 | 
					import { vibrate } from "../platform/hardware";
 | 
				
			||||||
 | 
					import { createTimeSource, TimeSourceProvider } from "../platform/timesrc";
 | 
				
			||||||
 | 
					import ReplyEditor from "./ReplyEditor";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let cachedEntry: [string, mastodon.v1.Status] | undefined;
 | 
					let cachedEntry: [string, mastodon.v1.Status] | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,11 +41,14 @@ function getCache(acct: string, id: string) {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TootBottomSheet: Component = (props) => {
 | 
					const TootBottomSheet: Component = (props) => {
 | 
				
			||||||
  const params = useParams<{ acct: string; id: string }>();
 | 
					  const params = useParams<{ acct: string; id: string }>();
 | 
				
			||||||
  const location = useLocation<{ tootBottomSheetPushedCount?: number }>();
 | 
					  const location = useLocation<{ tootBottomSheetPushedCount?: number }>();
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
  const allSession = useSessions();
 | 
					  const allSession = useSessions();
 | 
				
			||||||
 | 
					  const time = createTimeSource();
 | 
				
			||||||
  const acctText = () => decodeURIComponent(params.acct);
 | 
					  const acctText = () => decodeURIComponent(params.acct);
 | 
				
			||||||
  const session = () => {
 | 
					  const session = () => {
 | 
				
			||||||
    const [inputUsername, inputSite] = acctText().split("@", 2);
 | 
					    const [inputUsername, inputSite] = acctText().split("@", 2);
 | 
				
			||||||
| 
						 | 
					@ -164,12 +173,6 @@ const TootBottomSheet: Component = (props) => {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  css`
 | 
					  css`
 | 
				
			||||||
    .bottom-dock {
 | 
					 | 
				
			||||||
      position: sticky;
 | 
					 | 
				
			||||||
      bottom: 0;
 | 
					 | 
				
			||||||
      z-index: var(--tutu-zidx-nav);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .name :global(img) {
 | 
					    .name :global(img) {
 | 
				
			||||||
      max-height: 1em;
 | 
					      max-height: 1em;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -203,74 +206,69 @@ const TootBottomSheet: Component = (props) => {
 | 
				
			||||||
        </AppBar>
 | 
					        </AppBar>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <For each={ancestors()}>
 | 
					      <TimeSourceProvider value={time}>
 | 
				
			||||||
        {(item) => (
 | 
					        <For each={ancestors()}>
 | 
				
			||||||
          <RegularToot
 | 
					          {(item) => (
 | 
				
			||||||
            id={`toot-${item.id}`}
 | 
					            <RegularToot
 | 
				
			||||||
            class={cards.card}
 | 
					              id={`toot-${item.id}`}
 | 
				
			||||||
            status={item}
 | 
					              class={cards.card}
 | 
				
			||||||
            actionable={false}
 | 
					              status={item}
 | 
				
			||||||
            onClick={[switchContext, item]}
 | 
					              actionable={false}
 | 
				
			||||||
          ></RegularToot>
 | 
					              onClick={[switchContext, item]}
 | 
				
			||||||
        )}
 | 
					            ></RegularToot>
 | 
				
			||||||
      </For>
 | 
					          )}
 | 
				
			||||||
 | 
					        </For>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <article>
 | 
					        <article>
 | 
				
			||||||
        <Show when={toot()}>
 | 
					          <Show when={toot()}>
 | 
				
			||||||
          <RegularToot
 | 
					            <RegularToot
 | 
				
			||||||
            id={`toot-${toot()!.id}`}
 | 
					              id={`toot-${toot()!.id}`}
 | 
				
			||||||
            class={cards.card}
 | 
					              class={cards.card}
 | 
				
			||||||
            style={{
 | 
					              style={{
 | 
				
			||||||
              "scroll-margin-top": "calc(var(--scaffold-topbar-height) + 20px)",
 | 
					                "scroll-margin-top":
 | 
				
			||||||
            }}
 | 
					                  "calc(var(--scaffold-topbar-height) + 20px)",
 | 
				
			||||||
            status={toot()!}
 | 
					              }}
 | 
				
			||||||
            actionable={!!actSession()}
 | 
					              status={toot()!}
 | 
				
			||||||
            evaluated={true}
 | 
					              actionable={!!actSession()}
 | 
				
			||||||
            onBookmark={onBookmark}
 | 
					              evaluated={true}
 | 
				
			||||||
            onRetoot={onBoost}
 | 
					              onBookmark={onBookmark}
 | 
				
			||||||
            onFavourite={onFav}
 | 
					              onRetoot={onBoost}
 | 
				
			||||||
          ></RegularToot>
 | 
					              onFavourite={onFav}
 | 
				
			||||||
        </Show>
 | 
					            ></RegularToot>
 | 
				
			||||||
      </article>
 | 
					          </Show>
 | 
				
			||||||
 | 
					        </article>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <Show when={tootContext.loading}>
 | 
					 | 
				
			||||||
        <div
 | 
					 | 
				
			||||||
          style={{
 | 
					 | 
				
			||||||
            display: "flex",
 | 
					 | 
				
			||||||
            "justify-content": "center",
 | 
					 | 
				
			||||||
            "margin-block": "12px",
 | 
					 | 
				
			||||||
          }}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <CircularProgress style="width: 1.5em; height: 1.5em;" />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </Show>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <For each={descendants()}>
 | 
					 | 
				
			||||||
        {(item) => (
 | 
					 | 
				
			||||||
          <RegularToot
 | 
					 | 
				
			||||||
            id={`toot-${item.id}`}
 | 
					 | 
				
			||||||
            class={cards.card}
 | 
					 | 
				
			||||||
            status={item}
 | 
					 | 
				
			||||||
            actionable={false}
 | 
					 | 
				
			||||||
            onClick={[switchContext, item]}
 | 
					 | 
				
			||||||
          ></RegularToot>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </For>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div class="bottom-dock">
 | 
					 | 
				
			||||||
        <Show when={profile()}>
 | 
					        <Show when={profile()}>
 | 
				
			||||||
          <div style="display: flex; gap: 8px; background: var(--tutu-color-surface); padding: 8px 8px calc(var(--safe-area-inset-bottom, 0px) + 8px);">
 | 
					          <ReplyEditor
 | 
				
			||||||
            <Avatar src={profile()!.inf?.avatar} />
 | 
					            profile={profile()!}
 | 
				
			||||||
            <textarea
 | 
					            replyToDisplayName={toot()?.account?.displayName || ""}
 | 
				
			||||||
              placeholder={`Reply to ${toot()?.account?.displayName ?? ""}...`}
 | 
					          />
 | 
				
			||||||
              style={{ width: "100%", border: "none" }}
 | 
					        </Show>
 | 
				
			||||||
            ></textarea>
 | 
					
 | 
				
			||||||
            <IconButton>
 | 
					        <Show when={tootContext.loading}>
 | 
				
			||||||
              <Send />
 | 
					          <div
 | 
				
			||||||
            </IconButton>
 | 
					            style={{
 | 
				
			||||||
 | 
					              display: "flex",
 | 
				
			||||||
 | 
					              "justify-content": "center",
 | 
				
			||||||
 | 
					              "margin-block": "12px",
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <CircularProgress style="width: 1.5em; height: 1.5em;" />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </Show>
 | 
					        </Show>
 | 
				
			||||||
      </div>
 | 
					
 | 
				
			||||||
 | 
					        <For each={descendants()}>
 | 
				
			||||||
 | 
					          {(item) => (
 | 
				
			||||||
 | 
					            <RegularToot
 | 
				
			||||||
 | 
					              id={`toot-${item.id}`}
 | 
				
			||||||
 | 
					              class={cards.card}
 | 
				
			||||||
 | 
					              status={item}
 | 
				
			||||||
 | 
					              actionable={false}
 | 
				
			||||||
 | 
					              onClick={[switchContext, item]}
 | 
				
			||||||
 | 
					            ></RegularToot>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </For>
 | 
				
			||||||
 | 
					      </TimeSourceProvider>
 | 
				
			||||||
    </Scaffold>
 | 
					    </Scaffold>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/timelines/TootComposer.module.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/timelines/TootComposer.module.css
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.composer {
 | 
				
			||||||
 | 
					  composes: card from "../material/cards.module.css";
 | 
				
			||||||
 | 
					  --card-gut: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.replyInput {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: flex-start;
 | 
				
			||||||
 | 
					  gap: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue