Compare commits
No commits in common. "5d6eb7282a3d521e182dd39587af7a4e3a57b5e5" and "4c0925a6a12cdafafdcc4327d0da8d2e9df081a7" have entirely different histories.
5d6eb7282a
...
4c0925a6a1
15 changed files with 179 additions and 144 deletions
2
.env
2
.env
|
@ -2,4 +2,4 @@ DEV_SERVER_HTTPS_CERT_BASE=
|
||||||
DEV_SERVER_HTTPS_CERT_PASS=
|
DEV_SERVER_HTTPS_CERT_PASS=
|
||||||
DEV_LOCATOR_EDITOR=vscode
|
DEV_LOCATOR_EDITOR=vscode
|
||||||
VITE_DEVTOOLS_OVERLAY=true
|
VITE_DEVTOOLS_OVERLAY=true
|
||||||
VITE_PLATFORM_MASONRY_ALWAYS_COMPAT=
|
VITE_PLATFROM_MASONRY_ALWAYS_COMPAT=
|
|
@ -20,6 +20,17 @@ export function resolveCustomEmoji(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function appliedCustomEmoji(
|
||||||
|
target: { innerHTML: string },
|
||||||
|
content: string,
|
||||||
|
emojis?: mastodon.v1.CustomEmoji[],
|
||||||
|
) {
|
||||||
|
createRenderEffect(() => {
|
||||||
|
const result = emojis ? resolveCustomEmoji(content, emojis) : content;
|
||||||
|
target.innerHTML = result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function hasCustomEmoji(s: string) {
|
export function hasCustomEmoji(s: string) {
|
||||||
return CUSTOM_EMOJI_REGEX.test(s);
|
return CUSTOM_EMOJI_REGEX.test(s);
|
||||||
}
|
}
|
||||||
|
|
2
src/overrides.d.ts
vendored
2
src/overrides.d.ts
vendored
|
@ -14,7 +14,7 @@ interface ImportMetaEnv {
|
||||||
/**
|
/**
|
||||||
* Always use compatible version of Masonry.
|
* Always use compatible version of Masonry.
|
||||||
*/
|
*/
|
||||||
readonly VITE_PLATFORM_MASONRY_ALWAYS_COMPAT?: string
|
readonly VITE_PLATFROM_MASONRY_ALWAYS_COMPAT?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|
|
@ -46,49 +46,30 @@ function createCompatMasonry(
|
||||||
|
|
||||||
const size = createElementSize(element);
|
const size = createElementSize(element);
|
||||||
|
|
||||||
let layoutNotRequested = true;
|
const treeMutObx = new MutationObserver(() => {
|
||||||
|
layout.reloadItems?.();
|
||||||
|
});
|
||||||
|
|
||||||
const reflow = () => {
|
onCleanup(() => treeMutObx.disconnect());
|
||||||
layout.layout?.();
|
|
||||||
layoutNotRequested = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
createRenderEffect(() => {
|
createRenderEffect(() => {
|
||||||
const opts = options();
|
const opts = options();
|
||||||
layout.option?.(opts);
|
layout.option?.(opts);
|
||||||
});
|
});
|
||||||
|
|
||||||
const treeMutObx = new MutationObserver(() => {
|
|
||||||
layout.reloadItems?.();
|
|
||||||
if (layoutNotRequested) {
|
|
||||||
layoutNotRequested = false;
|
|
||||||
requestAnimationFrame(reflow);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onCleanup(() => treeMutObx.disconnect());
|
|
||||||
|
|
||||||
createRenderEffect(() => {
|
createRenderEffect(() => {
|
||||||
treeMutObx.observe(element, { childList: true });
|
treeMutObx.observe(element, { childList: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
createRenderEffect(() => {
|
createRenderEffect(() => {
|
||||||
const width = size.width; // only tracking width
|
const width = size.width; // only tracking width
|
||||||
if (layoutNotRequested) {
|
layout.layout?.();
|
||||||
layoutNotRequested = false;
|
|
||||||
requestAnimationFrame(reflow);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (import.meta.hot) {
|
if (import.meta.hot) {
|
||||||
const onHotReloadUpdate = () => {
|
import.meta.hot.on("vite:afterUpdate", () => {
|
||||||
if (layoutNotRequested) {
|
layout.layout?.();
|
||||||
layoutNotRequested = false;
|
});
|
||||||
requestAnimationFrame(reflow);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
import.meta.hot.on("vite:afterUpdate", onHotReloadUpdate);
|
|
||||||
import.meta.hot.off("vite:afterUpdate", onHotReloadUpdate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,11 +80,11 @@ const supportsCSSMasonryLayout = /* @__PURE__ */ CSS.supports(
|
||||||
|
|
||||||
console.debug("supports css masonry layout", supportsCSSMasonryLayout);
|
console.debug("supports css masonry layout", supportsCSSMasonryLayout);
|
||||||
|
|
||||||
const useNativeImpl = import.meta.env.VITE_PLATFORM_MASONRY_ALWAYS_COMPAT
|
const useNativeImpl = import.meta.env.VITE_PLATFROM_MASONRY_ALWAYS_COMPAT
|
||||||
? false
|
? false
|
||||||
: supportsCSSMasonryLayout;
|
: supportsCSSMasonryLayout;
|
||||||
|
|
||||||
if (import.meta.env.VITE_PLATFORM_MASONRY_ALWAYS_COMPAT) {
|
if (import.meta.env.VITE_PLATFROM_MASONRY_ALWAYS_COMPAT) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Masonry is in compat mode because VITE_PLATFORM_MASONRY_ALWAYS_COMPAT is enabled",
|
"Masonry is in compat mode because VITE_PLATFORM_MASONRY_ALWAYS_COMPAT is enabled",
|
||||||
);
|
);
|
||||||
|
|
|
@ -171,6 +171,10 @@ const Profile: Component = () => {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const useSessionDisplayName = (e: HTMLElement) => {
|
||||||
|
createRenderEffect(() => (e.innerHTML = sessionDisplayName()));
|
||||||
|
};
|
||||||
|
|
||||||
const toggleSubscribeHome = async (event: Event) => {
|
const toggleSubscribeHome = async (event: Event) => {
|
||||||
const client = session().client;
|
const client = session().client;
|
||||||
if (!session().account) return;
|
if (!session().account) return;
|
||||||
|
@ -214,7 +218,9 @@ const Profile: Component = () => {
|
||||||
style={{
|
style={{
|
||||||
visibility: scrolledPastBanner() ? undefined : "hidden",
|
visibility: scrolledPastBanner() ? undefined : "hidden",
|
||||||
}}
|
}}
|
||||||
innerHTML={displayName()}
|
ref={(e: HTMLElement) =>
|
||||||
|
createRenderEffect(() => (e.innerHTML = displayName()))
|
||||||
|
}
|
||||||
></Title>
|
></Title>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -247,7 +253,7 @@ const Profile: Component = () => {
|
||||||
<Avatar src={session().account?.inf?.avatar} />
|
<Avatar src={session().account?.inf?.avatar} />
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText secondary={"Default account"}>
|
<ListItemText secondary={"Default account"}>
|
||||||
<span innerHTML={sessionDisplayName()}></span>
|
<span ref={useSessionDisplayName}></span>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
{/* <ArrowRight /> // for future */}
|
{/* <ArrowRight /> // for future */}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -378,7 +384,7 @@ const Profile: Component = () => {
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span innerHTML={sessionDisplayName()}></span>
|
<span ref={useSessionDisplayName}></span>
|
||||||
<span>'s Home</span>
|
<span>'s Home</span>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
|
|
||||||
|
@ -413,7 +419,9 @@ const Profile: Component = () => {
|
||||||
</Show>
|
</Show>
|
||||||
<Body2
|
<Body2
|
||||||
component="span"
|
component="span"
|
||||||
innerHTML={displayName()}
|
ref={(e: HTMLElement) =>
|
||||||
|
createRenderEffect(() => (e.innerHTML = displayName()))
|
||||||
|
}
|
||||||
aria-label="Display name"
|
aria-label="Display name"
|
||||||
></Body2>
|
></Body2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -456,7 +464,9 @@ const Profile: Component = () => {
|
||||||
<section
|
<section
|
||||||
class="description"
|
class="description"
|
||||||
aria-label={`${profile()?.displayName || "the user"}'s description`}
|
aria-label={`${profile()?.displayName || "the user"}'s description`}
|
||||||
innerHTML={description() || ""}
|
ref={(e) =>
|
||||||
|
createRenderEffect(() => (e.innerHTML = description() || ""))
|
||||||
|
}
|
||||||
></section>
|
></section>
|
||||||
|
|
||||||
<table
|
<table
|
||||||
|
@ -474,7 +484,11 @@ const Profile: Component = () => {
|
||||||
<Verified />
|
<Verified />
|
||||||
</Show>
|
</Show>
|
||||||
</td>
|
</td>
|
||||||
<td innerHTML={item.value}></td>
|
<td
|
||||||
|
ref={(e) => {
|
||||||
|
createRenderEffect(() => (e.innerHTML = item.value));
|
||||||
|
}}
|
||||||
|
></td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { For, onMount, type Component, type JSX } from "solid-js";
|
import {
|
||||||
|
For,
|
||||||
|
onMount,
|
||||||
|
type Component,
|
||||||
|
type JSX,
|
||||||
|
} from "solid-js";
|
||||||
import Scaffold from "~material/Scaffold";
|
import Scaffold from "~material/Scaffold";
|
||||||
import {
|
import {
|
||||||
AppBar,
|
AppBar,
|
||||||
|
@ -14,7 +19,6 @@ import { Close as CloseIcon } from "@suid/icons-material";
|
||||||
import iso639_1 from "iso-639-1";
|
import iso639_1 from "iso-639-1";
|
||||||
import { createTranslator } from "~platform/i18n";
|
import { createTranslator } from "~platform/i18n";
|
||||||
import { Title } from "~material/typography";
|
import { Title } from "~material/typography";
|
||||||
import "./TootLangPicker.css";
|
|
||||||
|
|
||||||
type ChooseTootLangProps = {
|
type ChooseTootLangProps = {
|
||||||
code: string;
|
code: string;
|
||||||
|
@ -54,7 +58,6 @@ const ChooseTootLang: Component<ChooseTootLangProps> = (props) => {
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
}
|
}
|
||||||
class="TootLangPicker"
|
|
||||||
>
|
>
|
||||||
<List
|
<List
|
||||||
ref={listRef!}
|
ref={listRef!}
|
|
@ -10,19 +10,23 @@ import {
|
||||||
createContext,
|
createContext,
|
||||||
useContext,
|
useContext,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
|
import tootStyle from "./toot.module.css";
|
||||||
|
import { formatRelative } from "date-fns";
|
||||||
|
import Img from "~material/Img.js";
|
||||||
import { Body2 } from "~material/typography.js";
|
import { Body2 } from "~material/typography.js";
|
||||||
|
import { SmartToySharp, Lock } from "@suid/icons-material";
|
||||||
import { useTimeSource } from "~platform/timesrc.js";
|
import { useTimeSource } from "~platform/timesrc.js";
|
||||||
import { resolveCustomEmoji } from "../masto/toot.js";
|
import { resolveCustomEmoji } from "../masto/toot.js";
|
||||||
import { Divider } from "@suid/material";
|
import { Divider } from "@suid/material";
|
||||||
import cardStyle from "~material/cards.module.css";
|
import cardStyle from "~material/cards.module.css";
|
||||||
import MediaAttachmentGrid from "./toots/MediaAttachmentGrid.jsx";
|
import MediaAttachmentGrid from "./toots/MediaAttachmentGrid.jsx";
|
||||||
|
import { useDateFnLocale } from "~platform/i18n";
|
||||||
import { makeAcctText, useDefaultSession } from "../masto/clients";
|
import { makeAcctText, useDefaultSession } from "../masto/clients";
|
||||||
import TootContent from "./toots/TootContent";
|
import TootContent from "./toots/TootContent";
|
||||||
import BoostIcon from "./toots/BoostIcon";
|
import BoostIcon from "./toots/BoostIcon";
|
||||||
import PreviewCard from "./toots/PreviewCard";
|
import PreviewCard from "./toots/PreviewCard";
|
||||||
import TootPoll from "./toots/TootPoll";
|
import TootPoll from "./toots/TootPoll";
|
||||||
import TootActionGroup from "./toots/TootActionGroup.js";
|
import TootActionGroup from "./toots/TootActionGroup.js";
|
||||||
import TootAuthorGroup from "./toots/TootAuthorGroup.js";
|
|
||||||
import "./RegularToot.css";
|
import "./RegularToot.css";
|
||||||
|
|
||||||
export type TootEnv = {
|
export type TootEnv = {
|
||||||
|
@ -62,15 +66,63 @@ type RegularTootProps = {
|
||||||
|
|
||||||
export function findRootToot(element: HTMLElement) {
|
export function findRootToot(element: HTMLElement) {
|
||||||
let current: HTMLElement | null = element;
|
let current: HTMLElement | null = element;
|
||||||
while (current && !current.classList.contains("RegularToot")) {
|
while (current && !current.classList.contains(tootStyle.toot)) {
|
||||||
current = current.parentElement;
|
current = current.parentElement;
|
||||||
}
|
}
|
||||||
if (!current) {
|
if (!current) {
|
||||||
throw Error(`the element must be placed under a element with .RegularToot`);
|
throw Error(
|
||||||
|
`the element must be placed under a element with ${tootStyle.toot}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TootAuthorGroup(
|
||||||
|
props: {
|
||||||
|
status: mastodon.v1.Status;
|
||||||
|
now: Date;
|
||||||
|
} & JSX.HTMLElementTags["div"],
|
||||||
|
) {
|
||||||
|
const [managed, rest] = splitProps(props, ["status", "now"]);
|
||||||
|
const toot = () => managed.status;
|
||||||
|
const dateFnLocale = useDateFnLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={tootStyle.tootAuthorGrp} {...rest}>
|
||||||
|
<Img src={toot().account.avatar} class={tootStyle.tootAvatar} />
|
||||||
|
<div class={tootStyle.tootAuthorNameGrp}>
|
||||||
|
<div class={tootStyle.tootAuthorNamePrimary}>
|
||||||
|
<Show when={toot().account.bot}>
|
||||||
|
<SmartToySharp class="acct-mark" aria-label="Bot" />
|
||||||
|
</Show>
|
||||||
|
<Show when={toot().account.locked}>
|
||||||
|
<Lock class="acct-mark" aria-label="Locked" />
|
||||||
|
</Show>
|
||||||
|
<Body2
|
||||||
|
component="span"
|
||||||
|
ref={(e: { innerHTML: string }) => {
|
||||||
|
createRenderEffect(() => {
|
||||||
|
e.innerHTML = resolveCustomEmoji(
|
||||||
|
toot().account.displayName,
|
||||||
|
toot().account.emojis,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<time datetime={toot().createdAt}>
|
||||||
|
{formatRelative(toot().createdAt, managed.now, {
|
||||||
|
locale: dateFnLocale(),
|
||||||
|
})}
|
||||||
|
</time>
|
||||||
|
<span>
|
||||||
|
@{toot().account.username}@{new URL(toot().account.url).hostname}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* find bottom-to-top the element with `data-action`.
|
* find bottom-to-top the element with `data-action`.
|
||||||
*/
|
*/
|
||||||
|
@ -143,8 +195,8 @@ const RegularToot: Component<RegularTootProps> = (oprops) => {
|
||||||
<>
|
<>
|
||||||
<article
|
<article
|
||||||
classList={{
|
classList={{
|
||||||
RegularToot: true,
|
"RegularToot": true,
|
||||||
expanded: props.evaluated,
|
"expanded": props.evaluated,
|
||||||
"thread-top": props.thread === "top",
|
"thread-top": props.thread === "top",
|
||||||
"thread-mid": props.thread === "middle",
|
"thread-mid": props.thread === "middle",
|
||||||
"thread-btm": props.thread === "bottom",
|
"thread-btm": props.thread === "bottom",
|
||||||
|
@ -158,10 +210,14 @@ const RegularToot: Component<RegularTootProps> = (oprops) => {
|
||||||
<div class="retoot-grp">
|
<div class="retoot-grp">
|
||||||
<BoostIcon />
|
<BoostIcon />
|
||||||
<Body2
|
<Body2
|
||||||
innerHTML={resolveCustomEmoji(
|
ref={(e: { innerHTML: string }) => {
|
||||||
status().account.displayName,
|
createRenderEffect(() => {
|
||||||
toot().emojis,
|
e.innerHTML = resolveCustomEmoji(
|
||||||
)}
|
status().account.displayName,
|
||||||
|
toot().emojis,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}}
|
||||||
></Body2>
|
></Body2>
|
||||||
<span>boosts</span>
|
<span>boosts</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -261,7 +261,13 @@ const TootBottomSheet: Component = (props) => {
|
||||||
>
|
>
|
||||||
<BackButton color="inherit" />
|
<BackButton color="inherit" />
|
||||||
<Title component="div" class="name" use:solid-styled>
|
<Title component="div" class="name" use:solid-styled>
|
||||||
<span innerHTML={tootDisplayName() ?? "Someone"}></span>
|
<span
|
||||||
|
ref={(e: HTMLElement) =>
|
||||||
|
createRenderEffect(
|
||||||
|
() => (e.innerHTML = tootDisplayName() ?? "Someone"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></span>
|
||||||
<span>'s toot</span>
|
<span>'s toot</span>
|
||||||
</Title>
|
</Title>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
|
@ -44,7 +44,7 @@ import "./TootComposer.css";
|
||||||
import BottomSheet from "~material/BottomSheet";
|
import BottomSheet from "~material/BottomSheet";
|
||||||
import { useAppLocale } from "~platform/i18n";
|
import { useAppLocale } from "~platform/i18n";
|
||||||
import iso639_1 from "iso-639-1";
|
import iso639_1 from "iso-639-1";
|
||||||
import ChooseTootLang from "./TootLangPicker";
|
import ChooseTootLang from "./ChooseTootLang";
|
||||||
import type { mastodon } from "masto";
|
import type { mastodon } from "masto";
|
||||||
import cardStyles from "~material/cards.module.css";
|
import cardStyles from "~material/cards.module.css";
|
||||||
import Menu, { createManagedMenuState } from "~material/Menu";
|
import Menu, { createManagedMenuState } from "~material/Menu";
|
||||||
|
@ -349,10 +349,15 @@ const TootComposer: Component<{
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText secondary={"Default account"}>
|
<ListItemText secondary={"Default account"}>
|
||||||
<span
|
<span
|
||||||
innerHTML={resolveCustomEmoji(
|
ref={(e) => {
|
||||||
session()?.account.inf?.displayName || "",
|
createRenderEffect(() => {
|
||||||
session()?.account.inf?.emojis ?? [],
|
const inf = session()?.account.inf;
|
||||||
)}
|
return (e.innerHTML = resolveCustomEmoji(
|
||||||
|
inf?.displayName || "",
|
||||||
|
inf?.emojis ?? [],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}}
|
||||||
></span>
|
></span>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
.TootLangPicker {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
.TootAuthorGroup {
|
.tootAuthorGrp {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TootAuthorGroup > .name-grp {
|
.tootAuthorNameGrp {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
color: var(--tutu-color-secondary-text-on-surface);
|
color: var(--tutu-color-secondary-text-on-surface);
|
||||||
|
@ -30,10 +30,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TootAuthorGroup > .name-grp > .name-primary {
|
.tootAuthorNamePrimary {
|
||||||
color: var(--tutu-color-on-surface);
|
color: var(--tutu-color-on-surface);
|
||||||
|
|
||||||
> .acct-mark {
|
> :global(.acct-mark) {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
color: var(--tutu-color-secondary-text-on-surface);
|
color: var(--tutu-color-secondary-text-on-surface);
|
||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.TootAuthorGroup > .avatar {
|
.tootAvatar {
|
||||||
width: calc(var(--toot-avatar-size, 40px) - 1px);
|
width: calc(var(--toot-avatar-size, 40px) - 1px);
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
@ -49,4 +49,4 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--tutu-color-surface);
|
border: 1px solid var(--tutu-color-surface);
|
||||||
background-color: var(--tutu-color-surface-d);
|
background-color: var(--tutu-color-surface-d);
|
||||||
}
|
}
|
|
@ -1,53 +0,0 @@
|
||||||
import { SmartToySharp, Lock } from "@suid/icons-material";
|
|
||||||
import { formatRelative } from "date-fns";
|
|
||||||
import type { mastodon } from "masto";
|
|
||||||
import { createRenderEffect, Show, splitProps, type JSX } from "solid-js";
|
|
||||||
import Img from "~material/Img";
|
|
||||||
import { Body2 } from "~material/typography";
|
|
||||||
import { useAppLocale } from "~platform/i18n";
|
|
||||||
import { resolveCustomEmoji } from "../../masto/toot";
|
|
||||||
import "./TootAuthorGroup.css";
|
|
||||||
|
|
||||||
function TootAuthorGroup(
|
|
||||||
props: {
|
|
||||||
status: mastodon.v1.Status;
|
|
||||||
now: Date;
|
|
||||||
} & JSX.HTMLElementTags["div"],
|
|
||||||
) {
|
|
||||||
const [managed, rest] = splitProps(props, ["status", "now"]);
|
|
||||||
const toot = () => managed.status;
|
|
||||||
const { dateFn: dateFnLocale } = useAppLocale();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="TootAuthorGroup" {...rest}>
|
|
||||||
<Img src={toot().account.avatar} class="avatar" />
|
|
||||||
<div class={"name-grp"}>
|
|
||||||
<div class="name-primary">
|
|
||||||
<Show when={toot().account.bot}>
|
|
||||||
<SmartToySharp class="acct-mark" aria-label="Bot" />
|
|
||||||
</Show>
|
|
||||||
<Show when={toot().account.locked}>
|
|
||||||
<Lock class="acct-mark" aria-label="Locked" />
|
|
||||||
</Show>
|
|
||||||
<Body2
|
|
||||||
component="span"
|
|
||||||
innerHTML={resolveCustomEmoji(
|
|
||||||
toot().account.displayName,
|
|
||||||
toot().account.emojis,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<time datetime={toot().createdAt}>
|
|
||||||
{formatRelative(toot().createdAt, managed.now, {
|
|
||||||
locale: dateFnLocale(),
|
|
||||||
})}
|
|
||||||
</time>
|
|
||||||
<span>
|
|
||||||
@{toot().account.username}@{new URL(toot().account.url).hostname}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TootAuthorGroup;
|
|
|
@ -81,13 +81,15 @@ const TootContent: Component<TootContentProps> = (oprops) => {
|
||||||
<Show when={props.sensitive}>
|
<Show when={props.sensitive}>
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
innerHTML={
|
ref={(ref) => {
|
||||||
props.spoilerText
|
createRenderEffect(() => {
|
||||||
? props.emojis
|
ref.innerHTML = props.spoilerText
|
||||||
? resolveCustomEmoji(props.spoilerText, props.emojis)
|
? props.emojis
|
||||||
: props.spoilerText
|
? resolveCustomEmoji(props.spoilerText, props.emojis)
|
||||||
: ""
|
: props.spoilerText
|
||||||
}
|
: "";
|
||||||
|
});
|
||||||
|
}}
|
||||||
></span>
|
></span>
|
||||||
<Button onClick={props.onToggleReveal}>{t("cw")}</Button>
|
<Button onClick={props.onToggleReveal}>{t("cw")}</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,12 +97,14 @@ const TootContent: Component<TootContentProps> = (oprops) => {
|
||||||
<Show when={shouldRevealContent()}>
|
<Show when={shouldRevealContent()}>
|
||||||
<div
|
<div
|
||||||
class="content"
|
class="content"
|
||||||
innerHTML={
|
ref={(ref) =>
|
||||||
props.source
|
createRenderEffect(() => {
|
||||||
? props.emojis
|
ref.innerHTML = props.source
|
||||||
? resolveCustomEmoji(props.source, props.emojis)
|
? props.emojis
|
||||||
: props.source
|
? resolveCustomEmoji(props.source, props.emojis)
|
||||||
: ""
|
: props.source
|
||||||
|
: "";
|
||||||
|
})
|
||||||
}
|
}
|
||||||
></div>
|
></div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -20,7 +20,10 @@ import {
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Radio,
|
Radio,
|
||||||
} from "@suid/material";
|
} from "@suid/material";
|
||||||
import { formatDistance, isBefore } from "date-fns";
|
import {
|
||||||
|
formatDistance,
|
||||||
|
isBefore,
|
||||||
|
} from "date-fns";
|
||||||
import { useTimeSource } from "~platform/timesrc";
|
import { useTimeSource } from "~platform/timesrc";
|
||||||
import { useDateFnLocale } from "~platform/i18n";
|
import { useDateFnLocale } from "~platform/i18n";
|
||||||
import TootPollDialog from "./TootPollDialog";
|
import TootPollDialog from "./TootPollDialog";
|
||||||
|
@ -28,13 +31,13 @@ import { ANIM_CURVE_STD } from "~material/theme";
|
||||||
import { useTootEnv } from "../RegularToot";
|
import { useTootEnv } from "../RegularToot";
|
||||||
|
|
||||||
type TootPollProps = {
|
type TootPollProps = {
|
||||||
value: mastodon.v1.Poll;
|
value: mastodon.v1.Poll
|
||||||
status: mastodon.v1.Status;
|
status: mastodon.v1.Status
|
||||||
};
|
};
|
||||||
|
|
||||||
const TootPoll: Component<TootPollProps> = (props) => {
|
const TootPoll: Component<TootPollProps> = (props) => {
|
||||||
let list: HTMLUListElement;
|
let list: HTMLUListElement;
|
||||||
const { vote } = useTootEnv();
|
const {vote}= useTootEnv()
|
||||||
|
|
||||||
const now = useTimeSource();
|
const now = useTimeSource();
|
||||||
const dateFnLocale = useDateFnLocale();
|
const dateFnLocale = useDateFnLocale();
|
||||||
|
@ -43,7 +46,7 @@ const TootPoll: Component<TootPollProps> = (props) => {
|
||||||
|
|
||||||
const [initialVote, setInitialVote] = createSignal(0);
|
const [initialVote, setInitialVote] = createSignal(0);
|
||||||
|
|
||||||
const poll = () => props.value;
|
const poll = () => props.value
|
||||||
|
|
||||||
const isShowResult = () => {
|
const isShowResult = () => {
|
||||||
const n = mustShowResult();
|
const n = mustShowResult();
|
||||||
|
@ -115,10 +118,14 @@ const TootPoll: Component<TootPollProps> = (props) => {
|
||||||
>
|
>
|
||||||
<ListItemText>
|
<ListItemText>
|
||||||
<span
|
<span
|
||||||
innerHTML={resolveCustomEmoji(
|
ref={(e) =>
|
||||||
option().title,
|
createRenderEffect(() => {
|
||||||
option().emojis,
|
e.innerHTML = resolveCustomEmoji(
|
||||||
)}
|
option().title,
|
||||||
|
option().emojis,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
></span>
|
></span>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
|
|
||||||
|
|
|
@ -96,10 +96,15 @@ const TootPollDialog: Component<TootPollDialogPoll> = (props) => {
|
||||||
>
|
>
|
||||||
<ListItemText>
|
<ListItemText>
|
||||||
<span
|
<span
|
||||||
innerHTML={resolveCustomEmoji(
|
ref={(e) =>
|
||||||
option().title,
|
createRenderEffect(
|
||||||
option().emojis,
|
() =>
|
||||||
)}
|
(e.innerHTML = resolveCustomEmoji(
|
||||||
|
option().title,
|
||||||
|
option().emojis,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
></span>
|
></span>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue