mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-03-25 23:24:44 +01:00
commit
0cf7d683ee
19 changed files with 194 additions and 95 deletions
8
.github/workflows/prodtag.yml
vendored
8
.github/workflows/prodtag.yml
vendored
|
@ -14,15 +14,17 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: production
|
ref: production
|
||||||
- run: git tag "`date +%Y.%m.%d`.`git rev-parse --short HEAD`" $(git rev-parse HEAD)
|
# - run: git tag "`date +%Y.%m.%d`.`git rev-parse --short HEAD`" $(git rev-parse HEAD)
|
||||||
- run: git push --tags
|
# - run: git push --tags
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- run: npm ci && npm run build
|
- run: npm ci && npm run build
|
||||||
- run: cd dist && zip -r ../phanpy-dist.zip . && cd ..
|
- run: cd dist && zip -r ../phanpy-dist.zip . && cd ..
|
||||||
|
- id: tag_name
|
||||||
|
run: echo ::set-output name=tag_name::$(date +%Y.%m.%d).$(git rev-parse --short HEAD)
|
||||||
- uses: softprops/action-gh-release@v1
|
- uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ steps.tag_name.outputs.tag_name }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: phanpy-dist.zip
|
files: phanpy-dist.zip
|
||||||
|
|
|
@ -179,12 +179,12 @@ self.addEventListener('notificationclick', (event) => {
|
||||||
console.log('NOTIFICATION CLICK navigate', url);
|
console.log('NOTIFICATION CLICK navigate', url);
|
||||||
if (bestClient) {
|
if (bestClient) {
|
||||||
console.log('NOTIFICATION CLICK postMessage', bestClient);
|
console.log('NOTIFICATION CLICK postMessage', bestClient);
|
||||||
|
bestClient.focus();
|
||||||
bestClient.postMessage?.({
|
bestClient.postMessage?.({
|
||||||
type: 'notification',
|
type: 'notification',
|
||||||
id: tag,
|
id: tag,
|
||||||
accessToken: access_token,
|
accessToken: access_token,
|
||||||
});
|
});
|
||||||
bestClient.focus();
|
|
||||||
} else {
|
} else {
|
||||||
console.log('NOTIFICATION CLICK openWindow', url);
|
console.log('NOTIFICATION CLICK openWindow', url);
|
||||||
await self.clients.openWindow(url);
|
await self.clients.openWindow(url);
|
||||||
|
|
20
src/app.css
20
src/app.css
|
@ -676,6 +676,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding-block: 16px !important;
|
padding-block: 16px !important;
|
||||||
|
|
||||||
|
.avatars-bunch > .avatar:not(:first-child) {
|
||||||
|
margin-left: -4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.timeline .show-more:hover {
|
.timeline .show-more:hover {
|
||||||
filter: none !important;
|
filter: none !important;
|
||||||
|
@ -2116,6 +2120,13 @@ ul.link-list li a .icon {
|
||||||
transparent
|
transparent
|
||||||
);
|
);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
transition: opacity 0.3s ease-out;
|
||||||
|
|
||||||
|
&.loading,
|
||||||
|
.loading > & {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.filter-bar.centered {
|
.filter-bar.centered {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -2133,14 +2144,19 @@ ul.link-list li a .icon {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
transition: all 0.3s ease-out;
|
transition: border-color 0.3s ease-out;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.filter-bar > a:is(:hover, :focus) {
|
.filter-bar > a:focus-visible {
|
||||||
border-color: var(--link-light-color);
|
border-color: var(--link-light-color);
|
||||||
}
|
}
|
||||||
|
@media (hover: hover) {
|
||||||
|
.filter-bar > a:hover {
|
||||||
|
border-color: var(--link-light-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
.filter-bar > a > * {
|
.filter-bar > a > * {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -550,11 +550,13 @@ function AccountInfo({
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
to={accountLink}
|
to={accountLink}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showAccount = false;
|
// states.showAccount = false;
|
||||||
|
setTimeout(() => {
|
||||||
states.showGenericAccounts = {
|
states.showGenericAccounts = {
|
||||||
heading: 'Followers',
|
heading: 'Followers',
|
||||||
fetchAccounts: fetchFollowers,
|
fetchAccounts: fetchFollowers,
|
||||||
};
|
};
|
||||||
|
}, 0);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!!familiarFollowers.length && (
|
{!!familiarFollowers.length && (
|
||||||
|
@ -581,11 +583,13 @@ function AccountInfo({
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
to={accountLink}
|
to={accountLink}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showAccount = false;
|
// states.showAccount = false;
|
||||||
|
setTimeout(() => {
|
||||||
states.showGenericAccounts = {
|
states.showGenericAccounts = {
|
||||||
heading: 'Following',
|
heading: 'Following',
|
||||||
fetchAccounts: fetchFollowing,
|
fetchAccounts: fetchFollowing,
|
||||||
};
|
};
|
||||||
|
}, 0);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span title={followingCount}>
|
<span title={followingCount}>
|
||||||
|
@ -597,13 +601,13 @@ function AccountInfo({
|
||||||
<LinkOrDiv
|
<LinkOrDiv
|
||||||
class="insignificant"
|
class="insignificant"
|
||||||
to={accountLink}
|
to={accountLink}
|
||||||
onClick={
|
// onClick={
|
||||||
standalone
|
// standalone
|
||||||
? undefined
|
// ? undefined
|
||||||
: () => {
|
// : () => {
|
||||||
hideAllModals();
|
// hideAllModals();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
>
|
>
|
||||||
<span title={statusesCount}>
|
<span title={statusesCount}>
|
||||||
{shortenNumber(statusesCount)}
|
{shortenNumber(statusesCount)}
|
||||||
|
@ -626,9 +630,9 @@ function AccountInfo({
|
||||||
<LinkOrDiv
|
<LinkOrDiv
|
||||||
to={accountLink}
|
to={accountLink}
|
||||||
class="account-metadata-box"
|
class="account-metadata-box"
|
||||||
onClick={() => {
|
// onClick={() => {
|
||||||
states.showAccount = false;
|
// states.showAccount = false;
|
||||||
}}
|
// }}
|
||||||
>
|
>
|
||||||
<div class="shazam-container">
|
<div class="shazam-container">
|
||||||
<div class="shazam-container-inner">
|
<div class="shazam-container-inner">
|
||||||
|
@ -1511,7 +1515,7 @@ function PrivateNoteSheet({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<header>
|
<header>
|
||||||
<b>Private note for @{account?.acct}</b>
|
<b>Private note about @{account?.username || account?.acct}</b>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<form
|
<form
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
import useLocationChange from '../utils/useLocationChange';
|
||||||
|
|
||||||
import AccountInfo from './account-info';
|
import AccountInfo from './account-info';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
@ -16,17 +17,19 @@ function AccountSheet({ account, instance: propInstance, onClose }) {
|
||||||
}
|
}
|
||||||
}, [account]);
|
}, [account]);
|
||||||
|
|
||||||
|
useLocationChange(onClose);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="sheet"
|
class="sheet"
|
||||||
onClick={(e) => {
|
// onClick={(e) => {
|
||||||
const accountBlock = e.target.closest('.account-block');
|
// const accountBlock = e.target.closest('.account-block');
|
||||||
if (accountBlock) {
|
// if (accountBlock) {
|
||||||
onClose({
|
// onClose({
|
||||||
destination: 'account-statuses',
|
// destination: 'account-statuses',
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}}
|
// }}
|
||||||
>
|
>
|
||||||
{!!onClose && (
|
{!!onClose && (
|
||||||
<button type="button" class="sheet-close outer" onClick={onClose}>
|
<button type="button" class="sheet-close outer" onClick={onClose}>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { InView } from 'react-intersection-observer';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
import useLocationChange from '../utils/useLocationChange';
|
||||||
|
|
||||||
import AccountBlock from './account-block';
|
import AccountBlock from './account-block';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
@ -16,6 +17,8 @@ export default function GenericAccounts({ onClose = () => {} }) {
|
||||||
const [accounts, setAccounts] = useState([]);
|
const [accounts, setAccounts] = useState([]);
|
||||||
const [showMore, setShowMore] = useState(false);
|
const [showMore, setShowMore] = useState(false);
|
||||||
|
|
||||||
|
useLocationChange(onClose);
|
||||||
|
|
||||||
if (!snapStates.showGenericAccounts) {
|
if (!snapStates.showGenericAccounts) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ import './loader.css';
|
||||||
|
|
||||||
function Loader({ abrupt, hidden, ...props }) {
|
function Loader({ abrupt, hidden, ...props }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<span
|
||||||
{...props}
|
{...props}
|
||||||
class={`loader-container ${abrupt ? 'abrupt' : ''} ${
|
class={`loader-container ${abrupt ? 'abrupt' : ''} ${
|
||||||
hidden ? 'hidden' : ''
|
hidden ? 'hidden' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div class="loader" />
|
<span class="loader" />
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ function Media({
|
||||||
enabled: pinchZoomEnabled,
|
enabled: pinchZoomEnabled,
|
||||||
draggableUnZoomed: false,
|
draggableUnZoomed: false,
|
||||||
inertiaFriction: 0.9,
|
inertiaFriction: 0.9,
|
||||||
|
doubleTapZoomOutOnMaxScale: true,
|
||||||
containerProps: {
|
containerProps: {
|
||||||
className: 'media-zoom',
|
className: 'media-zoom',
|
||||||
style: {
|
style: {
|
||||||
|
|
|
@ -117,9 +117,10 @@ export default function Modals() {
|
||||||
instance={snapStates.showAccount?.instance}
|
instance={snapStates.showAccount?.instance}
|
||||||
onClose={({ destination } = {}) => {
|
onClose={({ destination } = {}) => {
|
||||||
states.showAccount = false;
|
states.showAccount = false;
|
||||||
if (destination) {
|
// states.showGenericAccounts = false;
|
||||||
states.showAccounts = false;
|
// if (destination) {
|
||||||
}
|
// states.showAccounts = false;
|
||||||
|
// }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
.nav-menu section:last-child {
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
margin-bottom: -8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 23em) {
|
@media (min-width: 23em) {
|
||||||
.nav-menu {
|
.nav-menu {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -8,6 +14,7 @@
|
||||||
'left right';
|
'left right';
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 22em;
|
width: 22em;
|
||||||
|
max-width: calc(100vw - 16px);
|
||||||
}
|
}
|
||||||
.nav-menu .top-menu {
|
.nav-menu .top-menu {
|
||||||
grid-area: top;
|
grid-area: top;
|
||||||
|
@ -27,7 +34,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.nav-menu section:last-child {
|
.nav-menu section:last-child {
|
||||||
background-color: var(--bg-faded-color);
|
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
to right,
|
to right,
|
||||||
var(--divider-color) 1px,
|
var(--divider-color) 1px,
|
||||||
|
@ -45,6 +51,15 @@
|
||||||
animation: phanpying 0.2s ease-in-out both;
|
animation: phanpying 0.2s ease-in-out both;
|
||||||
border-top-right-radius: inherit;
|
border-top-right-radius: inherit;
|
||||||
border-bottom-right-radius: inherit;
|
border-bottom-right-radius: inherit;
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.divider-grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: auto;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.nav-menu section:last-child > .szh-menu__divider:first-child {
|
.nav-menu section:last-child > .szh-menu__divider:first-child {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -249,6 +249,7 @@ function NavMenu(props) {
|
||||||
<Icon icon="block" size="l" />
|
<Icon icon="block" size="l" />
|
||||||
Blocked users…
|
Blocked users…
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuDivider className="divider-grow" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showKeyboardShortcutsHelp = true;
|
states.showKeyboardShortcutsHelp = true;
|
||||||
|
@ -263,7 +264,7 @@ function NavMenu(props) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="shortcut" size="l" />{' '}
|
<Icon icon="shortcut" size="l" />{' '}
|
||||||
<span>Shortcuts Settings…</span>
|
<span>Shortcuts / Columns…</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -249,8 +249,7 @@ function ShortcutsSettings({ onClose }) {
|
||||||
</h2>
|
</h2>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<p>
|
<p>Specify a list of shortcuts that'll appear as:</p>
|
||||||
Specify a list of shortcuts that'll appear as:
|
|
||||||
<div class="shortcuts-view-mode">
|
<div class="shortcuts-view-mode">
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
|
@ -298,7 +297,6 @@ function ShortcutsSettings({ onClose }) {
|
||||||
<option value="multi-column">Multi-column</option>
|
<option value="multi-column">Multi-column</option>
|
||||||
<option value="tab-menu-bar">Tab/Menu bar </option>
|
<option value="tab-menu-bar">Tab/Menu bar </option>
|
||||||
</select> */}
|
</select> */}
|
||||||
</p>
|
|
||||||
{/* <p>
|
{/* <p>
|
||||||
<details>
|
<details>
|
||||||
<summary class="insignificant">
|
<summary class="insignificant">
|
||||||
|
|
|
@ -579,7 +579,11 @@ function Status({
|
||||||
try {
|
try {
|
||||||
const done = await confirmBoostStatus();
|
const done = await confirmBoostStatus();
|
||||||
if (!isSizeLarge && done) {
|
if (!isSizeLarge && done) {
|
||||||
showToast(reblogged ? 'Unboosted' : 'Boosted');
|
showToast(
|
||||||
|
reblogged
|
||||||
|
? `Unboosted @${username || acct}'s post`
|
||||||
|
: `Boosted @${username || acct}'s post`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}}
|
}}
|
||||||
|
@ -597,7 +601,11 @@ function Status({
|
||||||
try {
|
try {
|
||||||
favouriteStatus();
|
favouriteStatus();
|
||||||
if (!isSizeLarge) {
|
if (!isSizeLarge) {
|
||||||
showToast(favourited ? 'Unfavourited' : 'Favourited');
|
showToast(
|
||||||
|
favourited
|
||||||
|
? `Unfavourited @${username || acct}'s post`
|
||||||
|
: `Favourited @${username || acct}'s post`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}}
|
}}
|
||||||
|
@ -621,7 +629,11 @@ function Status({
|
||||||
try {
|
try {
|
||||||
bookmarkStatus();
|
bookmarkStatus();
|
||||||
if (!isSizeLarge) {
|
if (!isSizeLarge) {
|
||||||
showToast(bookmarked ? 'Unbookmarked' : 'Bookmarked');
|
showToast(
|
||||||
|
bookmarked
|
||||||
|
? `Unbookmarked @${username || acct}'s post`
|
||||||
|
: `Bookmarked @${username || acct}'s post`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}}
|
}}
|
||||||
|
@ -829,7 +841,11 @@ function Status({
|
||||||
try {
|
try {
|
||||||
favouriteStatus();
|
favouriteStatus();
|
||||||
if (!isSizeLarge) {
|
if (!isSizeLarge) {
|
||||||
showToast(favourited ? 'Unfavourited' : 'Favourited');
|
showToast(
|
||||||
|
favourited
|
||||||
|
? `Unfavourited @${username || acct}'s post`
|
||||||
|
: `Favourited @${username || acct}'s post`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
|
@ -843,7 +859,11 @@ function Status({
|
||||||
try {
|
try {
|
||||||
bookmarkStatus();
|
bookmarkStatus();
|
||||||
if (!isSizeLarge) {
|
if (!isSizeLarge) {
|
||||||
showToast(bookmarked ? 'Unbookmarked' : 'Bookmarked');
|
showToast(
|
||||||
|
bookmarked
|
||||||
|
? `Unbookmarked @${username || acct}'s post`
|
||||||
|
: `Bookmarked @${username || acct}'s post`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
|
@ -858,7 +878,11 @@ function Status({
|
||||||
try {
|
try {
|
||||||
const done = await confirmBoostStatus();
|
const done = await confirmBoostStatus();
|
||||||
if (!isSizeLarge && done) {
|
if (!isSizeLarge && done) {
|
||||||
showToast(reblogged ? 'Unboosted' : 'Boosted');
|
showToast(
|
||||||
|
reblogged
|
||||||
|
? `Unboosted @${username || acct}'s post`
|
||||||
|
: `Boosted @${username || acct}'s post`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -334,7 +334,13 @@ function Timeline({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
{!!timelineStart && <div class="timeline-start">{timelineStart}</div>}
|
{!!timelineStart && (
|
||||||
|
<div
|
||||||
|
class={`timeline-start ${uiState === 'loading' ? 'loading' : ''}`}
|
||||||
|
>
|
||||||
|
{timelineStart}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!!items.length ? (
|
{!!items.length ? (
|
||||||
<>
|
<>
|
||||||
<ul class="timeline">
|
<ul class="timeline">
|
||||||
|
|
|
@ -258,7 +258,7 @@ function AccountStatuses() {
|
||||||
useItemID
|
useItemID
|
||||||
boostsCarousel={snapStates.settings.boostsCarousel}
|
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||||
timelineStart={TimelineStart}
|
timelineStart={TimelineStart}
|
||||||
refresh={excludeReplies + excludeBoosts + tagged + media}
|
refresh={[excludeReplies, excludeBoosts, tagged, media].toString()}
|
||||||
headerEnd={
|
headerEnd={
|
||||||
<Menu2
|
<Menu2
|
||||||
portal
|
portal
|
||||||
|
|
|
@ -152,7 +152,7 @@ function Search(props) {
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{!!q && (
|
{!!q && (
|
||||||
<div class="filter-bar">
|
<div class={`filter-bar ${uiState === 'loading' ? 'loading' : ''}`}>
|
||||||
{!!type && (
|
{!!type && (
|
||||||
<Link to={`/search${q ? `?q=${encodeURIComponent(q)}` : ''}`}>
|
<Link to={`/search${q ? `?q=${encodeURIComponent(q)}` : ''}`}>
|
||||||
‹ All
|
‹ All
|
||||||
|
|
|
@ -1062,7 +1062,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
onClick={() => setLimit((l) => l + LIMIT)}
|
onClick={() => setLimit((l) => l + LIMIT)}
|
||||||
style={{ marginBlockEnd: '6em' }}
|
style={{ marginBlockEnd: '6em' }}
|
||||||
>
|
>
|
||||||
<div class="ib">
|
<div class="ib avatars-bunch">
|
||||||
{/* show avatars for first 5 statuses */}
|
{/* show avatars for first 5 statuses */}
|
||||||
{statuses.slice(limit, limit + 5).map((status) => (
|
{statuses.slice(limit, limit + 5).map((status) => (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import mem from './mem';
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
function getHTMLText(html) {
|
function getHTMLText(html) {
|
||||||
if (!html) return '';
|
if (!html) return '';
|
||||||
|
@ -10,4 +12,4 @@ function getHTMLText(html) {
|
||||||
return div.innerText.replace(/[\r\n]{3,}/g, '\n\n').trim();
|
return div.innerText.replace(/[\r\n]{3,}/g, '\n\n').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getHTMLText;
|
export default mem(getHTMLText);
|
||||||
|
|
23
src/utils/useLocationChange.js
Normal file
23
src/utils/useLocationChange.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { useEffect, useRef } from 'preact/hooks';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Hook that runs a callback when the location changes
|
||||||
|
// Won't run on the first render
|
||||||
|
|
||||||
|
export default function useLocationChange(fn) {
|
||||||
|
if (!fn) return;
|
||||||
|
const location = useLocation();
|
||||||
|
const currentLocationRef = useRef(location.pathname);
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log('location', {
|
||||||
|
// current: currentLocationRef.current,
|
||||||
|
// next: location.pathname,
|
||||||
|
// });
|
||||||
|
if (
|
||||||
|
currentLocationRef.current &&
|
||||||
|
location.pathname !== currentLocationRef.current
|
||||||
|
) {
|
||||||
|
fn?.();
|
||||||
|
}
|
||||||
|
}, [location.pathname, fn]);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue