Compare commits

..

No commits in common. "5d6eb7282a3d521e182dd39587af7a4e3a57b5e5" and "4c0925a6a12cdafafdcc4327d0da8d2e9df081a7" have entirely different histories.

15 changed files with 179 additions and 144 deletions

2
.env
View file

@ -2,4 +2,4 @@ DEV_SERVER_HTTPS_CERT_BASE=
DEV_SERVER_HTTPS_CERT_PASS=
DEV_LOCATOR_EDITOR=vscode
VITE_DEVTOOLS_OVERLAY=true
VITE_PLATFORM_MASONRY_ALWAYS_COMPAT=
VITE_PLATFROM_MASONRY_ALWAYS_COMPAT=

View file

@ -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) {
return CUSTOM_EMOJI_REGEX.test(s);
}

2
src/overrides.d.ts vendored
View file

@ -14,7 +14,7 @@ interface ImportMetaEnv {
/**
* Always use compatible version of Masonry.
*/
readonly VITE_PLATFORM_MASONRY_ALWAYS_COMPAT?: string
readonly VITE_PLATFROM_MASONRY_ALWAYS_COMPAT?: string
}
interface ImportMeta {

View file

@ -46,49 +46,30 @@ function createCompatMasonry(
const size = createElementSize(element);
let layoutNotRequested = true;
const treeMutObx = new MutationObserver(() => {
layout.reloadItems?.();
});
const reflow = () => {
layout.layout?.();
layoutNotRequested = true;
};
onCleanup(() => treeMutObx.disconnect());
createRenderEffect(() => {
const opts = options();
layout.option?.(opts);
});
const treeMutObx = new MutationObserver(() => {
layout.reloadItems?.();
if (layoutNotRequested) {
layoutNotRequested = false;
requestAnimationFrame(reflow);
}
});
onCleanup(() => treeMutObx.disconnect());
createRenderEffect(() => {
treeMutObx.observe(element, { childList: true });
});
createRenderEffect(() => {
const width = size.width; // only tracking width
if (layoutNotRequested) {
layoutNotRequested = false;
requestAnimationFrame(reflow);
}
layout.layout?.();
});
if (import.meta.hot) {
const onHotReloadUpdate = () => {
if (layoutNotRequested) {
layoutNotRequested = false;
requestAnimationFrame(reflow);
}
};
import.meta.hot.on("vite:afterUpdate", onHotReloadUpdate);
import.meta.hot.off("vite:afterUpdate", onHotReloadUpdate);
import.meta.hot.on("vite:afterUpdate", () => {
layout.layout?.();
});
}
}
@ -99,11 +80,11 @@ const supportsCSSMasonryLayout = /* @__PURE__ */ CSS.supports(
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
: supportsCSSMasonryLayout;
if (import.meta.env.VITE_PLATFORM_MASONRY_ALWAYS_COMPAT) {
if (import.meta.env.VITE_PLATFROM_MASONRY_ALWAYS_COMPAT) {
console.warn(
"Masonry is in compat mode because VITE_PLATFORM_MASONRY_ALWAYS_COMPAT is enabled",
);

View file

@ -171,6 +171,10 @@ const Profile: Component = () => {
),
);
const useSessionDisplayName = (e: HTMLElement) => {
createRenderEffect(() => (e.innerHTML = sessionDisplayName()));
};
const toggleSubscribeHome = async (event: Event) => {
const client = session().client;
if (!session().account) return;
@ -214,7 +218,9 @@ const Profile: Component = () => {
style={{
visibility: scrolledPastBanner() ? undefined : "hidden",
}}
innerHTML={displayName()}
ref={(e: HTMLElement) =>
createRenderEffect(() => (e.innerHTML = displayName()))
}
></Title>
<IconButton
@ -247,7 +253,7 @@ const Profile: Component = () => {
<Avatar src={session().account?.inf?.avatar} />
</ListItemAvatar>
<ListItemText secondary={"Default account"}>
<span innerHTML={sessionDisplayName()}></span>
<span ref={useSessionDisplayName}></span>
</ListItemText>
{/* <ArrowRight /> // for future */}
</MenuItem>
@ -378,7 +384,7 @@ const Profile: Component = () => {
: undefined
}
>
<span innerHTML={sessionDisplayName()}></span>
<span ref={useSessionDisplayName}></span>
<span>'s Home</span>
</ListItemText>
@ -413,7 +419,9 @@ const Profile: Component = () => {
</Show>
<Body2
component="span"
innerHTML={displayName()}
ref={(e: HTMLElement) =>
createRenderEffect(() => (e.innerHTML = displayName()))
}
aria-label="Display name"
></Body2>
</div>
@ -456,7 +464,9 @@ const Profile: Component = () => {
<section
class="description"
aria-label={`${profile()?.displayName || "the user"}'s description`}
innerHTML={description() || ""}
ref={(e) =>
createRenderEffect(() => (e.innerHTML = description() || ""))
}
></section>
<table
@ -474,7 +484,11 @@ const Profile: Component = () => {
<Verified />
</Show>
</td>
<td innerHTML={item.value}></td>
<td
ref={(e) => {
createRenderEffect(() => (e.innerHTML = item.value));
}}
></td>
</tr>
);
}}

View file

@ -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 {
AppBar,
@ -14,7 +19,6 @@ import { Close as CloseIcon } from "@suid/icons-material";
import iso639_1 from "iso-639-1";
import { createTranslator } from "~platform/i18n";
import { Title } from "~material/typography";
import "./TootLangPicker.css";
type ChooseTootLangProps = {
code: string;
@ -54,7 +58,6 @@ const ChooseTootLang: Component<ChooseTootLangProps> = (props) => {
</Toolbar>
</AppBar>
}
class="TootLangPicker"
>
<List
ref={listRef!}

View file

@ -10,19 +10,23 @@ import {
createContext,
useContext,
} 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 { SmartToySharp, Lock } from "@suid/icons-material";
import { useTimeSource } from "~platform/timesrc.js";
import { resolveCustomEmoji } from "../masto/toot.js";
import { Divider } from "@suid/material";
import cardStyle from "~material/cards.module.css";
import MediaAttachmentGrid from "./toots/MediaAttachmentGrid.jsx";
import { useDateFnLocale } from "~platform/i18n";
import { makeAcctText, useDefaultSession } from "../masto/clients";
import TootContent from "./toots/TootContent";
import BoostIcon from "./toots/BoostIcon";
import PreviewCard from "./toots/PreviewCard";
import TootPoll from "./toots/TootPoll";
import TootActionGroup from "./toots/TootActionGroup.js";
import TootAuthorGroup from "./toots/TootAuthorGroup.js";
import "./RegularToot.css";
export type TootEnv = {
@ -62,15 +66,63 @@ type RegularTootProps = {
export function findRootToot(element: HTMLElement) {
let current: HTMLElement | null = element;
while (current && !current.classList.contains("RegularToot")) {
while (current && !current.classList.contains(tootStyle.toot)) {
current = current.parentElement;
}
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;
}
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`.
*/
@ -143,8 +195,8 @@ const RegularToot: Component<RegularTootProps> = (oprops) => {
<>
<article
classList={{
RegularToot: true,
expanded: props.evaluated,
"RegularToot": true,
"expanded": props.evaluated,
"thread-top": props.thread === "top",
"thread-mid": props.thread === "middle",
"thread-btm": props.thread === "bottom",
@ -158,10 +210,14 @@ const RegularToot: Component<RegularTootProps> = (oprops) => {
<div class="retoot-grp">
<BoostIcon />
<Body2
innerHTML={resolveCustomEmoji(
status().account.displayName,
toot().emojis,
)}
ref={(e: { innerHTML: string }) => {
createRenderEffect(() => {
e.innerHTML = resolveCustomEmoji(
status().account.displayName,
toot().emojis,
);
});
}}
></Body2>
<span>boosts</span>
</div>

View file

@ -261,7 +261,13 @@ const TootBottomSheet: Component = (props) => {
>
<BackButton color="inherit" />
<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>
</Title>
</Toolbar>

View file

@ -44,7 +44,7 @@ import "./TootComposer.css";
import BottomSheet from "~material/BottomSheet";
import { useAppLocale } from "~platform/i18n";
import iso639_1 from "iso-639-1";
import ChooseTootLang from "./TootLangPicker";
import ChooseTootLang from "./ChooseTootLang";
import type { mastodon } from "masto";
import cardStyles from "~material/cards.module.css";
import Menu, { createManagedMenuState } from "~material/Menu";
@ -349,10 +349,15 @@ const TootComposer: Component<{
</ListItemAvatar>
<ListItemText secondary={"Default account"}>
<span
innerHTML={resolveCustomEmoji(
session()?.account.inf?.displayName || "",
session()?.account.inf?.emojis ?? [],
)}
ref={(e) => {
createRenderEffect(() => {
const inf = session()?.account.inf;
return (e.innerHTML = resolveCustomEmoji(
inf?.displayName || "",
inf?.emojis ?? [],
));
});
}}
></span>
</ListItemText>
</MenuItem>

View file

@ -1,4 +0,0 @@
.TootLangPicker {
overflow: auto;
}

View file

@ -1,4 +1,4 @@
.TootAuthorGroup {
.tootAuthorGrp {
display: flex;
align-items: flex-start;
gap: 8px;
@ -10,7 +10,7 @@
}
}
.TootAuthorGroup > .name-grp {
.tootAuthorNameGrp {
display: grid;
grid-template-columns: 1fr auto;
color: var(--tutu-color-secondary-text-on-surface);
@ -30,10 +30,10 @@
}
}
.TootAuthorGroup > .name-grp > .name-primary {
.tootAuthorNamePrimary {
color: var(--tutu-color-on-surface);
> .acct-mark {
> :global(.acct-mark) {
font-size: 1.2em;
color: var(--tutu-color-secondary-text-on-surface);
vertical-align: sub;
@ -41,7 +41,7 @@
}
}
.TootAuthorGroup > .avatar {
.tootAvatar {
width: calc(var(--toot-avatar-size, 40px) - 1px);
aspect-ratio: 1/1;
object-fit: contain;
@ -49,4 +49,4 @@
overflow: hidden;
border: 1px solid var(--tutu-color-surface);
background-color: var(--tutu-color-surface-d);
}
}

View file

@ -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;

View file

@ -81,13 +81,15 @@ const TootContent: Component<TootContentProps> = (oprops) => {
<Show when={props.sensitive}>
<div>
<span
innerHTML={
props.spoilerText
? props.emojis
? resolveCustomEmoji(props.spoilerText, props.emojis)
: props.spoilerText
: ""
}
ref={(ref) => {
createRenderEffect(() => {
ref.innerHTML = props.spoilerText
? props.emojis
? resolveCustomEmoji(props.spoilerText, props.emojis)
: props.spoilerText
: "";
});
}}
></span>
<Button onClick={props.onToggleReveal}>{t("cw")}</Button>
</div>
@ -95,12 +97,14 @@ const TootContent: Component<TootContentProps> = (oprops) => {
<Show when={shouldRevealContent()}>
<div
class="content"
innerHTML={
props.source
? props.emojis
? resolveCustomEmoji(props.source, props.emojis)
: props.source
: ""
ref={(ref) =>
createRenderEffect(() => {
ref.innerHTML = props.source
? props.emojis
? resolveCustomEmoji(props.source, props.emojis)
: props.source
: "";
})
}
></div>
</Show>

View file

@ -20,7 +20,10 @@ import {
ListItemText,
Radio,
} from "@suid/material";
import { formatDistance, isBefore } from "date-fns";
import {
formatDistance,
isBefore,
} from "date-fns";
import { useTimeSource } from "~platform/timesrc";
import { useDateFnLocale } from "~platform/i18n";
import TootPollDialog from "./TootPollDialog";
@ -28,13 +31,13 @@ import { ANIM_CURVE_STD } from "~material/theme";
import { useTootEnv } from "../RegularToot";
type TootPollProps = {
value: mastodon.v1.Poll;
status: mastodon.v1.Status;
value: mastodon.v1.Poll
status: mastodon.v1.Status
};
const TootPoll: Component<TootPollProps> = (props) => {
let list: HTMLUListElement;
const { vote } = useTootEnv();
const {vote}= useTootEnv()
const now = useTimeSource();
const dateFnLocale = useDateFnLocale();
@ -43,7 +46,7 @@ const TootPoll: Component<TootPollProps> = (props) => {
const [initialVote, setInitialVote] = createSignal(0);
const poll = () => props.value;
const poll = () => props.value
const isShowResult = () => {
const n = mustShowResult();
@ -115,10 +118,14 @@ const TootPoll: Component<TootPollProps> = (props) => {
>
<ListItemText>
<span
innerHTML={resolveCustomEmoji(
option().title,
option().emojis,
)}
ref={(e) =>
createRenderEffect(() => {
e.innerHTML = resolveCustomEmoji(
option().title,
option().emojis,
);
})
}
></span>
</ListItemText>

View file

@ -96,10 +96,15 @@ const TootPollDialog: Component<TootPollDialogPoll> = (props) => {
>
<ListItemText>
<span
innerHTML={resolveCustomEmoji(
option().title,
option().emojis,
)}
ref={(e) =>
createRenderEffect(
() =>
(e.innerHTML = resolveCustomEmoji(
option().title,
option().emojis,
)),
)
}
></span>
</ListItemText>