Skip to content

Commit d34cce6

Browse files
authored
Merge pull request #450 from cheeaun/main
Update from main
2 parents aed8422 + f72ec0a commit d34cce6

File tree

12 files changed

+293
-42
lines changed

12 files changed

+293
-42
lines changed

src/app.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,6 +1788,7 @@ body > .szh-menu-container {
17881788
animation-duration: 0.3s;
17891789
animation-timing-function: ease-in-out;
17901790
width: auto;
1791+
min-width: min(12em, 90vw);
17911792
}
17921793
.szh-menu .footer {
17931794
margin: 8px 0 -8px;

src/components/ICONS.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,5 @@ export const ICONS = {
103103
'arrows-right': () => import('@iconify-icons/mingcute/arrows-right-line'),
104104
code: () => import('@iconify-icons/mingcute/code-line'),
105105
copy: () => import('@iconify-icons/mingcute/copy-2-line'),
106+
quote: () => import('@iconify-icons/mingcute/quote-left-line'),
106107
};

src/components/account-info.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,12 +453,15 @@ function AccountInfo({
453453
e.target.classList.add('loaded');
454454
try {
455455
// Get color from four corners of image
456-
const canvas = document.createElement('canvas');
456+
const canvas = window.OffscreenCanvas
457+
? new OffscreenCanvas(1, 1)
458+
: document.createElement('canvas');
457459
const ctx = canvas.getContext('2d', {
458460
willReadFrequently: true,
459461
});
460462
canvas.width = e.target.width;
461463
canvas.height = e.target.height;
464+
ctx.imageSmoothingEnabled = false;
462465
ctx.drawImage(e.target, 0, 0);
463466
// const colors = [
464467
// ctx.getImageData(0, 0, 1, 1).data,

src/components/avatar.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const canvas = window.OffscreenCanvas
2121
const ctx = canvas.getContext('2d', {
2222
willReadFrequently: true,
2323
});
24+
ctx.imageSmoothingEnabled = false;
2425

2526
function Avatar({ url, size, alt = '', squircle, ...props }) {
2627
size = SIZES[size] || size || SIZES.m;

src/components/compose.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ function Compose({
235235
};
236236
const focusTextarea = () => {
237237
setTimeout(() => {
238+
if (!textareaRef.current) return;
239+
// status starts with newline, focus on first position
240+
if (draftStatus?.status?.startsWith?.('\n')) {
241+
textareaRef.current.selectionStart = 0;
242+
textareaRef.current.selectionEnd = 0;
243+
}
238244
console.debug('FOCUS textarea');
239245
textareaRef.current?.focus();
240246
}, 300);

src/components/menu-confirm.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu';
1+
import { MenuItem, SubMenu } from '@szhsin/react-menu';
22
import { cloneElement } from 'preact';
33
import { useRef } from 'preact/hooks';
44

@@ -10,6 +10,7 @@ function MenuConfirm({
1010
confirmLabel,
1111
menuItemClassName,
1212
menuFooter,
13+
menuExtras,
1314
...props
1415
}) {
1516
const { children, onClick, ...restProps } = props;
@@ -53,6 +54,7 @@ function MenuConfirm({
5354
<MenuItem className={menuItemClassName} onClick={onClick}>
5455
{confirmLabel}
5556
</MenuItem>
57+
{menuExtras}
5658
{menuFooter}
5759
</Parent>
5860
);

src/components/status.jsx

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -748,10 +748,24 @@ function Status({
748748
confirmLabel={
749749
<>
750750
<Icon icon="rocket" />
751-
<span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>
751+
<span>{reblogged ? 'Unboost' : 'Boost'}</span>
752752
</>
753753
}
754754
className={`menu-reblog ${reblogged ? 'checked' : ''}`}
755+
menuExtras={
756+
<MenuItem
757+
onClick={() => {
758+
states.showCompose = {
759+
draftStatus: {
760+
status: `\n${url}`,
761+
},
762+
};
763+
}}
764+
>
765+
<Icon icon="quote" />
766+
<span>Quote</span>
767+
</MenuItem>
768+
}
755769
menuFooter={
756770
mediaNoDesc &&
757771
!reblogged && (
@@ -1100,7 +1114,12 @@ function Status({
11001114
const { clientX, clientY } = e.touches?.[0] || e;
11011115
// link detection copied from onContextMenu because here it works
11021116
const link = e.target.closest('a');
1103-
if (link && statusRef.current.contains(link)) return;
1117+
if (
1118+
link &&
1119+
statusRef.current.contains(link) &&
1120+
!link.getAttribute('href').startsWith('#')
1121+
)
1122+
return;
11041123
e.preventDefault();
11051124
setContextMenuProps({
11061125
anchorPoint: {
@@ -1346,7 +1365,12 @@ function Status({
13461365
if (e.metaKey) return;
13471366
// console.log('context menu', e);
13481367
const link = e.target.closest('a');
1349-
if (link && statusRef.current.contains(link)) return;
1368+
if (
1369+
link &&
1370+
statusRef.current.contains(link) &&
1371+
!link.getAttribute('href').startsWith('#')
1372+
)
1373+
return;
13501374

13511375
// If there's selected text, don't show custom context menu
13521376
const selection = window.getSelection?.();
@@ -1910,11 +1934,23 @@ function Status({
19101934
confirmLabel={
19111935
<>
19121936
<Icon icon="rocket" />
1913-
<span>
1914-
{reblogged ? 'Unboost?' : 'Boost to everyone?'}
1915-
</span>
1937+
<span>{reblogged ? 'Unboost' : 'Boost'}</span>
19161938
</>
19171939
}
1940+
menuExtras={
1941+
<MenuItem
1942+
onClick={() => {
1943+
states.showCompose = {
1944+
draftStatus: {
1945+
status: `\n${url}`,
1946+
},
1947+
};
1948+
}}
1949+
>
1950+
<Icon icon="quote" />
1951+
<span>Quote</span>
1952+
</MenuItem>
1953+
}
19181954
menuFooter={
19191955
mediaNoDesc &&
19201956
!reblogged && (
@@ -2131,10 +2167,13 @@ function Card({ card, selfReferential, instance }) {
21312167
const w = 44;
21322168
const h = 44;
21332169
const blurhashPixels = decodeBlurHash(blurhash, w, h);
2134-
const canvas = document.createElement('canvas');
2170+
const canvas = window.OffscreenCanvas
2171+
? new OffscreenCanvas(1, 1)
2172+
: document.createElement('canvas');
21352173
canvas.width = w;
21362174
canvas.height = h;
21372175
const ctx = canvas.getContext('2d');
2176+
ctx.imageSmoothingEnabled = false;
21382177
const imageData = ctx.createImageData(w, h);
21392178
imageData.data.set(blurhashPixels);
21402179
ctx.putImageData(imageData, 0, 0);

src/pages/catchup.css

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,10 +626,24 @@
626626
gap: 4px;
627627
align-items: center;
628628
flex-shrink: 0;
629+
min-height: 24px;
629630

630-
.icon {
631+
> .avatar {
632+
outline: 1px solid var(--bg-blur-color);
633+
}
634+
635+
> .avatar ~ .avatar {
636+
margin-left: -8px;
637+
}
638+
639+
> .icon {
631640
color: var(--reblog-color);
632641
}
642+
643+
> .name-text {
644+
opacity: 0.75;
645+
filter: grayscale(0.75);
646+
}
633647
}
634648

635649
.post-author {

src/pages/catchup.jsx

Lines changed: 126 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -429,9 +429,29 @@ function Catchup() {
429429
return postFilterMatches;
430430
});
431431

432+
// Deduplicate boosts
433+
const boostedPosts = {};
434+
filteredPosts = filteredPosts.filter((post) => {
435+
if (post.reblog) {
436+
if (boostedPosts[post.reblog.id]) {
437+
if (boostedPosts[post.reblog.id].__BOOSTERS) {
438+
boostedPosts[post.reblog.id].__BOOSTERS.add(post.account);
439+
} else {
440+
boostedPosts[post.reblog.id].__BOOSTERS = new Set([post.account]);
441+
}
442+
return false;
443+
} else {
444+
boostedPosts[post.reblog.id] = post;
445+
}
446+
}
447+
return true;
448+
});
449+
432450
if (selectedAuthor && authorCountsMap.has(selectedAuthor)) {
433451
filteredPosts = filteredPosts.filter(
434-
(post) => post.account.id === selectedAuthor,
452+
(post) =>
453+
post.account.id === selectedAuthor ||
454+
[...(post.__BOOSTERS || [])].find((a) => a.id === selectedAuthor),
435455
);
436456
}
437457

@@ -589,35 +609,40 @@ function Catchup() {
589609
authors,
590610
]);
591611

592-
const prevSelectedAuthorMissing = useRef(false);
593612
useEffect(() => {
594-
// console.log({
595-
// prevSelectedAuthorMissing,
596-
// selectedAuthor,
597-
// authors,
598-
// });
599-
let timer;
600613
if (selectedAuthor) {
601614
if (authors[selectedAuthor]) {
602-
if (prevSelectedAuthorMissing.current) {
603-
timer = setTimeout(() => {
604-
authorsListParent.current
605-
.querySelector(`[data-author="${selectedAuthor}"]`)
606-
?.scrollIntoView({
607-
behavior: 'smooth',
608-
block: 'nearest',
609-
inline: 'center',
610-
});
611-
}, 500);
612-
prevSelectedAuthorMissing.current = false;
615+
// Check if author is visible and within the scrollable area viewport
616+
const authorElement = authorsListParent.current.querySelector(
617+
`[data-author="${selectedAuthor}"]`,
618+
);
619+
const scrollableRect =
620+
authorsListParent.current?.getBoundingClientRect();
621+
const authorRect = authorElement?.getBoundingClientRect();
622+
console.log({
623+
sLeft: scrollableRect.left,
624+
sRight: scrollableRect.right,
625+
aLeft: authorRect.left,
626+
aRight: authorRect.right,
627+
});
628+
if (
629+
authorRect.left < scrollableRect.left ||
630+
authorRect.right > scrollableRect.right
631+
) {
632+
authorElement.scrollIntoView({
633+
block: 'nearest',
634+
inline: 'center',
635+
behavior: 'smooth',
636+
});
637+
} else if (authorRect.top < 0) {
638+
authorElement.scrollIntoView({
639+
block: 'nearest',
640+
inline: 'nearest',
641+
behavior: 'smooth',
642+
});
613643
}
614-
} else {
615-
prevSelectedAuthorMissing.current = true;
616644
}
617645
}
618-
return () => {
619-
clearTimeout(timer);
620-
};
621646
}, [selectedAuthor, authors]);
622647

623648
const [showHelp, setShowHelp] = useState(false);
@@ -663,6 +688,76 @@ function Catchup() {
663688
},
664689
{
665690
preventDefault: true,
691+
ignoreModifiers: true,
692+
},
693+
);
694+
695+
useHotkeys(
696+
'k',
697+
() => {
698+
const activeItem = document.activeElement.closest(itemsSelector);
699+
const activeItemRect = activeItem?.getBoundingClientRect();
700+
const allItems = Array.from(
701+
scrollableRef.current.querySelectorAll(itemsSelector),
702+
);
703+
if (
704+
activeItem &&
705+
activeItemRect.top < scrollableRef.current.clientHeight &&
706+
activeItemRect.bottom > 0
707+
) {
708+
const activeItemIndex = allItems.indexOf(activeItem);
709+
let prevItem = allItems[activeItemIndex - 1];
710+
if (prevItem) {
711+
prevItem.focus();
712+
prevItem.scrollIntoView({
713+
block: 'center',
714+
inline: 'center',
715+
behavior: 'smooth',
716+
});
717+
}
718+
} else {
719+
const topmostItem = allItems.find((item) => {
720+
const itemRect = item.getBoundingClientRect();
721+
return itemRect.top >= 44 && itemRect.left >= 0;
722+
});
723+
if (topmostItem) {
724+
topmostItem.focus();
725+
topmostItem.scrollIntoView({
726+
block: 'nearest',
727+
inline: 'center',
728+
behavior: 'smooth',
729+
});
730+
}
731+
}
732+
},
733+
{
734+
preventDefault: true,
735+
ignoreModifiers: true,
736+
},
737+
);
738+
739+
useHotkeys(
740+
'h, l',
741+
(_, handler) => {
742+
// Go next/prev selectedAuthor in authorCountsList list
743+
if (selectedAuthor) {
744+
const key = handler.keys[0];
745+
const index = authorCountsList.indexOf(selectedAuthor);
746+
if (key === 'h') {
747+
if (index > 0 && index < authorCountsList.length) {
748+
setSelectedAuthor(authorCountsList[index - 1]);
749+
}
750+
} else if (key === 'l') {
751+
if (index < authorCountsList.length - 1 && index >= 0) {
752+
setSelectedAuthor(authorCountsList[index + 1]);
753+
}
754+
}
755+
}
756+
},
757+
{
758+
preventDefault: true,
759+
ignoreModifiers: true,
760+
enableOnFormTags: true,
666761
},
667762
);
668763

@@ -1351,6 +1446,7 @@ const PostLine = memo(
13511446
_followedTags: isFollowedTags,
13521447
_filtered: filterInfo,
13531448
visibility,
1449+
__BOOSTERS,
13541450
} = post;
13551451
const isReplyTo = inReplyToId && inReplyToAccountId !== account.id;
13561452
const isFiltered = !!filterInfo;
@@ -1384,7 +1480,12 @@ const PostLine = memo(
13841480
<Avatar
13851481
url={account.avatarStatic || account.avatar}
13861482
squircle={account.bot}
1387-
/>{' '}
1483+
/>
1484+
{__BOOSTERS?.size > 0
1485+
? [...__BOOSTERS].map((b) => (
1486+
<Avatar url={b.avatarStatic || b.avatar} squircle={b.bot} />
1487+
))
1488+
: ''}{' '}
13881489
<Icon icon="rocket" />{' '}
13891490
{/* <Avatar
13901491
url={reblog.account.avatarStatic || reblog.account.avatar}

src/pages/notifications.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
border-color: var(--reply-to-color);
144144
box-shadow: 0 0 0 3px var(--reply-to-faded-color);
145145
}
146+
.notification:focus-visible .status-link,
146147
.notification .status-link:is(:hover, :focus) {
147148
background-color: var(--bg-blur-color);
148149
filter: saturate(1);

0 commit comments

Comments
 (0)