kopia lustrzana https://github.com/cheeaun/phanpy
commit
dbef125ee3
|
@ -199,6 +199,7 @@ These are self-hosted by other wonderful folks.
|
|||
- [phanpy.vmst.io](https://phanpy.vmst.io/) by [@vmstan@vmst.io](https://vmst.io/@vmstan)
|
||||
- [phanpy.gotosocial.social](https://phanpy.gotosocial.social/) by [@admin@gotosocial.social](https://gotosocial.social/@admin)
|
||||
- [phanpy.bauxite.tech](https://phanpy.bauxite.tech) by [@b4ux1t3@hachyderm.io](https://hachyderm.io/@b4ux1t3)
|
||||
- [phanpy.hear-me.social](https://phanpy.hear-me.social) by [@admin@hear-me.social](https://hear-me.social/@admin)
|
||||
|
||||
> Note: Add yours by creating a pull request.
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"dayjs": "~1.11.10",
|
||||
"dayjs-twitter": "~0.5.0",
|
||||
"fast-blurhash": "~1.1.2",
|
||||
"fast-deep-equal": "~3.1.3",
|
||||
"fast-equals": "~5.0.1",
|
||||
"idb-keyval": "~6.2.1",
|
||||
"just-debounce-it": "~3.2.0",
|
||||
"lz-string": "~1.5.0",
|
||||
|
@ -4405,13 +4405,16 @@
|
|||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-3.0.3.tgz",
|
||||
"integrity": "sha512-NCe8qxnZFARSHGztGMZOO/PC1qa5MIFB5Hp66WdzbCRAz8U8US3bx1UTgLS49efBQPcUtO9gf5oVEY8o7y/7Kg==",
|
||||
"license": "MIT"
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
|
||||
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
|
@ -5570,6 +5573,11 @@
|
|||
"micro-memoize": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/moize/node_modules/fast-equals": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-3.0.3.tgz",
|
||||
"integrity": "sha512-NCe8qxnZFARSHGztGMZOO/PC1qa5MIFB5Hp66WdzbCRAz8U8US3bx1UTgLS49efBQPcUtO9gf5oVEY8o7y/7Kg=="
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"dayjs": "~1.11.10",
|
||||
"dayjs-twitter": "~0.5.0",
|
||||
"fast-blurhash": "~1.1.2",
|
||||
"fast-deep-equal": "~3.1.3",
|
||||
"fast-equals": "~5.0.1",
|
||||
"idb-keyval": "~6.2.1",
|
||||
"just-debounce-it": "~3.2.0",
|
||||
"lz-string": "~1.5.0",
|
||||
|
|
98
src/app.css
98
src/app.css
|
@ -547,9 +547,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
list-style: none;
|
||||
}
|
||||
.timeline.contextual > li .replies > .replies-summary {
|
||||
padding: 8px;
|
||||
--summary-padding: 8px;
|
||||
padding: var(--summary-padding);
|
||||
background-color: var(--bg-faded-color);
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
|
@ -559,12 +560,67 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
box-shadow: 0 0 0 2px var(--bg-color);
|
||||
position: relative;
|
||||
list-style: none;
|
||||
white-space: nowrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-right: calc(44px + 8px);
|
||||
|
||||
b {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.avatars {
|
||||
flex-shrink: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
.avatar {
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin: 0 0 0 -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.replies-counts {
|
||||
/* flex-grow: 1; */
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.replies-summary-chevron {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.replies-parent-link {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
align-self: stretch;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--summary-padding) calc(var(--summary-padding) * 2);
|
||||
transform: translateX(100%);
|
||||
margin: calc(-1 * var(--summary-padding)) calc(-1 * var(--summary-padding))
|
||||
calc(-1 * var(--summary-padding)) 0;
|
||||
border-radius: 8px;
|
||||
background-color: var(--link-bg-color);
|
||||
|
||||
&:is(:hover, :focus) {
|
||||
color: var(--link-text-color);
|
||||
box-shadow: inset 0 0 0 2px var(--link-faded-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--link-faded-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.timeline.contextual > li .replies > .replies-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
|
@ -572,14 +628,14 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
.timeline.contextual > li .replies > .replies-summary > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.timeline.contextual > li .replies > .replies-summary .avatars {
|
||||
margin-right: 8px;
|
||||
|
||||
> *:not(:first-child) {
|
||||
margin: 0 0 0 -4px;
|
||||
}
|
||||
}
|
||||
.timeline.contextual > li .replies > .replies-summary:active,
|
||||
.timeline.contextual
|
||||
> li
|
||||
.replies
|
||||
> .replies-summary
|
||||
.timeline.contextual
|
||||
> li
|
||||
.replies
|
||||
> .replies-summary:active,
|
||||
.timeline.contextual > li .replies[open] > .replies-summary {
|
||||
color: var(--text-color);
|
||||
background-color: var(--comment-line-color);
|
||||
|
@ -591,6 +647,18 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
}
|
||||
.timeline.contextual > li .replies[open] > .replies-summary {
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
.avatars {
|
||||
opacity: 0.5;
|
||||
|
||||
.avatar {
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
}
|
||||
|
||||
.replies-summary-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
.timeline.contextual > li .replies .replies-summary[hidden] {
|
||||
display: none;
|
||||
|
@ -763,6 +831,11 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
border-top-right-radius: 0;
|
||||
border-top: 0;
|
||||
background-size: 100% 16px;
|
||||
|
||||
&:has(.status-pre-meta) {
|
||||
/* 20px = icon of the pre-meta */
|
||||
background-size: 100% calc(16px + 20px);
|
||||
}
|
||||
}
|
||||
.timeline:not(.flat)
|
||||
> li:is(.timeline-item-container-middle, .timeline-item-container-end)
|
||||
|
@ -2187,6 +2260,9 @@ ul.link-list li a .icon {
|
|||
.header-grid {
|
||||
background-color: var(--bg-blur-color);
|
||||
}
|
||||
#columns > .deck-container > .timeline-deck {
|
||||
content-visibility: auto;
|
||||
}
|
||||
#columns .header-grid input {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import './compose.css';
|
|||
|
||||
import '@github/text-expander-element';
|
||||
import { MenuItem } from '@szhsin/react-menu';
|
||||
import equal from 'fast-deep-equal';
|
||||
import { deepEqual } from 'fast-equals';
|
||||
import { forwardRef } from 'preact/compat';
|
||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
@ -502,7 +502,10 @@ function Compose({
|
|||
mediaAttachments,
|
||||
},
|
||||
};
|
||||
if (!equal(backgroundDraft, prevBackgroundDraft.current) && !canClose()) {
|
||||
if (
|
||||
!deepEqual(backgroundDraft, prevBackgroundDraft.current) &&
|
||||
!canClose()
|
||||
) {
|
||||
console.debug('not equal', backgroundDraft, prevBackgroundDraft.current);
|
||||
db.drafts
|
||||
.set(key, {
|
||||
|
@ -1838,10 +1841,17 @@ function MediaAttachment({
|
|||
method: 'POST',
|
||||
body,
|
||||
}).then((r) => r.json());
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
setDescription(response.description);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Failed to generate description');
|
||||
showToast(
|
||||
`Failed to generate description${
|
||||
e?.message ? `: ${e.message}` : ''
|
||||
}`,
|
||||
);
|
||||
} finally {
|
||||
setUIState('default');
|
||||
toastRef.current?.hideToast?.();
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
.embed-modal-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: none;
|
||||
|
||||
.top-controls {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.embed-content {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
iframe {
|
||||
pointer-events: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import './embed-modal.css';
|
||||
|
||||
import Icon from './icon';
|
||||
|
||||
function EmbedModal({ html, url, onClose = () => {} }) {
|
||||
return (
|
||||
<div class="embed-modal-container">
|
||||
<div class="top-controls">
|
||||
<button type="button" class="light" onClick={() => onClose()}>
|
||||
<Icon icon="x" />
|
||||
</button>
|
||||
{url && (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="button plain"
|
||||
>
|
||||
<span>Open link</span> <Icon icon="external" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div class="embed-content" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmbedModal;
|
|
@ -10,6 +10,7 @@ import states from '../utils/states';
|
|||
import AccountSheet from './account-sheet';
|
||||
import Compose from './compose';
|
||||
import Drafts from './drafts';
|
||||
import EmbedModal from './embed-modal';
|
||||
import GenericAccounts from './generic-accounts';
|
||||
import MediaAltModal from './media-alt-modal';
|
||||
import MediaModal from './media-modal';
|
||||
|
@ -200,6 +201,21 @@ export default function Modals() {
|
|||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{!!snapStates.showEmbedModal && (
|
||||
<Modal
|
||||
onClose={() => {
|
||||
states.showEmbedModal = false;
|
||||
}}
|
||||
>
|
||||
<EmbedModal
|
||||
html={snapStates.showEmbedModal.html}
|
||||
url={snapStates.showEmbedModal.url}
|
||||
onClose={() => {
|
||||
states.showEmbedModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
MenuItem,
|
||||
} from '@szhsin/react-menu';
|
||||
import { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash';
|
||||
import { shallowEqual } from 'fast-equals';
|
||||
import { memo } from 'preact/compat';
|
||||
import {
|
||||
useCallback,
|
||||
|
@ -1983,6 +1984,20 @@ function Card({ card, selfReferential, instance }) {
|
|||
|
||||
if (snapStates.unfurledLinks[url]) return null;
|
||||
|
||||
const hasIframeHTML = /<iframe/i.test(html);
|
||||
const handleClick = useCallback(
|
||||
(e) => {
|
||||
if (hasIframeHTML) {
|
||||
e.preventDefault();
|
||||
states.showEmbedModal = {
|
||||
html,
|
||||
url: url || embedUrl,
|
||||
};
|
||||
}
|
||||
},
|
||||
[hasIframeHTML],
|
||||
);
|
||||
|
||||
if (hasText && (image || (type === 'photo' && blurhash))) {
|
||||
const domain = new URL(url).hostname
|
||||
.replace(/^www\./, '')
|
||||
|
@ -2015,6 +2030,7 @@ function Card({ card, selfReferential, instance }) {
|
|||
'--average-color':
|
||||
rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div class="card-image">
|
||||
<img
|
||||
|
@ -2053,6 +2069,7 @@ function Card({ card, selfReferential, instance }) {
|
|||
target="_blank"
|
||||
rel="nofollow noopener noreferrer"
|
||||
class="card photo"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<img
|
||||
src={embedUrl}
|
||||
|
@ -2067,42 +2084,46 @@ function Card({ card, selfReferential, instance }) {
|
|||
/>
|
||||
</a>
|
||||
);
|
||||
} else if (type === 'video') {
|
||||
if (/youtube/i.test(providerName)) {
|
||||
// Get ID from e.g. https://www.youtube.com/watch?v=[VIDEO_ID]
|
||||
const videoID = url.match(/watch\?v=([^&]+)/)?.[1];
|
||||
if (videoID) {
|
||||
return <lite-youtube videoid={videoID} nocookie></lite-youtube>;
|
||||
} else {
|
||||
if (type === 'video') {
|
||||
if (/youtube/i.test(providerName)) {
|
||||
// Get ID from e.g. https://www.youtube.com/watch?v=[VIDEO_ID]
|
||||
const videoID = url.match(/watch\?v=([^&]+)/)?.[1];
|
||||
if (videoID) {
|
||||
return <lite-youtube videoid={videoID} nocookie></lite-youtube>;
|
||||
}
|
||||
}
|
||||
// return (
|
||||
// <div
|
||||
// class="card video"
|
||||
// style={{
|
||||
// aspectRatio: `${width}/${height}`,
|
||||
// }}
|
||||
// dangerouslySetInnerHTML={{ __html: html }}
|
||||
// />
|
||||
// );
|
||||
}
|
||||
if (hasText && !image) {
|
||||
const domain = new URL(url).hostname.replace(/^www\./, '');
|
||||
return (
|
||||
<a
|
||||
href={cardStatusURL || url}
|
||||
target={cardStatusURL ? null : '_blank'}
|
||||
rel="nofollow noopener noreferrer"
|
||||
class={`card link no-image`}
|
||||
lang={language}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div class="meta-container">
|
||||
<p class="meta domain">
|
||||
<Icon icon="link" size="s" /> <span>{domain}</span>
|
||||
</p>
|
||||
<p class="title">{title}</p>
|
||||
<p class="meta">{description || providerName || authorName}</p>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
class="card video"
|
||||
style={{
|
||||
aspectRatio: `${width}/${height}`,
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
);
|
||||
} else if (hasText && !image) {
|
||||
const domain = new URL(url).hostname.replace(/^www\./, '');
|
||||
return (
|
||||
<a
|
||||
href={cardStatusURL || url}
|
||||
target={cardStatusURL ? null : '_blank'}
|
||||
rel="nofollow noopener noreferrer"
|
||||
class={`card link no-image`}
|
||||
lang={language}
|
||||
>
|
||||
<div class="meta-container">
|
||||
<p class="meta domain">
|
||||
<Icon icon="link" size="s" /> <span>{domain}</span>
|
||||
</p>
|
||||
<p class="title">{title}</p>
|
||||
<p class="meta">{description || providerName || authorName}</p>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2460,4 +2481,12 @@ const QuoteStatuses = memo(({ id, instance, level = 0 }) => {
|
|||
});
|
||||
});
|
||||
|
||||
export default memo(Status);
|
||||
export default memo(Status, (oldProps, newProps) => {
|
||||
// Shallow equal all props except 'status'
|
||||
// This will be pure static until status ID changes
|
||||
const { status, ...restOldProps } = oldProps;
|
||||
const { status: newStatus, ...restNewProps } = newProps;
|
||||
return (
|
||||
status?.id === newStatus?.id && shallowEqual(restOldProps, restNewProps)
|
||||
);
|
||||
});
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
--text-color: #1c1e21;
|
||||
--text-insignificant-color: #1c1e2199;
|
||||
--link-color: var(--blue-color);
|
||||
--link-bg-color: #4169e122;
|
||||
--link-light-color: #4169e199;
|
||||
--link-faded-color: #4169e155;
|
||||
--link-bg-hover-color: #f0f2f599;
|
||||
|
@ -89,6 +90,7 @@
|
|||
--media-outline-color: color-mix(in lch, var(--media-fg-color), transparent);
|
||||
|
||||
--timing-function: cubic-bezier(0.3, 0.5, 0, 1);
|
||||
--spring-timing-funtion: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
|
@ -447,6 +449,17 @@ kbd {
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes slide-up-smooth {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes position-object {
|
||||
0% {
|
||||
object-position: 50% 50%;
|
||||
|
|
|
@ -164,7 +164,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
|
|||
portal
|
||||
setDownOverflow
|
||||
overflow="auto"
|
||||
viewScroll="close"
|
||||
// viewScroll="close"
|
||||
position="anchor"
|
||||
menuButton={
|
||||
<button type="button" class="plain">
|
||||
|
|
|
@ -37,16 +37,20 @@ export default function HttpRoute() {
|
|||
const { masto: currentMasto, instance: currentInstance } = api();
|
||||
const result = await currentMasto.v2.search.fetch({
|
||||
q: url,
|
||||
type: 'statuses',
|
||||
limit: 1,
|
||||
resolve: true,
|
||||
});
|
||||
if (result.statuses.length) {
|
||||
const status = result.statuses[0];
|
||||
window.location.hash = `/${currentInstance}/s/${status.id}?view=full`;
|
||||
} else {
|
||||
} else if (result.accounts.length) {
|
||||
const account = result.accounts[0];
|
||||
window.location.hash = `/${currentInstance}/a/${account.id}`;
|
||||
} else if (statusURL) {
|
||||
// Fallback to original URL, which will probably show error
|
||||
window.location.hash = statusURL + '?view=full';
|
||||
} else {
|
||||
setUIState('error');
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -115,22 +115,25 @@ ul.link-list.hashtag-list li a {
|
|||
display: none;
|
||||
}
|
||||
.search-popover-item:is(:hover, :focus, .focus) {
|
||||
background-color: var(--button-bg-color);
|
||||
color: var(--button-text-color);
|
||||
background-color: var(--link-bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
.search-popover-item:is(:focus, .focus) {
|
||||
box-shadow: inset 4px 0 0 0 var(--button-bg-color);
|
||||
}
|
||||
.search-popover-item :is(mark, q) {
|
||||
background-color: var(--bg-faded-blur-color);
|
||||
color: inherit;
|
||||
color: var(--text-color);
|
||||
background-color: var(--link-bg-color);
|
||||
}
|
||||
.search-popover-item:is(:hover, :focus, .focus) :is(mark, q) {
|
||||
background-color: var(--button-bg-color);
|
||||
background-color: var(--link-bg-color);
|
||||
}
|
||||
.search-popover:hover .search-popover-item.focus:not(:hover, :focus),
|
||||
.search-popover:hover
|
||||
.search-popover-item.focus:not(:hover, :focus)
|
||||
:is(mark, q) {
|
||||
background-color: unset;
|
||||
color: unset;
|
||||
/* background-color: unset; */
|
||||
/* color: unset; */
|
||||
}
|
||||
.search-popover-item > span {
|
||||
min-width: 0;
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
}
|
||||
}
|
||||
.ancestors-indicator:not([hidden]) {
|
||||
animation: slide-up 0.3s both ease-out 0.3s;
|
||||
animation: slide-up-smooth 0.3s both var(--spring-timing-funtion) 0.3s;
|
||||
}
|
||||
.ancestors-indicator[hidden] {
|
||||
opacity: 0;
|
||||
|
|
|
@ -184,6 +184,15 @@ function StatusPage(params) {
|
|||
);
|
||||
}
|
||||
|
||||
function StatusParent(props) {
|
||||
const { linkable, to, onClick, ...restProps } = props;
|
||||
return linkable ? (
|
||||
<Link class="status-link" to={to} onClick={onClick} {...restProps} />
|
||||
) : (
|
||||
<div class="status-focus" tabIndex={0} {...restProps} />
|
||||
);
|
||||
}
|
||||
|
||||
function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const mediaParam = searchParams.get('media');
|
||||
|
@ -429,7 +438,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
};
|
||||
|
||||
useEffect(initContext, [id, masto]);
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (!statuses.length) return;
|
||||
console.debug('STATUSES', statuses);
|
||||
const scrollPosition = scrollPositions[id];
|
||||
|
@ -705,24 +714,8 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
weight,
|
||||
} = status;
|
||||
const isHero = statusID === id;
|
||||
// const StatusParent = useCallback(
|
||||
// (props) =>
|
||||
// isThread || thread || ancestor ? (
|
||||
// <Link
|
||||
// class="status-link"
|
||||
// to={
|
||||
// instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`
|
||||
// }
|
||||
// onClick={() => {
|
||||
// resetScrollPosition(statusID);
|
||||
// }}
|
||||
// {...props}
|
||||
// />
|
||||
// ) : (
|
||||
// <div class="status-focus" tabIndex={0} {...props} />
|
||||
// ),
|
||||
// [isThread, thread],
|
||||
// );
|
||||
const isLinkable = isThread || ancestor;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={statusID}
|
||||
|
@ -808,25 +801,42 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
)}
|
||||
</>
|
||||
) : (
|
||||
// <StatusParent>
|
||||
<Link
|
||||
class="status-link"
|
||||
<StatusParent
|
||||
linkable={isLinkable}
|
||||
to={instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`}
|
||||
onClick={() => {
|
||||
resetScrollPosition(statusID);
|
||||
}}
|
||||
>
|
||||
<InView
|
||||
skip={i !== 0 || !ancestor}
|
||||
threshold={0.5}
|
||||
onChange={(inView) => {
|
||||
queueMicrotask(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setReachTopPost(inView);
|
||||
{/* <Link
|
||||
class="status-link"
|
||||
to={instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`}
|
||||
onClick={() => {
|
||||
resetScrollPosition(statusID);
|
||||
}}
|
||||
> */}
|
||||
{i === 0 && ancestor ? (
|
||||
<InView
|
||||
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>
|
||||
) : (
|
||||
<Status
|
||||
statusID={statusID}
|
||||
instance={instance}
|
||||
|
@ -836,7 +846,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
onMediaClick={handleMediaClick}
|
||||
onStatusLinkClick={handleStatusLinkClick}
|
||||
/>
|
||||
</InView>
|
||||
)}
|
||||
{ancestor && repliesCount > 1 && (
|
||||
<div class="replies-link">
|
||||
<Icon icon="comment2" />{' '}
|
||||
|
@ -853,8 +863,8 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
</span>
|
||||
</div>
|
||||
)} */}
|
||||
{/* </StatusParent> */}
|
||||
</Link>
|
||||
</StatusParent>
|
||||
// </Link>
|
||||
)}
|
||||
{descendant && replies?.length > 0 && (
|
||||
<SubComments
|
||||
|
@ -864,6 +874,10 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
level={1}
|
||||
accWeight={weight}
|
||||
openAll={totalDescendants.current < SUBCOMMENTS_OPEN_ALL_LIMIT}
|
||||
parentLink={{
|
||||
to: instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`,
|
||||
onClick: () => resetScrollPosition(statusID),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{uiState === 'loading' &&
|
||||
|
@ -932,6 +946,11 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
return ids.map((id) => statusKey(id, instance));
|
||||
}, [showMore, statuses, limit, instance]);
|
||||
|
||||
const statusesList = useMemo(
|
||||
() => statuses.slice(0, limit).map(renderStatus),
|
||||
[statuses, limit, renderStatus],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex="-1"
|
||||
|
@ -1176,7 +1195,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
uiState === 'loading' ? 'loading' : ''
|
||||
}`}
|
||||
>
|
||||
{statuses.slice(0, limit).map(renderStatus)}
|
||||
{statusesList}
|
||||
{showMore > 0 && (
|
||||
<li>
|
||||
<button
|
||||
|
@ -1244,6 +1263,7 @@ function SubComments({
|
|||
level,
|
||||
accWeight,
|
||||
openAll,
|
||||
parentLink,
|
||||
}) {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
|
@ -1330,34 +1350,49 @@ function SubComments({
|
|||
/>
|
||||
))}
|
||||
</span>
|
||||
<b>
|
||||
<span title={replies.length}>{shortenNumber(replies.length)}</span>{' '}
|
||||
repl
|
||||
{replies.length === 1 ? 'y' : 'ies'}
|
||||
</b>
|
||||
{!sameCount && totalComments > 1 && (
|
||||
<>
|
||||
{' '}
|
||||
·{' '}
|
||||
<span>
|
||||
<span title={totalComments}>{shortenNumber(totalComments)}</span>{' '}
|
||||
comment
|
||||
{totalComments === 1 ? '' : 's'}
|
||||
</span>
|
||||
</>
|
||||
<span class="replies-counts">
|
||||
<b>
|
||||
<span title={replies.length}>{shortenNumber(replies.length)}</span>{' '}
|
||||
repl
|
||||
{replies.length === 1 ? 'y' : 'ies'}
|
||||
</b>
|
||||
{!sameCount && totalComments > 1 && (
|
||||
<>
|
||||
{' '}
|
||||
·{' '}
|
||||
<span>
|
||||
<span title={totalComments}>
|
||||
{shortenNumber(totalComments)}
|
||||
</span>{' '}
|
||||
comment
|
||||
{totalComments === 1 ? '' : 's'}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<Icon icon="chevron-down" class="replies-summary-chevron" />
|
||||
{!!parentLink && (
|
||||
<Link
|
||||
class="replies-parent-link"
|
||||
to={parentLink.to}
|
||||
onClick={parentLink.onClick}
|
||||
title="View post with its replies"
|
||||
>
|
||||
»
|
||||
</Link>
|
||||
)}
|
||||
</summary>
|
||||
<ul>
|
||||
{replies.map((r) => (
|
||||
<li key={r.id}>
|
||||
<Link
|
||||
{/* <Link
|
||||
class="status-link"
|
||||
to={instance ? `/${instance}/s/${r.id}` : `/s/${r.id}`}
|
||||
onClick={() => {
|
||||
resetScrollPosition(r.id);
|
||||
}}
|
||||
>
|
||||
{/* <div class="status-focus" tabIndex={0}> */}
|
||||
> */}
|
||||
<div class="status-focus" tabIndex={0}>
|
||||
<Status
|
||||
statusID={r.id}
|
||||
instance={instance}
|
||||
|
@ -1374,8 +1409,8 @@ function SubComments({
|
|||
</span>
|
||||
</div>
|
||||
)}
|
||||
{/* </div> */}
|
||||
</Link>
|
||||
</div>
|
||||
{/* </Link> */}
|
||||
{r.replies?.length && (
|
||||
<SubComments
|
||||
instance={instance}
|
||||
|
@ -1383,6 +1418,12 @@ function SubComments({
|
|||
level={level + 1}
|
||||
accWeight={!open ? r.weight : totalWeight}
|
||||
openAll={openAll}
|
||||
parentLink={{
|
||||
to: instance ? `/${instance}/s/${r.id}` : `/s/${r.id}`,
|
||||
onClick: () => {
|
||||
resetScrollPosition(r.id);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { deepEqual } from 'fast-equals';
|
||||
import { proxy, subscribe } from 'valtio';
|
||||
import { subscribeKey } from 'valtio/utils';
|
||||
|
||||
|
@ -49,6 +50,7 @@ const states = proxy({
|
|||
showKeyboardShortcutsHelp: false,
|
||||
showGenericAccounts: false,
|
||||
showMediaAlt: false,
|
||||
showEmbedModal: false,
|
||||
// Shortcuts
|
||||
shortcuts: [],
|
||||
// Settings
|
||||
|
@ -150,6 +152,7 @@ export function hideAllModals() {
|
|||
states.showKeyboardShortcutsHelp = false;
|
||||
states.showGenericAccounts = false;
|
||||
states.showMediaAlt = false;
|
||||
states.showEmbedModal = false;
|
||||
}
|
||||
|
||||
export function statusKey(id, instance) {
|
||||
|
@ -178,6 +181,7 @@ export function saveStatus(status, instance, opts) {
|
|||
if (!status) return;
|
||||
const oldStatus = getStatus(status.id, instance);
|
||||
if (!override && oldStatus) return;
|
||||
if (deepEqual(status, oldStatus)) return;
|
||||
queueMicrotask(() => {
|
||||
const key = statusKey(status.id, instance);
|
||||
if (oldStatus?._pinned) status._pinned = oldStatus._pinned;
|
||||
|
|
Ładowanie…
Reference in New Issue