Experimental reply parent hint

This commit is contained in:
Lim Chee Aun 2024-01-30 14:34:54 +08:00
parent 14f5c37721
commit f3d77dd04e
7 changed files with 847 additions and 700 deletions

View file

@ -330,6 +330,62 @@
font-size: 90%; font-size: 90%;
} }
.status.compact-reply {
--avatar-size: 20px;
--line-start: 40px;
--line-width: 3px;
--line-end: calc(var(--line-start) + var(--line-width));
display: flex;
gap: 12px;
--top-padding: 8px;
padding-top: var(--top-padding);
padding-bottom: 0;
background-image: linear-gradient(
160deg,
var(--reply-to-faded-color),
transparent
);
> * {
opacity: 0.65;
transition: opacity 1s ease-out;
}
.status-link:hover & > * {
opacity: 1;
}
&:before {
content: '';
position: absolute;
top: calc(var(--top-padding) + var(--avatar-size));
left: var(--line-start);
width: var(--line-width);
height: calc(100% - var(--top-padding) - var(--avatar-size) + 16px);
background-color: var(--comment-line-color);
z-index: 0;
mask-image: linear-gradient(to bottom, #000 8px, transparent);
}
.avatar {
margin-left: calc((50px - var(--avatar-size)) / 2);
justify-self: center;
z-index: 1;
}
.content-compact {
overflow: hidden;
display: -webkit-box;
display: box;
-webkit-box-orient: vertical;
box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
font-size: 90%;
line-height: var(--avatar-size);
}
}
.status .container { .status .container {
flex-grow: 1; flex-grow: 1;
min-width: 0; min-width: 0;

View file

@ -126,6 +126,7 @@ function Status({
showFollowedTags, showFollowedTags,
allowContextMenu, allowContextMenu,
showActionsBar, showActionsBar,
showReplyParent,
}) { }) {
if (skeleton) { if (skeleton) {
return ( return (
@ -1271,6 +1272,10 @@ function Status({
]); ]);
return ( return (
<>
{showReplyParent && !!(inReplyToId && inReplyToAccount) && (
<StatusCompact sKey={sKey} />
)}
<article <article
data-state-post-id={sKey} data-state-post-id={sKey}
ref={(node) => { ref={(node) => {
@ -1670,7 +1675,11 @@ function Status({
</> </>
)} )}
{!!content && ( {!!content && (
<div class="content" ref={contentRef} data-read-more={readMoreText}> <div
class="content"
ref={contentRef}
data-read-more={readMoreText}
>
<div <div
lang={language} lang={language}
dir="auto" dir="auto"
@ -1814,7 +1823,9 @@ function Status({
showCaption={mediaAttachments.length === 1} showCaption={mediaAttachments.length === 1}
lang={language} lang={language}
altIndex={ altIndex={
showMultipleMediaCaptions && !!media.description && i + 1 showMultipleMediaCaptions &&
!!media.description &&
i + 1
} }
to={`/${instance}/s/${id}?${ to={`/${instance}/s/${id}?${
withinContext ? 'media' : 'media-only' withinContext ? 'media' : 'media-only'
@ -1920,7 +1931,9 @@ function Status({
confirmLabel={ confirmLabel={
<> <>
<Icon icon="rocket" /> <Icon icon="rocket" />
<span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span> <span>
{reblogged ? 'Unboost?' : 'Boost to everyone?'}
</span>
</> </>
} }
menuFooter={ menuFooter={
@ -2018,6 +2031,7 @@ function Status({
</Modal> </Modal>
)} )}
</article> </article>
</>
); );
} }
@ -2412,6 +2426,41 @@ function nicePostURL(url) {
); );
} }
function StatusCompact({ sKey }) {
const snapStates = useSnapshot(states);
const statusReply = snapStates.statusReply[sKey];
if (!statusReply) return null;
const { id, instance } = statusReply;
const status = getStatus(id, instance);
if (!status) return null;
const {
sensitive,
spoilerText,
account: { avatar, avatarStatic, bot },
visibility,
content,
} = status;
if (sensitive || spoilerText) return null;
if (!content) return null;
const statusPeekText = statusPeek(status);
return (
<article
class={`status compact-reply ${
visibility === 'direct' ? 'visibility-direct' : ''
}`}
tabindex="-1"
>
<Avatar url={avatarStatic || avatar} squircle={bot} />
<div class="content-compact" title={statusPeekText}>
{statusPeekText}
</div>
</article>
);
}
function FilteredStatus({ function FilteredStatus({
status, status,
filterInfo, filterInfo,

View file

@ -46,6 +46,7 @@ function Timeline({
view, view,
filterContext, filterContext,
showFollowedTags, showFollowedTags,
showReplyParent,
}) { }) {
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
const [items, setItems] = useState([]); const [items, setItems] = useState([]);
@ -84,7 +85,7 @@ function Timeline({
if (boostsCarousel) { if (boostsCarousel) {
value = groupBoosts(value); value = groupBoosts(value);
} }
value = groupContext(value); value = groupContext(value, instance);
} }
if (pinnedPosts.length) { if (pinnedPosts.length) {
value = pinnedPosts.concat(value); value = pinnedPosts.concat(value);
@ -522,6 +523,7 @@ function TimelineItem({
filterContext, filterContext,
view, view,
showFollowedTags, showFollowedTags,
showReplyParent,
}) { }) {
const { id: statusID, reblog, items, type, _pinned } = status; const { id: statusID, reblog, items, type, _pinned } = status;
if (_pinned) useItemID = false; if (_pinned) useItemID = false;
@ -680,6 +682,7 @@ function TimelineItem({
instance={instance} instance={instance}
enableCommentHint enableCommentHint
showFollowedTags={showFollowedTags} showFollowedTags={showFollowedTags}
showReplyParent
// allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
) : ( ) : (
@ -688,6 +691,7 @@ function TimelineItem({
instance={instance} instance={instance}
enableCommentHint enableCommentHint
showFollowedTags={showFollowedTags} showFollowedTags={showFollowedTags}
showReplyParent
// allowFilters={allowFilters} // allowFilters={allowFilters}
/> />
)} )}

View file

@ -129,6 +129,7 @@ function Following({ title, path, id, ...props }) {
// allowFilters // allowFilters
filterContext="home" filterContext="home"
showFollowedTags showFollowedTags
showReplyParent
/> />
); );
} }

View file

@ -104,6 +104,7 @@ function List(props) {
boostsCarousel={snapStates.settings.boostsCarousel} boostsCarousel={snapStates.settings.boostsCarousel}
// allowFilters // allowFilters
filterContext="home" filterContext="home"
showReplyParent
// refresh={reloadCount} // refresh={reloadCount}
headerStart={ headerStart={
<Link to="/l" class="button plain"> <Link to="/l" class="button plain">

View file

@ -37,6 +37,7 @@ const states = proxy({
unfurledLinks: {}, unfurledLinks: {},
statusQuotes: {}, statusQuotes: {},
statusFollowedTags: {}, statusFollowedTags: {},
statusReply: {},
accounts: {}, accounts: {},
routeNotification: null, routeNotification: null,
// Modals // Modals

View file

@ -1,6 +1,8 @@
import { api } from './api';
import { extractTagsFromStatus, getFollowedTags } from './followed-tags'; import { extractTagsFromStatus, getFollowedTags } from './followed-tags';
import pmem from './pmem';
import { fetchRelationships } from './relationships'; import { fetchRelationships } from './relationships';
import states, { statusKey } from './states'; import states, { saveStatus, statusKey } from './states';
import store from './store'; import store from './store';
export function groupBoosts(values) { export function groupBoosts(values) {
@ -81,7 +83,7 @@ export function dedupeBoosts(items, instance) {
return filteredItems; return filteredItems;
} }
export function groupContext(items) { export function groupContext(items, instance) {
const contexts = []; const contexts = [];
let contextIndex = 0; let contextIndex = 0;
items.forEach((item) => { items.forEach((item) => {
@ -173,12 +175,45 @@ export function groupContext(items) {
return; return;
} }
} }
if (item.inReplyToId && item.inReplyToAccountId !== item.account.id) {
const sKey = statusKey(item.id, instance);
if (states.statusReply[sKey]) {
return;
}
// If it's a reply and not a thread
queueMicrotask(async () => {
try {
const { masto } = api({ instance });
// const replyToStatus = await masto.v1.statuses
// .$select(item.inReplyToId)
// .fetch();
const replyToStatus = await fetchStatus(item.inReplyToId, masto);
saveStatus(replyToStatus, instance, {
skipThreading: true,
skipUnfurling: true,
});
states.statusReply[sKey] = {
id: replyToStatus.id,
instance,
};
} catch (e) {
// Silently fail
console.error(e);
}
});
}
newItems.push(item); newItems.push(item);
}); });
return newItems; return newItems;
} }
const fetchStatus = pmem((statusID, masto) => {
return masto.v1.statuses.$select(statusID).fetch();
});
export async function assignFollowedTags(items, instance) { export async function assignFollowedTags(items, instance) {
const followedTags = await getFollowedTags(); // [{name: 'tag'}, {...}] const followedTags = await getFollowedTags(); // [{name: 'tag'}, {...}]
if (!followedTags.length) return; if (!followedTags.length) return;