mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-03-13 09:28:50 +01:00
commit
adf0b351c1
15 changed files with 269 additions and 150 deletions
34
src/app.css
34
src/app.css
|
@ -144,9 +144,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
user-select: none;
|
||||
}
|
||||
.deck > header .header-grid {
|
||||
background-color: var(--bg-blur-color);
|
||||
background-color: var(--bg-color);
|
||||
/* background-color: var(--bg-blur-color);
|
||||
background-image: linear-gradient(to bottom, var(--bg-color), transparent);
|
||||
backdrop-filter: saturate(180%) blur(20px);
|
||||
backdrop-filter: saturate(180%) blur(20px); */
|
||||
border-bottom: var(--hairline-width) solid var(--divider-color);
|
||||
min-height: 3em;
|
||||
display: grid;
|
||||
|
@ -893,17 +894,20 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
text-shadow: 0 1px var(--bg-color);
|
||||
}
|
||||
.status-carousel > ul {
|
||||
--carousel-gap: 16px;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overflow-y: clip;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-behavior: smooth;
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
gap: 16px;
|
||||
gap: var(--carousel-gap);
|
||||
align-items: flex-start;
|
||||
counter-reset: index;
|
||||
min-height: 160px;
|
||||
max-height: 65vh;
|
||||
max-height: 65dvh;
|
||||
}
|
||||
.status-carousel > ul > li {
|
||||
scroll-snap-align: center;
|
||||
|
@ -915,14 +919,23 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 65vh;
|
||||
max-height: 65dvh;
|
||||
/* max-height: 65vh;
|
||||
max-height: 65dvh; */
|
||||
counter-increment: index;
|
||||
position: relative;
|
||||
}
|
||||
.status-carousel > ul > li:is(:empty, :has(> a:empty)) {
|
||||
display: none;
|
||||
}
|
||||
.status-carousel .status-carousel-beacon {
|
||||
margin-right: calc(-1 * var(--carousel-gap));
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
||||
~ .status-carousel-beacon {
|
||||
margin-left: calc(-1 * var(--carousel-gap));
|
||||
}
|
||||
}
|
||||
/*
|
||||
Assume that browsers that do support inline-size property also support container queries.
|
||||
https://www.smashingmagazine.com/2021/05/css-container-queries-use-cases-migration-strategies/#progressive-enhancement-polyfills
|
||||
|
@ -941,6 +954,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
}
|
||||
.status-carousel .content-container .content:only-child {
|
||||
font-size: calc(100% + 25% * max(2 - var(--content-text-weight), 0));
|
||||
|
||||
&:has(.status-card) {
|
||||
font-size: unset;
|
||||
}
|
||||
}
|
||||
/* .status-carousel
|
||||
.content-container[data-content-text-weight='1']
|
||||
|
@ -1422,7 +1439,7 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
|||
right: max(16px, env(safe-area-inset-right));
|
||||
padding: 16px;
|
||||
background-color: var(--button-bg-blur-color);
|
||||
backdrop-filter: blur(16px);
|
||||
/* backdrop-filter: blur(16px); */
|
||||
z-index: 10;
|
||||
box-shadow: 0 3px 8px -1px var(--drop-shadow-color),
|
||||
0 10px 36px -4px var(--button-bg-blur-color);
|
||||
|
@ -1635,7 +1652,7 @@ body > .szh-menu-container {
|
|||
border-radius: 8px;
|
||||
box-shadow: 0 3px 16px -3px var(--drop-shadow-color);
|
||||
text-align: left;
|
||||
animation: appear-smooth 0.15s ease-in-out;
|
||||
/* animation: appear-smooth 0.15s ease-in-out; */
|
||||
width: 16em;
|
||||
max-width: 90vw;
|
||||
/* overflow: hidden; */
|
||||
|
@ -2471,6 +2488,7 @@ ul.link-list li a .icon {
|
|||
border-bottom: 0;
|
||||
border-radius: 16px;
|
||||
background-color: var(--bg-faded-blur-color);
|
||||
backdrop-filter: blur(16px);
|
||||
background-image: none;
|
||||
border-radius: 16px;
|
||||
min-height: 4em;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import moize from 'moize';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
|
||||
const SIZES = {
|
||||
|
@ -110,6 +111,29 @@ export const ICONS = {
|
|||
|
||||
const ICONDATA = {};
|
||||
|
||||
// Memoize the dangerouslySetInnerHTML of the SVGs
|
||||
const SVGICon = moize(
|
||||
function ({ size, width, height, body, rotate, flip }) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
dangerouslySetInnerHTML={{ __html: body }}
|
||||
style={{
|
||||
transform: `${rotate ? `rotate(${rotate})` : ''} ${
|
||||
flip ? `scaleX(-1)` : ''
|
||||
}`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
{
|
||||
isShallowEqual: true,
|
||||
maxSize: Object.keys(ICONS).length,
|
||||
},
|
||||
);
|
||||
|
||||
function Icon({
|
||||
icon,
|
||||
size = 'm',
|
||||
|
@ -122,6 +146,11 @@ function Icon({
|
|||
|
||||
const iconSize = SIZES[size];
|
||||
let iconBlock = ICONS[icon];
|
||||
if (!iconBlock) {
|
||||
console.warn(`Icon ${icon} not found`);
|
||||
return null;
|
||||
}
|
||||
|
||||
let rotate, flip;
|
||||
if (Array.isArray(iconBlock)) {
|
||||
[iconBlock, rotate, flip] = iconBlock;
|
||||
|
@ -150,16 +179,24 @@ function Icon({
|
|||
}}
|
||||
>
|
||||
{iconData && (
|
||||
<svg
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
viewBox={`0 0 ${iconData.width} ${iconData.height}`}
|
||||
dangerouslySetInnerHTML={{ __html: iconData.body }}
|
||||
style={{
|
||||
transform: `${rotate ? `rotate(${rotate})` : ''} ${
|
||||
flip ? `scaleX(-1)` : ''
|
||||
}`,
|
||||
}}
|
||||
// <svg
|
||||
// width={iconSize}
|
||||
// height={iconSize}
|
||||
// viewBox={`0 0 ${iconData.width} ${iconData.height}`}
|
||||
// dangerouslySetInnerHTML={{ __html: iconData.body }}
|
||||
// style={{
|
||||
// transform: `${rotate ? `rotate(${rotate})` : ''} ${
|
||||
// flip ? `scaleX(-1)` : ''
|
||||
// }`,
|
||||
// }}
|
||||
// />
|
||||
<SVGICon
|
||||
size={iconSize}
|
||||
width={iconData.width}
|
||||
height={iconData.height}
|
||||
body={iconData.body}
|
||||
rotate={rotate}
|
||||
flip={flip}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
mix-blend-mode: luminosity;
|
||||
/* mix-blend-mode: luminosity; */
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
|
|
|
@ -15,6 +15,22 @@ import Link from './link';
|
|||
import Modal from './modal';
|
||||
import Notification from './notification';
|
||||
|
||||
{
|
||||
if ('serviceWorker' in navigator) {
|
||||
console.log('👂👂👂 Listen to message');
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
console.log('💥💥💥 Message event', event);
|
||||
const { type, id, accessToken } = event?.data || {};
|
||||
if (type === 'notification') {
|
||||
states.routeNotification = {
|
||||
id,
|
||||
accessToken,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default memo(function NotificationService() {
|
||||
if (!('serviceWorker' in navigator)) return null;
|
||||
|
||||
|
@ -82,25 +98,25 @@ export default memo(function NotificationService() {
|
|||
})();
|
||||
}, [id, accessToken]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// Listen to message from service worker
|
||||
const handleMessage = (event) => {
|
||||
console.log('💥💥💥 Message event', event);
|
||||
const { type, id, accessToken } = event?.data || {};
|
||||
if (type === 'notification') {
|
||||
states.routeNotification = {
|
||||
id,
|
||||
accessToken,
|
||||
};
|
||||
}
|
||||
};
|
||||
console.log('👂👂👂 Listen to message');
|
||||
navigator.serviceWorker.addEventListener('message', handleMessage);
|
||||
return () => {
|
||||
console.log('👂👂👂 Remove listen to message');
|
||||
navigator.serviceWorker.removeEventListener('message', handleMessage);
|
||||
};
|
||||
}, []);
|
||||
// useLayoutEffect(() => {
|
||||
// // Listen to message from service worker
|
||||
// const handleMessage = (event) => {
|
||||
// console.log('💥💥💥 Message event', event);
|
||||
// const { type, id, accessToken } = event?.data || {};
|
||||
// if (type === 'notification') {
|
||||
// states.routeNotification = {
|
||||
// id,
|
||||
// accessToken,
|
||||
// };
|
||||
// }
|
||||
// };
|
||||
// console.log('👂👂👂 Listen to message');
|
||||
// navigator.serviceWorker.addEventListener('message', handleMessage);
|
||||
// return () => {
|
||||
// console.log('👂👂👂 Remove listen to message');
|
||||
// navigator.serviceWorker.removeEventListener('message', handleMessage);
|
||||
// };
|
||||
// }, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (navigator?.clearAppBadge) {
|
||||
|
|
|
@ -172,7 +172,9 @@ export const SHORTCUTS_META = {
|
|||
id: 'search',
|
||||
title: ({ query }) => (query ? `"${query}"` : 'Search'),
|
||||
path: ({ query }) =>
|
||||
query ? `/search?q=${query}&type=statuses` : '/search',
|
||||
query
|
||||
? `/search?q=${encodeURIComponent(query)}&type=statuses`
|
||||
: '/search',
|
||||
icon: 'search',
|
||||
excludeViewMode: ({ query }) => (!query ? ['multi-column'] : []),
|
||||
},
|
||||
|
|
|
@ -59,8 +59,9 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background-color: var(--bg-blur-color);
|
||||
backdrop-filter: blur(16px) saturate(3);
|
||||
background-color: var(--bg-color);
|
||||
/* background-color: var(--bg-blur-color);
|
||||
backdrop-filter: blur(16px) saturate(3); */
|
||||
border-top: var(--hairline-width) solid var(--outline-color);
|
||||
box-shadow: 0 -8px 16px -8px var(--drop-shadow-color);
|
||||
overflow: auto;
|
||||
|
@ -165,6 +166,7 @@ shortcuts .tab-bar[hidden] {
|
|||
padding: env(safe-area-inset-top) env(safe-area-inset-right) 0
|
||||
env(safe-area-inset-left);
|
||||
background-color: var(--bg-faded-blur-color);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-bottom: var(--hairline-width) solid var(--bg-faded-color);
|
||||
|
|
|
@ -206,11 +206,11 @@
|
|||
.status-card:not(.status-carousel .status)
|
||||
:is(.content, .poll, .media-container) {
|
||||
max-height: 160px !important;
|
||||
overflow: hidden;
|
||||
overflow: clip;
|
||||
}
|
||||
.status.small:not(.status-carousel .status)
|
||||
.status.small:not(.status-carousel .status, .status.large .status)
|
||||
.status-card
|
||||
:is(.content, .poll, .media-container) {
|
||||
:is(.content, .poll, .media-container:not(.media-gt2)) {
|
||||
max-height: 80px !important;
|
||||
}
|
||||
.status.large .status-card :is(.content, .poll, .media-container) {
|
||||
|
@ -730,6 +730,9 @@
|
|||
tab-size: 2;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
.status-card .content p {
|
||||
margin-block: min(0.25em, 4px);
|
||||
}
|
||||
.status .content p:first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
@ -959,6 +962,11 @@
|
|||
height: auto;
|
||||
max-height: 60vh;
|
||||
}
|
||||
.status.status-card .media-container.media-eq1 .media {
|
||||
max-height: 160px;
|
||||
width: auto;
|
||||
max-width: min(var(--width), 100%);
|
||||
}
|
||||
/* Special media borders */
|
||||
.status .media-container.media-eq2 .media:first-of-type {
|
||||
border-radius: var(--media-radius) var(--media-radius-inner)
|
||||
|
@ -1957,7 +1965,7 @@ a.card:is(:hover, :focus):visited {
|
|||
color: var(--media-fg-color);
|
||||
background-color: var(--media-bg-color);
|
||||
border: var(--hairline-width) solid var(--media-outline-color);
|
||||
mix-blend-mode: luminosity;
|
||||
/* mix-blend-mode: luminosity; */
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
opacity: 0.65;
|
||||
|
|
|
@ -239,9 +239,10 @@ function Timeline({
|
|||
setNearReachStart(nearReachStart);
|
||||
if (reachStart) {
|
||||
loadItems(true);
|
||||
} else if (nearReachEnd || (reachEnd && showMore)) {
|
||||
loadItems();
|
||||
}
|
||||
// else if (nearReachEnd || (reachEnd && showMore)) {
|
||||
// loadItems();
|
||||
// }
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
@ -451,6 +452,8 @@ function Timeline({
|
|||
{uiState === 'default' &&
|
||||
(showMore ? (
|
||||
<InView
|
||||
root={scrollableRef.current}
|
||||
rootMargin={`0px 0px ${screen.height * 1.5}px 0px`}
|
||||
onChange={(inView) => {
|
||||
if (inView) {
|
||||
loadItems();
|
||||
|
@ -695,22 +698,29 @@ function StatusCarousel({ title, class: className, children }) {
|
|||
// });
|
||||
const startButtonRef = useRef();
|
||||
const endButtonRef = useRef();
|
||||
useScrollFn(
|
||||
{
|
||||
scrollableRef: carouselRef,
|
||||
direction: 'horizontal',
|
||||
init: true,
|
||||
},
|
||||
({ reachStart, reachEnd }) => {
|
||||
if (startButtonRef.current) startButtonRef.current.disabled = reachStart;
|
||||
if (endButtonRef.current) endButtonRef.current.disabled = reachEnd;
|
||||
},
|
||||
[],
|
||||
);
|
||||
// useScrollFn(
|
||||
// {
|
||||
// scrollableRef: carouselRef,
|
||||
// direction: 'horizontal',
|
||||
// init: true,
|
||||
// },
|
||||
// ({ reachStart, reachEnd }) => {
|
||||
// if (startButtonRef.current) startButtonRef.current.disabled = reachStart;
|
||||
// if (endButtonRef.current) endButtonRef.current.disabled = reachEnd;
|
||||
// },
|
||||
// [],
|
||||
// );
|
||||
// useEffect(() => {
|
||||
// init?.();
|
||||
// }, []);
|
||||
|
||||
const [render, setRender] = useState(false);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setRender(true);
|
||||
}, 1);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div class={`status-carousel ${className}`}>
|
||||
<header>
|
||||
|
@ -746,7 +756,23 @@ function StatusCarousel({ title, class: className, children }) {
|
|||
</button>
|
||||
</span>
|
||||
</header>
|
||||
<ul ref={carouselRef}>{children}</ul>
|
||||
<ul ref={carouselRef}>
|
||||
<InView
|
||||
class="status-carousel-beacon"
|
||||
onChange={(inView) => {
|
||||
if (startButtonRef.current)
|
||||
startButtonRef.current.disabled = inView;
|
||||
}}
|
||||
/>
|
||||
{children[0]}
|
||||
{render && children.slice(1)}
|
||||
<InView
|
||||
class="status-carousel-beacon"
|
||||
onChange={(inView) => {
|
||||
if (endButtonRef.current) endButtonRef.current.disabled = inView;
|
||||
}}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import { api } from '../utils/api';
|
|||
import { fetchRelationships } from '../utils/relationships';
|
||||
import shortenNumber from '../utils/shorten-number';
|
||||
import usePageVisibility from '../utils/usePageVisibility';
|
||||
import useScroll from '../utils/useScroll';
|
||||
import useTitle from '../utils/useTitle';
|
||||
|
||||
const SHORT_LIMIT = 5;
|
||||
|
@ -151,11 +150,9 @@ function Search({ columnMode, ...props }) {
|
|||
})();
|
||||
}
|
||||
|
||||
const { reachStart } = useScroll({
|
||||
scrollableRef,
|
||||
});
|
||||
const lastHiddenTime = useRef();
|
||||
usePageVisibility((visible) => {
|
||||
const reachStart = scrollableRef.current?.scrollTop === 0;
|
||||
if (visible && reachStart) {
|
||||
const timeDiff = Date.now() - lastHiddenTime.current;
|
||||
if (!lastHiddenTime.current || timeDiff > 1000 * 3) {
|
||||
|
|
|
@ -7,15 +7,12 @@
|
|||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
align-self: stretch;
|
||||
}
|
||||
.status-deck header h1 .deck-back {
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
.status-deck header.inview h1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hero-heading {
|
||||
font-size: var(--text-size);
|
||||
display: inline-block;
|
||||
|
|
|
@ -545,7 +545,6 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
const ancestors = statuses.filter((s) => s.ancestor);
|
||||
|
||||
const [heroInView, setHeroInView] = useState(true);
|
||||
const onView = useDebouncedCallback(setHeroInView, 100);
|
||||
const heroPointer = useMemo(() => {
|
||||
// get top offset of heroStatus
|
||||
if (!heroStatusRef.current || heroInView) return null;
|
||||
|
@ -652,10 +651,11 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
}
|
||||
});
|
||||
|
||||
const { nearReachStart } = useScroll({
|
||||
scrollableRef,
|
||||
distanceFromStartPx: 16,
|
||||
});
|
||||
const [reachTopPost, setReachTopPost] = useState(false);
|
||||
// const { nearReachStart } = useScroll({
|
||||
// scrollableRef,
|
||||
// distanceFromStartPx: 16,
|
||||
// });
|
||||
|
||||
const initialPageState = useRef(showMedia ? 'media+status' : 'status');
|
||||
|
||||
|
@ -693,7 +693,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
}, [mediaStatusID, showMedia]);
|
||||
|
||||
const renderStatus = useCallback(
|
||||
(status) => {
|
||||
(status, i) => {
|
||||
const {
|
||||
id: statusID,
|
||||
ancestor,
|
||||
|
@ -735,7 +735,13 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
<>
|
||||
<InView
|
||||
threshold={0.1}
|
||||
onChange={onView}
|
||||
onChange={(inView) => {
|
||||
queueMicrotask(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setHeroInView(inView);
|
||||
});
|
||||
});
|
||||
}}
|
||||
class="status-focus"
|
||||
tabIndex={0}
|
||||
>
|
||||
|
@ -810,15 +816,27 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
resetScrollPosition(statusID);
|
||||
}}
|
||||
>
|
||||
<Status
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
withinContext
|
||||
size={thread || ancestor ? 'm' : 's'}
|
||||
enableTranslate
|
||||
onMediaClick={handleMediaClick}
|
||||
onStatusLinkClick={handleStatusLinkClick}
|
||||
/>
|
||||
<InView
|
||||
skip={i !== 0 || !ancestor}
|
||||
threshold={0.5}
|
||||
onChange={(inView) => {
|
||||
queueMicrotask(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setReachTopPost(inView);
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Status
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
withinContext
|
||||
size={thread || ancestor ? 'm' : 's'}
|
||||
enableTranslate
|
||||
onMediaClick={handleMediaClick}
|
||||
onStatusLinkClick={handleStatusLinkClick}
|
||||
/>
|
||||
</InView>
|
||||
{ancestor && repliesCount > 1 && (
|
||||
<div class="replies-link">
|
||||
<Icon icon="comment2" />{' '}
|
||||
|
@ -935,9 +953,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
}}
|
||||
>
|
||||
<header
|
||||
class={`${heroInView ? 'inview' : ''} ${
|
||||
uiState === 'loading' ? 'loading' : ''
|
||||
}`}
|
||||
class={`${uiState === 'loading' ? 'loading' : ''}`}
|
||||
onDblClick={(e) => {
|
||||
// reload statuses
|
||||
states.reloadStatusPage++;
|
||||
|
@ -1011,7 +1027,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
behavior: 'smooth',
|
||||
});
|
||||
}}
|
||||
hidden={!ancestors.length || nearReachStart}
|
||||
hidden={!ancestors.length || reachTopPost}
|
||||
title={`${ancestors.length} posts above ‒ Go to top`}
|
||||
>
|
||||
<Icon icon="arrow-up" />
|
||||
|
|
|
@ -22,7 +22,7 @@ export function getInstanceStatusObject(url) {
|
|||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
|
||||
function getInstanceStatusURL(url) {
|
||||
|
|
|
@ -192,19 +192,14 @@ export function saveStatus(status, instance, opts) {
|
|||
// THREAD TRAVERSER
|
||||
if (!skipThreading) {
|
||||
queueMicrotask(() => {
|
||||
threadifyStatus(status, instance);
|
||||
if (status.reblog) {
|
||||
queueMicrotask(() => {
|
||||
threadifyStatus(status.reblog, instance);
|
||||
});
|
||||
}
|
||||
threadifyStatus(status.reblog || status, instance);
|
||||
});
|
||||
}
|
||||
|
||||
// UNFURLER
|
||||
if (!skipUnfurling) {
|
||||
queueMicrotask(() => {
|
||||
unfurlStatus(status, instance);
|
||||
unfurlStatus(status.reblog || status, instance);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -253,10 +248,10 @@ export const threadifyStatus = rateLimit(_threadifyStatus, 100);
|
|||
const fauxDiv = document.createElement('div');
|
||||
export function unfurlStatus(status, instance) {
|
||||
const { instance: currentInstance } = api();
|
||||
const content = status.reblog?.content || status.content;
|
||||
const content = status?.content;
|
||||
const hasLink = /<a/i.test(content);
|
||||
if (hasLink) {
|
||||
const sKey = statusKey(status?.reblog?.id || status?.id, instance);
|
||||
const sKey = statusKey(status?.id, instance);
|
||||
fauxDiv.innerHTML = content;
|
||||
const links = fauxDiv.querySelectorAll(
|
||||
'a[href]:not(.u-url):not(.mention):not(.hashtag)',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useEffect, useLayoutEffect, useState } from 'preact/hooks';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
||||
import { useThrottledCallback } from 'use-debounce';
|
||||
|
||||
export default function useScrollFn(
|
||||
{
|
||||
|
@ -22,59 +23,62 @@ export default function useScrollFn(
|
|||
const [nearReachStart, setNearReachStart] = useState(false);
|
||||
const [nearReachEnd, setNearReachEnd] = useState(false);
|
||||
const isVertical = direction === 'vertical';
|
||||
const previousScrollStart = useRef(null);
|
||||
|
||||
const onScroll = useThrottledCallback(() => {
|
||||
const scrollableElement = scrollableRef.current;
|
||||
const {
|
||||
scrollTop,
|
||||
scrollLeft,
|
||||
scrollHeight,
|
||||
scrollWidth,
|
||||
clientHeight,
|
||||
clientWidth,
|
||||
} = scrollableElement;
|
||||
const scrollStart = isVertical ? scrollTop : scrollLeft;
|
||||
const scrollDimension = isVertical ? scrollHeight : scrollWidth;
|
||||
const clientDimension = isVertical ? clientHeight : clientWidth;
|
||||
const scrollDistance = Math.abs(scrollStart - previousScrollStart.current);
|
||||
const distanceFromStartPx =
|
||||
_distanceFromStartPx ||
|
||||
Math.min(
|
||||
clientDimension * distanceFromStart,
|
||||
scrollDimension,
|
||||
scrollStart,
|
||||
);
|
||||
const distanceFromEndPx =
|
||||
_distanceFromEndPx ||
|
||||
Math.min(
|
||||
clientDimension * distanceFromEnd,
|
||||
scrollDimension,
|
||||
scrollDimension - scrollStart - clientDimension,
|
||||
);
|
||||
|
||||
if (
|
||||
scrollDistance >=
|
||||
(previousScrollStart.current < scrollStart
|
||||
? scrollThresholdEnd
|
||||
: scrollThresholdStart)
|
||||
) {
|
||||
setScrollDirection(
|
||||
previousScrollStart.current < scrollStart ? 'end' : 'start',
|
||||
);
|
||||
previousScrollStart.current = scrollStart;
|
||||
}
|
||||
|
||||
setReachStart(scrollStart <= 0);
|
||||
setReachEnd(scrollStart + clientDimension >= scrollDimension);
|
||||
setNearReachStart(scrollStart <= distanceFromStartPx);
|
||||
setNearReachEnd(
|
||||
scrollStart + clientDimension >= scrollDimension - distanceFromEndPx,
|
||||
);
|
||||
}, 500);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const scrollableElement = scrollableRef.current;
|
||||
if (!scrollableElement) return {};
|
||||
let previousScrollStart = isVertical
|
||||
? scrollableElement.scrollTop
|
||||
: scrollableElement.scrollLeft;
|
||||
|
||||
function onScroll() {
|
||||
const {
|
||||
scrollTop,
|
||||
scrollLeft,
|
||||
scrollHeight,
|
||||
scrollWidth,
|
||||
clientHeight,
|
||||
clientWidth,
|
||||
} = scrollableElement;
|
||||
const scrollStart = isVertical ? scrollTop : scrollLeft;
|
||||
const scrollDimension = isVertical ? scrollHeight : scrollWidth;
|
||||
const clientDimension = isVertical ? clientHeight : clientWidth;
|
||||
const scrollDistance = Math.abs(scrollStart - previousScrollStart);
|
||||
const distanceFromStartPx =
|
||||
_distanceFromStartPx ||
|
||||
Math.min(
|
||||
clientDimension * distanceFromStart,
|
||||
scrollDimension,
|
||||
scrollStart,
|
||||
);
|
||||
const distanceFromEndPx =
|
||||
_distanceFromEndPx ||
|
||||
Math.min(
|
||||
clientDimension * distanceFromEnd,
|
||||
scrollDimension,
|
||||
scrollDimension - scrollStart - clientDimension,
|
||||
);
|
||||
|
||||
if (
|
||||
scrollDistance >=
|
||||
(previousScrollStart < scrollStart
|
||||
? scrollThresholdEnd
|
||||
: scrollThresholdStart)
|
||||
) {
|
||||
setScrollDirection(previousScrollStart < scrollStart ? 'end' : 'start');
|
||||
previousScrollStart = scrollStart;
|
||||
}
|
||||
|
||||
setReachStart(scrollStart <= 0);
|
||||
setReachEnd(scrollStart + clientDimension >= scrollDimension);
|
||||
setNearReachStart(scrollStart <= distanceFromStartPx);
|
||||
setNearReachEnd(
|
||||
scrollStart + clientDimension >= scrollDimension - distanceFromEndPx,
|
||||
);
|
||||
}
|
||||
previousScrollStart.current =
|
||||
scrollableElement[isVertical ? 'scrollTop' : 'scrollLeft'];
|
||||
|
||||
scrollableElement.addEventListener('scroll', onScroll, { passive: true });
|
||||
|
||||
|
|
|
@ -9,11 +9,12 @@ import htmlPlugin from 'vite-plugin-html-config';
|
|||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import removeConsole from 'vite-plugin-remove-console';
|
||||
|
||||
const allowedEnvPrefixes = ['VITE_', 'PHANPY_'];
|
||||
const { NODE_ENV } = process.env;
|
||||
const {
|
||||
PHANPY_CLIENT_NAME: CLIENT_NAME,
|
||||
PHANPY_APP_ERROR_LOGGING: ERROR_LOGGING,
|
||||
} = loadEnv('production', process.cwd());
|
||||
} = loadEnv('production', process.cwd(), allowedEnvPrefixes);
|
||||
|
||||
const now = new Date();
|
||||
let commitHash;
|
||||
|
@ -35,7 +36,7 @@ const rollbarCode = fs.readFileSync(
|
|||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
envPrefix: ['VITE_', 'PHANPY_'],
|
||||
envPrefix: allowedEnvPrefixes,
|
||||
mode: NODE_ENV,
|
||||
define: {
|
||||
__BUILD_TIME__: JSON.stringify(now),
|
||||
|
|
Loading…
Reference in a new issue