Experimental 'Add to thread'
This commit is contained in:
parent
8ada3cebf8
commit
119cff3825
3 changed files with 150 additions and 13 deletions
24
src/app.css
24
src/app.css
|
@ -444,7 +444,8 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
var(--line-radius), var(--line-radius); */
|
var(--line-radius), var(--line-radius); */
|
||||||
--curves-radius: calc(var(--curves-width) / 2);
|
--curves-radius: calc(var(--curves-width) / 2);
|
||||||
height: calc(var(--curves-width) - var(--line-width));
|
height: calc(var(--curves-width) - var(--line-width));
|
||||||
background-image: radial-gradient(
|
background-image:
|
||||||
|
radial-gradient(
|
||||||
circle at bottom var(--forward),
|
circle at bottom var(--forward),
|
||||||
transparent calc(var(--curves-radius) - var(--line-width)),
|
transparent calc(var(--curves-radius) - var(--line-width)),
|
||||||
var(--comment-line-color) calc(var(--curves-radius) - var(--line-width))
|
var(--comment-line-color) calc(var(--curves-radius) - var(--line-width))
|
||||||
|
@ -475,7 +476,8 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
transparent
|
transparent
|
||||||
);
|
);
|
||||||
&.hero:not(:has(+ .thread), :first-child, :only-child, :last-child) {
|
&.hero:not(:has(+ .thread), :first-child, :only-child, :last-child) {
|
||||||
background-image: linear-gradient(
|
background-image:
|
||||||
|
linear-gradient(
|
||||||
var(--line-dir),
|
var(--line-dir),
|
||||||
transparent,
|
transparent,
|
||||||
transparent var(--indent-small-start),
|
transparent var(--indent-small-start),
|
||||||
|
@ -1050,7 +1052,8 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-image: radial-gradient(
|
background-image:
|
||||||
|
radial-gradient(
|
||||||
ellipse 50% 32px at bottom center,
|
ellipse 50% 32px at bottom center,
|
||||||
var(--carousel-faded-color),
|
var(--carousel-faded-color),
|
||||||
transparent
|
transparent
|
||||||
|
@ -1736,6 +1739,18 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-post-text {
|
||||||
|
max-width: 250px;
|
||||||
|
overflow: hidden;
|
||||||
|
/* ellipsis 2 lines */
|
||||||
|
white-space: normal;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
/* SHEET */
|
/* SHEET */
|
||||||
|
|
||||||
.sheet {
|
.sheet {
|
||||||
|
@ -2311,7 +2326,8 @@ body > .szh-menu-container {
|
||||||
var(--bg-color) var(--middle-circle-radius),
|
var(--bg-color) var(--middle-circle-radius),
|
||||||
transparent var(--middle-circle-radius)
|
transparent var(--middle-circle-radius)
|
||||||
);
|
);
|
||||||
background-image: var(--middle-circle),
|
background-image:
|
||||||
|
var(--middle-circle),
|
||||||
conic-gradient(var(--color) var(--fill), var(--outline-color) 0);
|
conic-gradient(var(--color) var(--fill), var(--outline-color) 0);
|
||||||
transform: scale(0.7);
|
transform: scale(0.7);
|
||||||
&:dir(rtl) {
|
&:dir(rtl) {
|
||||||
|
|
|
@ -1,24 +1,52 @@
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { ControlledMenu } from '@szhsin/react-menu';
|
import { ControlledMenu, MenuDivider, MenuItem } from '@szhsin/react-menu';
|
||||||
import { useRef, useState } from 'preact/hooks';
|
import { useCallback, useRef, useState } from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
|
import { api } from '../utils/api';
|
||||||
|
import niceDateTime from '../utils/nice-date-time';
|
||||||
import openCompose from '../utils/open-compose';
|
import openCompose from '../utils/open-compose';
|
||||||
import openOSK from '../utils/open-osk';
|
import openOSK from '../utils/open-osk';
|
||||||
|
import pmem from '../utils/pmem';
|
||||||
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
||||||
|
import showCompose from '../utils/show-compose';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
import statusPeek from '../utils/status-peek';
|
||||||
|
import { getCurrentAccountID } from '../utils/store-utils';
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import MenuLink from './menu-link';
|
import MenuLink from './menu-link';
|
||||||
|
import RelativeTime from './relative-time';
|
||||||
|
import SubMenu2 from './submenu2';
|
||||||
|
|
||||||
|
// Function to fetch the latest posts from the current user
|
||||||
|
// Use pmem to memoize fetch results for 1 minute
|
||||||
|
const fetchLatestPostsMemoized = pmem(
|
||||||
|
async (masto, currentAccountID) => {
|
||||||
|
const statusesIterator = masto.v1.accounts
|
||||||
|
.$select(currentAccountID)
|
||||||
|
.statuses.list({
|
||||||
|
limit: 3,
|
||||||
|
exclude_replies: true,
|
||||||
|
exclude_reblogs: true,
|
||||||
|
});
|
||||||
|
const { value } = await statusesIterator.next();
|
||||||
|
return value || [];
|
||||||
|
},
|
||||||
|
{ maxAge: 60000 },
|
||||||
|
); // 1 minute cache
|
||||||
|
|
||||||
export default function ComposeButton() {
|
export default function ComposeButton() {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
|
const { masto } = api();
|
||||||
|
|
||||||
// Context menu state
|
// Context menu state
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const [latestPosts, setLatestPosts] = useState([]);
|
||||||
|
const [loadingPosts, setLoadingPosts] = useState(false);
|
||||||
const buttonRef = useRef(null);
|
const buttonRef = useRef(null);
|
||||||
const menuRef = useRef(null);
|
const menuRef = useRef(null);
|
||||||
|
|
||||||
|
@ -58,6 +86,29 @@ export default function ComposeButton() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchLatestPosts = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoadingPosts(true);
|
||||||
|
const currentAccountID = getCurrentAccountID();
|
||||||
|
if (!currentAccountID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const posts = await fetchLatestPostsMemoized(masto, currentAccountID);
|
||||||
|
setLatestPosts(posts);
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
|
setLoadingPosts(false);
|
||||||
|
}
|
||||||
|
}, [masto]);
|
||||||
|
|
||||||
|
// Function to handle opening the compose window to reply to a post
|
||||||
|
const handleReplyToPost = useCallback((post) => {
|
||||||
|
showCompose({
|
||||||
|
replyToStatus: post,
|
||||||
|
});
|
||||||
|
setMenuOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
@ -100,6 +151,67 @@ export default function ComposeButton() {
|
||||||
<Trans>Scheduled Posts</Trans>
|
<Trans>Scheduled Posts</Trans>
|
||||||
</span>
|
</span>
|
||||||
</MenuLink>
|
</MenuLink>
|
||||||
|
<MenuDivider />
|
||||||
|
<SubMenu2
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<Icon icon="comment" size="l" />{' '}
|
||||||
|
<span className="menu-grow">
|
||||||
|
<Trans>Add to thread</Trans>
|
||||||
|
</span>
|
||||||
|
<Icon icon="chevron-right" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
onMenuChange={(e) => {
|
||||||
|
if (e.open) {
|
||||||
|
fetchLatestPosts();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loadingPosts ? (
|
||||||
|
<MenuItem disabled>
|
||||||
|
<span>
|
||||||
|
<Trans>Loading…</Trans>
|
||||||
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
) : latestPosts.length === 0 ? (
|
||||||
|
<MenuItem disabled>
|
||||||
|
<small>
|
||||||
|
<Trans>No posts found</Trans>
|
||||||
|
</small>
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
latestPosts.map((post) => {
|
||||||
|
const createdDate = new Date(post.createdAt);
|
||||||
|
const isWithinDay =
|
||||||
|
new Date().getTime() - createdDate.getTime() < 86400000;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem key={post.id} onClick={() => handleReplyToPost(post)}>
|
||||||
|
<small>
|
||||||
|
<div class="menu-post-text">{statusPeek(post)}</div>
|
||||||
|
<span className="more-insignificant">
|
||||||
|
{/* Show relative time if within a day */}
|
||||||
|
{isWithinDay && (
|
||||||
|
<>
|
||||||
|
<RelativeTime datetime={createdDate} format="micro" />{' '}
|
||||||
|
‒{' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<time
|
||||||
|
className="created"
|
||||||
|
dateTime={createdDate.toISOString()}
|
||||||
|
title={createdDate.toLocaleString()}
|
||||||
|
>
|
||||||
|
{niceDateTime(post.createdAt)}
|
||||||
|
</time>
|
||||||
|
</span>
|
||||||
|
</small>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</SubMenu2>
|
||||||
</ControlledMenu>
|
</ControlledMenu>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
23
src/locales/en.po
generated
23
src/locales/en.po
generated
|
@ -565,18 +565,32 @@ msgstr ""
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/compose-button.jsx:77
|
#: src/components/compose-button.jsx:128
|
||||||
#: src/compose.jsx:38
|
#: src/compose.jsx:38
|
||||||
msgid "Compose"
|
msgid "Compose"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/compose-button.jsx:100
|
#: src/components/compose-button.jsx:151
|
||||||
#: src/components/nav-menu.jsx:260
|
#: src/components/nav-menu.jsx:260
|
||||||
#: src/pages/scheduled-posts.jsx:31
|
#: src/pages/scheduled-posts.jsx:31
|
||||||
#: src/pages/scheduled-posts.jsx:76
|
#: src/pages/scheduled-posts.jsx:76
|
||||||
msgid "Scheduled Posts"
|
msgid "Scheduled Posts"
|
||||||
msgstr "Scheduled Posts"
|
msgstr "Scheduled Posts"
|
||||||
|
|
||||||
|
#: src/components/compose-button.jsx:160
|
||||||
|
msgid "Add to thread"
|
||||||
|
msgstr "Add to thread"
|
||||||
|
|
||||||
|
#: src/components/compose-button.jsx:174
|
||||||
|
#: src/components/status.jsx:3034
|
||||||
|
#: src/pages/annual-report.jsx:45
|
||||||
|
msgid "Loading…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/compose-button.jsx:180
|
||||||
|
msgid "No posts found"
|
||||||
|
msgstr "No posts found"
|
||||||
|
|
||||||
#: src/components/compose.jsx:211
|
#: src/components/compose.jsx:211
|
||||||
msgid "Add media"
|
msgid "Add media"
|
||||||
msgstr "Add media"
|
msgstr "Add media"
|
||||||
|
@ -2392,11 +2406,6 @@ msgstr ""
|
||||||
msgid "Failed to load history"
|
msgid "Failed to load history"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/status.jsx:3034
|
|
||||||
#: src/pages/annual-report.jsx:45
|
|
||||||
msgid "Loading…"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/components/status.jsx:3270
|
#: src/components/status.jsx:3270
|
||||||
msgid "HTML Code"
|
msgid "HTML Code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
Loading…
Add table
Reference in a new issue