Compare commits

..

5 commits

Author SHA1 Message Date
thislight
5d6eb7282a
use innerHTML property
All checks were successful
/ depoly (push) Successful in 1m18s
2024-11-24 17:16:06 +08:00
thislight
f56b92fff0
TootAuthorGroup: remove css module 2024-11-24 17:07:14 +08:00
thislight
edfbf5505b
TootLangPicker: fix scrolling 2024-11-24 16:55:40 +08:00
thislight
2c35950d27
Masonry: fix do not reflow in resizing 2024-11-24 16:46:00 +08:00
thislight
5199f2bb6a
Masonry: fix typo 2024-11-24 16:00:56 +08:00
15 changed files with 144 additions and 179 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_PLATFROM_MASONRY_ALWAYS_COMPAT=
VITE_PLATFORM_MASONRY_ALWAYS_COMPAT=

View file

@ -20,17 +20,6 @@ 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_PLATFROM_MASONRY_ALWAYS_COMPAT?: string
readonly VITE_PLATFORM_MASONRY_ALWAYS_COMPAT?: string
}
interface ImportMeta {

View file

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

View file

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

View file

@ -10,23 +10,19 @@ 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 = {
@ -66,63 +62,15 @@ type RegularTootProps = {
export function findRootToot(element: HTMLElement) {
let current: HTMLElement | null = element;
while (current && !current.classList.contains(tootStyle.toot)) {
while (current && !current.classList.contains("RegularToot")) {
current = current.parentElement;
}
if (!current) {
throw Error(
`the element must be placed under a element with ${tootStyle.toot}`,
);
throw Error(`the element must be placed under a element with .RegularToot`);
}
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`.
*/
@ -195,8 +143,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",
@ -210,14 +158,10 @@ const RegularToot: Component<RegularTootProps> = (oprops) => {
<div class="retoot-grp">
<BoostIcon />
<Body2
ref={(e: { innerHTML: string }) => {
createRenderEffect(() => {
e.innerHTML = resolveCustomEmoji(
status().account.displayName,
toot().emojis,
);
});
}}
innerHTML={resolveCustomEmoji(
status().account.displayName,
toot().emojis,
)}
></Body2>
<span>boosts</span>
</div>

View file

@ -261,13 +261,7 @@ const TootBottomSheet: Component = (props) => {
>
<BackButton color="inherit" />
<Title component="div" class="name" use:solid-styled>
<span
ref={(e: HTMLElement) =>
createRenderEffect(
() => (e.innerHTML = tootDisplayName() ?? "Someone"),
)
}
></span>
<span 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 "./ChooseTootLang";
import ChooseTootLang from "./TootLangPicker";
import type { mastodon } from "masto";
import cardStyles from "~material/cards.module.css";
import Menu, { createManagedMenuState } from "~material/Menu";
@ -349,15 +349,10 @@ const TootComposer: Component<{
</ListItemAvatar>
<ListItemText secondary={"Default account"}>
<span
ref={(e) => {
createRenderEffect(() => {
const inf = session()?.account.inf;
return (e.innerHTML = resolveCustomEmoji(
inf?.displayName || "",
inf?.emojis ?? [],
));
});
}}
innerHTML={resolveCustomEmoji(
session()?.account.inf?.displayName || "",
session()?.account.inf?.emojis ?? [],
)}
></span>
</ListItemText>
</MenuItem>

View file

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

View file

@ -1,9 +1,4 @@
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,
@ -19,6 +14,7 @@ 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;
@ -58,6 +54,7 @@ const ChooseTootLang: Component<ChooseTootLangProps> = (props) => {
</Toolbar>
</AppBar>
}
class="TootLangPicker"
>
<List
ref={listRef!}

View file

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

View file

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

View file

@ -20,10 +20,7 @@ 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";
@ -31,13 +28,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();
@ -46,7 +43,7 @@ const TootPoll: Component<TootPollProps> = (props) => {
const [initialVote, setInitialVote] = createSignal(0);
const poll = () => props.value
const poll = () => props.value;
const isShowResult = () => {
const n = mustShowResult();
@ -118,14 +115,10 @@ const TootPoll: Component<TootPollProps> = (props) => {
>
<ListItemText>
<span
ref={(e) =>
createRenderEffect(() => {
e.innerHTML = resolveCustomEmoji(
option().title,
option().emojis,
);
})
}
innerHTML={resolveCustomEmoji(
option().title,
option().emojis,
)}
></span>
</ListItemText>

View file

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