mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-03-23 14:13:21 +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
|
||||
with:
|
||||
ref: production
|
||||
- run: git tag "`date +%Y.%m.%d`.`git rev-parse --short HEAD`" $(git rev-parse HEAD)
|
||||
- run: git push --tags
|
||||
# - run: git tag "`date +%Y.%m.%d`.`git rev-parse --short HEAD`" $(git rev-parse HEAD)
|
||||
# - run: git push --tags
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- run: npm ci && npm run build
|
||||
- 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
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
tag_name: ${{ steps.tag_name.outputs.tag_name }}
|
||||
generate_release_notes: true
|
||||
files: phanpy-dist.zip
|
||||
|
|
|
@ -179,12 +179,12 @@ self.addEventListener('notificationclick', (event) => {
|
|||
console.log('NOTIFICATION CLICK navigate', url);
|
||||
if (bestClient) {
|
||||
console.log('NOTIFICATION CLICK postMessage', bestClient);
|
||||
bestClient.focus();
|
||||
bestClient.postMessage?.({
|
||||
type: 'notification',
|
||||
id: tag,
|
||||
accessToken: access_token,
|
||||
});
|
||||
bestClient.focus();
|
||||
} else {
|
||||
console.log('NOTIFICATION CLICK 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;
|
||||
border-radius: 0;
|
||||
padding-block: 16px !important;
|
||||
|
||||
.avatars-bunch > .avatar:not(:first-child) {
|
||||
margin-left: -4px;
|
||||
}
|
||||
}
|
||||
.timeline .show-more:hover {
|
||||
filter: none !important;
|
||||
|
@ -2116,6 +2120,13 @@ ul.link-list li a .icon {
|
|||
transparent
|
||||
);
|
||||
align-items: center;
|
||||
transition: opacity 0.3s ease-out;
|
||||
|
||||
&.loading,
|
||||
.loading > & {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.filter-bar.centered {
|
||||
justify-content: center;
|
||||
|
@ -2133,14 +2144,19 @@ ul.link-list li a .icon {
|
|||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.3s ease-out;
|
||||
transition: border-color 0.3s ease-out;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.filter-bar > a:is(:hover, :focus) {
|
||||
.filter-bar > a:focus-visible {
|
||||
border-color: var(--link-light-color);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.filter-bar > a:hover {
|
||||
border-color: var(--link-light-color);
|
||||
}
|
||||
}
|
||||
.filter-bar > a > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
|
@ -550,11 +550,13 @@ function AccountInfo({
|
|||
tabIndex={0}
|
||||
to={accountLink}
|
||||
onClick={() => {
|
||||
states.showAccount = false;
|
||||
states.showGenericAccounts = {
|
||||
heading: 'Followers',
|
||||
fetchAccounts: fetchFollowers,
|
||||
};
|
||||
// states.showAccount = false;
|
||||
setTimeout(() => {
|
||||
states.showGenericAccounts = {
|
||||
heading: 'Followers',
|
||||
fetchAccounts: fetchFollowers,
|
||||
};
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
{!!familiarFollowers.length && (
|
||||
|
@ -581,11 +583,13 @@ function AccountInfo({
|
|||
tabIndex={0}
|
||||
to={accountLink}
|
||||
onClick={() => {
|
||||
states.showAccount = false;
|
||||
states.showGenericAccounts = {
|
||||
heading: 'Following',
|
||||
fetchAccounts: fetchFollowing,
|
||||
};
|
||||
// states.showAccount = false;
|
||||
setTimeout(() => {
|
||||
states.showGenericAccounts = {
|
||||
heading: 'Following',
|
||||
fetchAccounts: fetchFollowing,
|
||||
};
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<span title={followingCount}>
|
||||
|
@ -597,13 +601,13 @@ function AccountInfo({
|
|||
<LinkOrDiv
|
||||
class="insignificant"
|
||||
to={accountLink}
|
||||
onClick={
|
||||
standalone
|
||||
? undefined
|
||||
: () => {
|
||||
hideAllModals();
|
||||
}
|
||||
}
|
||||
// onClick={
|
||||
// standalone
|
||||
// ? undefined
|
||||
// : () => {
|
||||
// hideAllModals();
|
||||
// }
|
||||
// }
|
||||
>
|
||||
<span title={statusesCount}>
|
||||
{shortenNumber(statusesCount)}
|
||||
|
@ -626,9 +630,9 @@ function AccountInfo({
|
|||
<LinkOrDiv
|
||||
to={accountLink}
|
||||
class="account-metadata-box"
|
||||
onClick={() => {
|
||||
states.showAccount = false;
|
||||
}}
|
||||
// onClick={() => {
|
||||
// states.showAccount = false;
|
||||
// }}
|
||||
>
|
||||
<div class="shazam-container">
|
||||
<div class="shazam-container-inner">
|
||||
|
@ -1511,7 +1515,7 @@ function PrivateNoteSheet({
|
|||
</button>
|
||||
)}
|
||||
<header>
|
||||
<b>Private note for @{account?.acct}</b>
|
||||
<b>Private note about @{account?.username || account?.acct}</b>
|
||||
</header>
|
||||
<main>
|
||||
<form
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useEffect } from 'preact/hooks';
|
|||
|
||||
import { api } from '../utils/api';
|
||||
import states from '../utils/states';
|
||||
import useLocationChange from '../utils/useLocationChange';
|
||||
|
||||
import AccountInfo from './account-info';
|
||||
import Icon from './icon';
|
||||
|
@ -16,17 +17,19 @@ function AccountSheet({ account, instance: propInstance, onClose }) {
|
|||
}
|
||||
}, [account]);
|
||||
|
||||
useLocationChange(onClose);
|
||||
|
||||
return (
|
||||
<div
|
||||
class="sheet"
|
||||
onClick={(e) => {
|
||||
const accountBlock = e.target.closest('.account-block');
|
||||
if (accountBlock) {
|
||||
onClose({
|
||||
destination: 'account-statuses',
|
||||
});
|
||||
}
|
||||
}}
|
||||
// onClick={(e) => {
|
||||
// const accountBlock = e.target.closest('.account-block');
|
||||
// if (accountBlock) {
|
||||
// onClose({
|
||||
// destination: 'account-statuses',
|
||||
// });
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
{!!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 states from '../utils/states';
|
||||
import useLocationChange from '../utils/useLocationChange';
|
||||
|
||||
import AccountBlock from './account-block';
|
||||
import Icon from './icon';
|
||||
|
@ -16,6 +17,8 @@ export default function GenericAccounts({ onClose = () => {} }) {
|
|||
const [accounts, setAccounts] = useState([]);
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
useLocationChange(onClose);
|
||||
|
||||
if (!snapStates.showGenericAccounts) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@ import './loader.css';
|
|||
|
||||
function Loader({ abrupt, hidden, ...props }) {
|
||||
return (
|
||||
<div
|
||||
<span
|
||||
{...props}
|
||||
class={`loader-container ${abrupt ? 'abrupt' : ''} ${
|
||||
hidden ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
<div class="loader" />
|
||||
</div>
|
||||
<span class="loader" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ function Media({
|
|||
enabled: pinchZoomEnabled,
|
||||
draggableUnZoomed: false,
|
||||
inertiaFriction: 0.9,
|
||||
doubleTapZoomOutOnMaxScale: true,
|
||||
containerProps: {
|
||||
className: 'media-zoom',
|
||||
style: {
|
||||
|
|
|
@ -117,9 +117,10 @@ export default function Modals() {
|
|||
instance={snapStates.showAccount?.instance}
|
||||
onClose={({ destination } = {}) => {
|
||||
states.showAccount = false;
|
||||
if (destination) {
|
||||
states.showAccounts = false;
|
||||
}
|
||||
// states.showGenericAccounts = false;
|
||||
// if (destination) {
|
||||
// states.showAccounts = false;
|
||||
// }
|
||||
}}
|
||||
/>
|
||||
</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) {
|
||||
.nav-menu {
|
||||
display: grid;
|
||||
|
@ -8,6 +14,7 @@
|
|||
'left right';
|
||||
padding: 0;
|
||||
width: 22em;
|
||||
max-width: calc(100vw - 16px);
|
||||
}
|
||||
.nav-menu .top-menu {
|
||||
grid-area: top;
|
||||
|
@ -27,7 +34,6 @@
|
|||
}
|
||||
}
|
||||
.nav-menu section:last-child {
|
||||
background-color: var(--bg-faded-color);
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
var(--divider-color) 1px,
|
||||
|
@ -45,6 +51,15 @@
|
|||
animation: phanpying 0.2s ease-in-out both;
|
||||
border-top-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 {
|
||||
display: none;
|
||||
|
|
|
@ -249,6 +249,7 @@ function NavMenu(props) {
|
|||
<Icon icon="block" size="l" />
|
||||
Blocked users…
|
||||
</MenuItem>
|
||||
<MenuDivider className="divider-grow" />
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
states.showKeyboardShortcutsHelp = true;
|
||||
|
@ -263,7 +264,7 @@ function NavMenu(props) {
|
|||
}}
|
||||
>
|
||||
<Icon icon="shortcut" size="l" />{' '}
|
||||
<span>Shortcuts Settings…</span>
|
||||
<span>Shortcuts / Columns…</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
|
|
|
@ -249,46 +249,45 @@ function ShortcutsSettings({ onClose }) {
|
|||
</h2>
|
||||
</header>
|
||||
<main>
|
||||
<p>
|
||||
Specify a list of shortcuts that'll appear as:
|
||||
<div class="shortcuts-view-mode">
|
||||
{[
|
||||
{
|
||||
value: 'float-button',
|
||||
label: 'Floating button',
|
||||
imgURL: floatingButtonUrl,
|
||||
},
|
||||
{
|
||||
value: 'tab-menu-bar',
|
||||
label: 'Tab/Menu bar',
|
||||
imgURL: tabMenuBarUrl,
|
||||
},
|
||||
{
|
||||
value: 'multi-column',
|
||||
label: 'Multi-column',
|
||||
imgURL: multiColumnUrl,
|
||||
},
|
||||
].map(({ value, label, imgURL }) => (
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="shortcuts-view-mode"
|
||||
value={value}
|
||||
checked={
|
||||
snapStates.settings.shortcutsViewMode === value ||
|
||||
(value === 'float-button' &&
|
||||
!snapStates.settings.shortcutsViewMode)
|
||||
}
|
||||
onChange={(e) => {
|
||||
states.settings.shortcutsViewMode = e.target.value;
|
||||
}}
|
||||
/>{' '}
|
||||
<img src={imgURL} alt="" width="80" height="58" />{' '}
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{/* <select
|
||||
<p>Specify a list of shortcuts that'll appear as:</p>
|
||||
<div class="shortcuts-view-mode">
|
||||
{[
|
||||
{
|
||||
value: 'float-button',
|
||||
label: 'Floating button',
|
||||
imgURL: floatingButtonUrl,
|
||||
},
|
||||
{
|
||||
value: 'tab-menu-bar',
|
||||
label: 'Tab/Menu bar',
|
||||
imgURL: tabMenuBarUrl,
|
||||
},
|
||||
{
|
||||
value: 'multi-column',
|
||||
label: 'Multi-column',
|
||||
imgURL: multiColumnUrl,
|
||||
},
|
||||
].map(({ value, label, imgURL }) => (
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="shortcuts-view-mode"
|
||||
value={value}
|
||||
checked={
|
||||
snapStates.settings.shortcutsViewMode === value ||
|
||||
(value === 'float-button' &&
|
||||
!snapStates.settings.shortcutsViewMode)
|
||||
}
|
||||
onChange={(e) => {
|
||||
states.settings.shortcutsViewMode = e.target.value;
|
||||
}}
|
||||
/>{' '}
|
||||
<img src={imgURL} alt="" width="80" height="58" />{' '}
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{/* <select
|
||||
value={snapStates.settings.shortcutsViewMode || 'float-button'}
|
||||
onChange={(e) => {
|
||||
states.settings.shortcutsViewMode = e.target.value;
|
||||
|
@ -298,7 +297,6 @@ function ShortcutsSettings({ onClose }) {
|
|||
<option value="multi-column">Multi-column</option>
|
||||
<option value="tab-menu-bar">Tab/Menu bar </option>
|
||||
</select> */}
|
||||
</p>
|
||||
{/* <p>
|
||||
<details>
|
||||
<summary class="insignificant">
|
||||
|
|
|
@ -579,7 +579,11 @@ function Status({
|
|||
try {
|
||||
const done = await confirmBoostStatus();
|
||||
if (!isSizeLarge && done) {
|
||||
showToast(reblogged ? 'Unboosted' : 'Boosted');
|
||||
showToast(
|
||||
reblogged
|
||||
? `Unboosted @${username || acct}'s post`
|
||||
: `Boosted @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
}}
|
||||
|
@ -597,7 +601,11 @@ function Status({
|
|||
try {
|
||||
favouriteStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(favourited ? 'Unfavourited' : 'Favourited');
|
||||
showToast(
|
||||
favourited
|
||||
? `Unfavourited @${username || acct}'s post`
|
||||
: `Favourited @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
}}
|
||||
|
@ -621,7 +629,11 @@ function Status({
|
|||
try {
|
||||
bookmarkStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(bookmarked ? 'Unbookmarked' : 'Bookmarked');
|
||||
showToast(
|
||||
bookmarked
|
||||
? `Unbookmarked @${username || acct}'s post`
|
||||
: `Bookmarked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
}}
|
||||
|
@ -829,7 +841,11 @@ function Status({
|
|||
try {
|
||||
favouriteStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(favourited ? 'Unfavourited' : 'Favourited');
|
||||
showToast(
|
||||
favourited
|
||||
? `Unfavourited @${username || acct}'s post`
|
||||
: `Favourited @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
@ -843,7 +859,11 @@ function Status({
|
|||
try {
|
||||
bookmarkStatus();
|
||||
if (!isSizeLarge) {
|
||||
showToast(bookmarked ? 'Unbookmarked' : 'Bookmarked');
|
||||
showToast(
|
||||
bookmarked
|
||||
? `Unbookmarked @${username || acct}'s post`
|
||||
: `Bookmarked @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
@ -858,7 +878,11 @@ function Status({
|
|||
try {
|
||||
const done = await confirmBoostStatus();
|
||||
if (!isSizeLarge && done) {
|
||||
showToast(reblogged ? 'Unboosted' : 'Boosted');
|
||||
showToast(
|
||||
reblogged
|
||||
? `Unboosted @${username || acct}'s post`
|
||||
: `Boosted @${username || acct}'s post`,
|
||||
);
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
|
|
|
@ -334,7 +334,13 @@ function Timeline({
|
|||
</button>
|
||||
)}
|
||||
</header>
|
||||
{!!timelineStart && <div class="timeline-start">{timelineStart}</div>}
|
||||
{!!timelineStart && (
|
||||
<div
|
||||
class={`timeline-start ${uiState === 'loading' ? 'loading' : ''}`}
|
||||
>
|
||||
{timelineStart}
|
||||
</div>
|
||||
)}
|
||||
{!!items.length ? (
|
||||
<>
|
||||
<ul class="timeline">
|
||||
|
|
|
@ -258,7 +258,7 @@ function AccountStatuses() {
|
|||
useItemID
|
||||
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||
timelineStart={TimelineStart}
|
||||
refresh={excludeReplies + excludeBoosts + tagged + media}
|
||||
refresh={[excludeReplies, excludeBoosts, tagged, media].toString()}
|
||||
headerEnd={
|
||||
<Menu2
|
||||
portal
|
||||
|
|
|
@ -152,7 +152,7 @@ function Search(props) {
|
|||
</header>
|
||||
<main>
|
||||
{!!q && (
|
||||
<div class="filter-bar">
|
||||
<div class={`filter-bar ${uiState === 'loading' ? 'loading' : ''}`}>
|
||||
{!!type && (
|
||||
<Link to={`/search${q ? `?q=${encodeURIComponent(q)}` : ''}`}>
|
||||
‹ All
|
||||
|
|
|
@ -1062,7 +1062,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
onClick={() => setLimit((l) => l + LIMIT)}
|
||||
style={{ marginBlockEnd: '6em' }}
|
||||
>
|
||||
<div class="ib">
|
||||
<div class="ib avatars-bunch">
|
||||
{/* show avatars for first 5 statuses */}
|
||||
{statuses.slice(limit, limit + 5).map((status) => (
|
||||
<Avatar
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import mem from './mem';
|
||||
|
||||
const div = document.createElement('div');
|
||||
function getHTMLText(html) {
|
||||
if (!html) return '';
|
||||
|
@ -10,4 +12,4 @@ function getHTMLText(html) {
|
|||
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