kopia lustrzana https://github.com/cheeaun/phanpy
Porównaj commity
32 Commity
9ad60db82d
...
6139d97271
Autor | SHA1 | Data |
---|---|---|
Helge | 6139d97271 | |
Lim Chee Aun | 55ad6500bc | |
Lim Chee Aun | f4b95d254c | |
Lim Chee Aun | effbe189e1 | |
Lim Chee Aun | 44e910b8c9 | |
Lim Chee Aun | a68dccd7cf | |
Lim Chee Aun | 9a6364a674 | |
Lim Chee Aun | e2f39596f0 | |
Lim Chee Aun | 701b9e99b3 | |
Lim Chee Aun | 294ab2bf00 | |
Lim Chee Aun | 304ce5a3e8 | |
Lim Chee Aun | 57390a291b | |
Lim Chee Aun | cd5920114f | |
Lim Chee Aun | 06c6360cae | |
Lim Chee Aun | afdfdb86da | |
Lim Chee Aun | 6f8f3e4fd0 | |
Lim Chee Aun | 342ff20986 | |
Lim Chee Aun | 94996d098e | |
Lim Chee Aun | c286562ee8 | |
Lim Chee Aun | 5babdc9d63 | |
Lim Chee Aun | 260bb8746d | |
Lim Chee Aun | 7be620808f | |
Lim Chee Aun | df3aca70fa | |
Lim Chee Aun | ec65163c89 | |
Lim Chee Aun | 6f22ec3842 | |
Lim Chee Aun | 2faf9b4c20 | |
Lim Chee Aun | 501e43207b | |
Lim Chee Aun | e782cc0dde | |
Lim Chee Aun | aefda31c2a | |
Lim Chee Aun | 9285a0ba9a | |
Chee Aun | 7fb56d9f6c | |
steve mookie kong | f7c69e56e9 |
|
@ -179,6 +179,9 @@ Available variables:
|
||||||
- May specify a self-hosted Lingva instance, powered by either [lingva-translate](https://github.com/thedaviddelta/lingva-translate) or [lingva-api](https://github.com/cheeaun/lingva-api)
|
- May specify a self-hosted Lingva instance, powered by either [lingva-translate](https://github.com/thedaviddelta/lingva-translate) or [lingva-api](https://github.com/cheeaun/lingva-api)
|
||||||
- List of fallback instances hard-coded in `/.env`
|
- List of fallback instances hard-coded in `/.env`
|
||||||
- [↗️ List of lingva-translate instances](https://github.com/thedaviddelta/lingva-translate?tab=readme-ov-file#instances)
|
- [↗️ List of lingva-translate instances](https://github.com/thedaviddelta/lingva-translate?tab=readme-ov-file#instances)
|
||||||
|
- `PHANPY_IMG_ALT_API_URL` (optional, no defaults):
|
||||||
|
- API endpoint for self-hosted instance of [img-alt-api](https://github.com/cheeaun/img-alt-api).
|
||||||
|
- If provided, a setting will appear for users to enable the image description generator in the composer. Disabled by default.
|
||||||
- `PHANPY_GIPHY_API_KEY` (optional, no defaults):
|
- `PHANPY_GIPHY_API_KEY` (optional, no defaults):
|
||||||
- API key for [GIPHY](https://developers.giphy.com/). See [API docs](https://developers.giphy.com/docs/api/).
|
- API key for [GIPHY](https://developers.giphy.com/). See [API docs](https://developers.giphy.com/docs/api/).
|
||||||
- If provided, a setting will appear for users to enable the GIF picker in the composer. Disabled by default.
|
- If provided, a setting will appear for users to enable the GIF picker in the composer. Disabled by default.
|
||||||
|
@ -205,6 +208,7 @@ These are self-hosted by other wonderful folks.
|
||||||
- [phanpy.hear-me.social](https://phanpy.hear-me.social) by [@admin@hear-me.social](https://hear-me.social/@admin)
|
- [phanpy.hear-me.social](https://phanpy.hear-me.social) by [@admin@hear-me.social](https://hear-me.social/@admin)
|
||||||
- [phanpy.fulda.social](https://phanpy.fulda.social) by [@Ganneff@fulda.social](https://fulda.social/@Ganneff)
|
- [phanpy.fulda.social](https://phanpy.fulda.social) by [@Ganneff@fulda.social](https://fulda.social/@Ganneff)
|
||||||
- [phanpy.crmbl.uk](https://phanpy.crmbl.uk) by [@snail@crmbl.uk](https://mstdn.crmbl.uk/@snail)
|
- [phanpy.crmbl.uk](https://phanpy.crmbl.uk) by [@snail@crmbl.uk](https://mstdn.crmbl.uk/@snail)
|
||||||
|
- [halo.mookiesplace.com](https://halo.mookiesplace.com) by [@mookie@mookiesplace.com](https://mookiesplace.com/@mookie)
|
||||||
|
|
||||||
> Note: Add yours by creating a pull request.
|
> Note: Add yours by creating a pull request.
|
||||||
|
|
||||||
|
|
14
src/app.css
14
src/app.css
|
@ -306,13 +306,20 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
.timeline {
|
.timeline {
|
||||||
> li:not(.timeline-item-carousel, .timeline-item-container) {
|
> li:not(.timeline-item-carousel, .timeline-item-container) {
|
||||||
&:has(.status-media-first) {
|
&:has(.status-media-first) {
|
||||||
width: fit-content;
|
@media (min-width: 40em) {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: min(480px, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
max-width: min(480px, 100%);
|
|
||||||
margin-inline: auto !important;
|
margin-inline: auto !important;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-block: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
&:has(.skeleton) {
|
&:has(.skeleton) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -1910,7 +1917,8 @@ body > .szh-menu-container {
|
||||||
/* two columns only */
|
/* two columns only */
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
.szh-menu .menu-horizontal:has(> .szh-menu__item:only-child) {
|
.szh-menu .menu-horizontal:has(> .szh-menu__item:only-child),
|
||||||
|
.szh-menu .menu-horizontal:has(> .szh-menu__submenu:only-child) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
.szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):first-child,
|
.szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):first-child,
|
||||||
|
|
|
@ -53,7 +53,7 @@ import { getAccessToken } from './utils/auth';
|
||||||
import focusDeck from './utils/focus-deck';
|
import focusDeck from './utils/focus-deck';
|
||||||
import states, { initStates, statusKey } from './utils/states';
|
import states, { initStates, statusKey } from './utils/states';
|
||||||
import store from './utils/store';
|
import store from './utils/store';
|
||||||
import { getCurrentAccount } from './utils/store-utils';
|
import { getCurrentAccount, setCurrentAccountID } from './utils/store-utils';
|
||||||
import './utils/toast-alert';
|
import './utils/toast-alert';
|
||||||
|
|
||||||
window.__STATES__ = states;
|
window.__STATES__ = states;
|
||||||
|
@ -338,7 +338,7 @@ function App() {
|
||||||
window.__IGNORE_GET_ACCOUNT_ERROR__ = true;
|
window.__IGNORE_GET_ACCOUNT_ERROR__ = true;
|
||||||
const account = getCurrentAccount();
|
const account = getCurrentAccount();
|
||||||
if (account) {
|
if (account) {
|
||||||
store.session.set('currentAccount', account.info.id);
|
setCurrentAccountID(account.info.id);
|
||||||
const { client } = api({ account });
|
const { client } = api({ account });
|
||||||
const { instance } = client;
|
const { instance } = client;
|
||||||
// console.log('masto', masto);
|
// console.log('masto', masto);
|
||||||
|
|
|
@ -22,7 +22,8 @@ import shortenNumber from '../utils/shorten-number';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import states, { hideAllModals } from '../utils/states';
|
import states, { hideAllModals } from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
import { updateAccount } from '../utils/store-utils';
|
import { getCurrentAccountID, updateAccount } from '../utils/store-utils';
|
||||||
|
import supports from '../utils/supports';
|
||||||
|
|
||||||
import AccountBlock from './account-block';
|
import AccountBlock from './account-block';
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
|
@ -198,10 +199,7 @@ function AccountInfo({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSelf = useMemo(
|
const isSelf = useMemo(() => id === getCurrentAccountID(), [id]);
|
||||||
() => id === store.session.get('currentAccount'),
|
|
||||||
[id],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const infoHasEssentials = !!(
|
const infoHasEssentials = !!(
|
||||||
|
@ -920,7 +918,7 @@ function RelatedActions({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (info) {
|
if (info) {
|
||||||
const currentAccount = store.session.get('currentAccount');
|
const currentAccount = getCurrentAccountID();
|
||||||
let currentID;
|
let currentID;
|
||||||
(async () => {
|
(async () => {
|
||||||
if (sameInstance && authenticated) {
|
if (sameInstance && authenticated) {
|
||||||
|
@ -1094,16 +1092,18 @@ function RelatedActions({
|
||||||
<Icon icon="translate" />
|
<Icon icon="translate" />
|
||||||
<span>Translate bio</span>
|
<span>Translate bio</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
{supports('@mastodon/profile-private-note') && (
|
||||||
onClick={() => {
|
<MenuItem
|
||||||
setShowPrivateNoteModal(true);
|
onClick={() => {
|
||||||
}}
|
setShowPrivateNoteModal(true);
|
||||||
>
|
}}
|
||||||
<Icon icon="pencil" />
|
>
|
||||||
<span>
|
<Icon icon="pencil" />
|
||||||
{privateNote ? 'Edit private note' : 'Add private note'}
|
<span>
|
||||||
</span>
|
{privateNote ? 'Edit private note' : 'Add private note'}
|
||||||
</MenuItem>
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{following && !!relationship && (
|
{following && !!relationship && (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@ -1452,19 +1452,22 @@ function RelatedActions({
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{currentAuthenticated && isSelf && standalone && (
|
{currentAuthenticated &&
|
||||||
<>
|
isSelf &&
|
||||||
<MenuDivider />
|
standalone &&
|
||||||
<MenuItem
|
supports('@mastodon/profile-edit') && (
|
||||||
onClick={() => {
|
<>
|
||||||
setShowEditProfile(true);
|
<MenuDivider />
|
||||||
}}
|
<MenuItem
|
||||||
>
|
onClick={() => {
|
||||||
<Icon icon="pencil" />
|
setShowEditProfile(true);
|
||||||
<span>Edit profile</span>
|
}}
|
||||||
</MenuItem>
|
>
|
||||||
</>
|
<Icon icon="pencil" />
|
||||||
)}
|
<span>Edit profile</span>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{import.meta.env.DEV && currentAuthenticated && isSelf && (
|
{import.meta.env.DEV && currentAuthenticated && isSelf && (
|
||||||
<>
|
<>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
|
|
|
@ -310,7 +310,7 @@
|
||||||
|
|
||||||
#compose-container .form-visibility-direct {
|
#compose-container .form-visibility-direct {
|
||||||
--yellow-stripes: repeating-linear-gradient(
|
--yellow-stripes: repeating-linear-gradient(
|
||||||
-45deg,
|
135deg,
|
||||||
var(--reply-to-faded-color),
|
var(--reply-to-faded-color),
|
||||||
var(--reply-to-faded-color) 10px,
|
var(--reply-to-faded-color) 10px,
|
||||||
var(--reply-to-faded-color) 10px,
|
var(--reply-to-faded-color) 10px,
|
||||||
|
|
|
@ -124,7 +124,7 @@ const MENTION_RE = new RegExp(
|
||||||
|
|
||||||
// AI-generated, all other regexes are too complicated
|
// AI-generated, all other regexes are too complicated
|
||||||
const HASHTAG_RE = new RegExp(
|
const HASHTAG_RE = new RegExp(
|
||||||
`(^|[^=\\/\\w])(#[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?)(?![\\/\\w])`,
|
`(^|[^=\\/\\w])(#[a-z0-9_]+([a-z0-9_.]+[a-z0-9_]+)?)(?![\\/\\w])`,
|
||||||
'ig',
|
'ig',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -988,7 +988,11 @@ function Compose({
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
newStatus = await masto.v1.statuses.create(params, {
|
newStatus = await masto.v1.statuses.create(params, {
|
||||||
idempotencyKey: UID.current,
|
requestInit: {
|
||||||
|
headers: {
|
||||||
|
'Idempotency-Key': UID.current,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// If idempotency key fails, try again without it
|
// If idempotency key fails, try again without it
|
||||||
|
|
|
@ -8,6 +8,7 @@ import FilterContext from '../utils/filter-context';
|
||||||
import { isFiltered } from '../utils/filters';
|
import { isFiltered } from '../utils/filters';
|
||||||
import states, { statusKey } from '../utils/states';
|
import states, { statusKey } from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
import { getCurrentAccountID } from '../utils/store-utils';
|
||||||
|
|
||||||
import Media from './media';
|
import Media from './media';
|
||||||
|
|
||||||
|
@ -88,7 +89,7 @@ function MediaPost({
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentAccount = useMemo(() => {
|
const currentAccount = useMemo(() => {
|
||||||
return store.session.get('currentAccount');
|
return getCurrentAccountID();
|
||||||
}, []);
|
}, []);
|
||||||
const isSelf = useMemo(() => {
|
const isSelf = useMemo(() => {
|
||||||
return currentAccount && currentAccount === accountId;
|
return currentAccount && currentAccount === accountId;
|
||||||
|
|
|
@ -341,13 +341,15 @@ function Media({
|
||||||
if (!hasDimensions) {
|
if (!hasDimensions) {
|
||||||
const $media = e.target.closest('.media');
|
const $media = e.target.closest('.media');
|
||||||
if ($media) {
|
if ($media) {
|
||||||
|
const { naturalWidth, naturalHeight } = e.target;
|
||||||
$media.dataset.orientation =
|
$media.dataset.orientation =
|
||||||
e.target.naturalWidth > e.target.naturalHeight
|
naturalWidth > naturalHeight ? 'landscape' : 'portrait';
|
||||||
? 'landscape'
|
$media.style.setProperty('--width', `${naturalWidth}px`);
|
||||||
: 'portrait';
|
$media.style.setProperty(
|
||||||
$media.style['--width'] = `${e.target.naturalWidth}px`;
|
'--height',
|
||||||
$media.style['--height'] = `${e.target.naturalHeight}px`;
|
`${naturalHeight}px`,
|
||||||
$media.style.aspectRatio = `${e.target.naturalWidth}/${e.target.naturalHeight}`;
|
);
|
||||||
|
$media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -511,19 +513,25 @@ function Media({
|
||||||
height={height}
|
height={height}
|
||||||
data-orientation={orientation}
|
data-orientation={orientation}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
onLoad={(e) => {
|
onLoad={(e) => {
|
||||||
if (!hasDimensions) {
|
if (!hasDimensions) {
|
||||||
const $media = e.target.closest('.media');
|
const $media = e.target.closest('.media');
|
||||||
if ($media) {
|
if ($media) {
|
||||||
|
const { naturalHeight, naturalWidth } = e.target;
|
||||||
$media.dataset.orientation =
|
$media.dataset.orientation =
|
||||||
e.target.naturalWidth > e.target.naturalHeight
|
naturalWidth > naturalHeight
|
||||||
? 'landscape'
|
? 'landscape'
|
||||||
: 'portrait';
|
: 'portrait';
|
||||||
$media.style['--width'] = `${e.target.naturalWidth}px`;
|
$media.style.setProperty(
|
||||||
$media.style[
|
'--width',
|
||||||
'--height'
|
`${naturalWidth}px`,
|
||||||
] = `${e.target.naturalHeight}px`;
|
);
|
||||||
$media.style.aspectRatio = `${e.target.naturalWidth}/${e.target.naturalHeight}`;
|
$media.style.setProperty(
|
||||||
|
'--height',
|
||||||
|
`${naturalHeight}px`,
|
||||||
|
);
|
||||||
|
$media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { getLists } from '../utils/lists';
|
||||||
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
import { getCurrentAccountID } from '../utils/store-utils';
|
||||||
|
import supports from '../utils/supports';
|
||||||
|
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
@ -24,9 +26,8 @@ function NavMenu(props) {
|
||||||
const [currentAccount, moreThanOneAccount] = useMemo(() => {
|
const [currentAccount, moreThanOneAccount] = useMemo(() => {
|
||||||
const accounts = store.local.getJSON('accounts') || [];
|
const accounts = store.local.getJSON('accounts') || [];
|
||||||
const acc =
|
const acc =
|
||||||
accounts.find(
|
accounts.find((account) => account.info.id === getCurrentAccountID()) ||
|
||||||
(account) => account.info.id === store.session.get('currentAccount'),
|
accounts[0];
|
||||||
) || accounts[0];
|
|
||||||
return [acc, accounts.length > 1];
|
return [acc, accounts.length > 1];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -83,8 +84,10 @@ function NavMenu(props) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportsLists = supports('@mastodon/lists');
|
||||||
const [lists, setLists] = useState([]);
|
const [lists, setLists] = useState([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!supportsLists) return;
|
||||||
if (menuState === 'open') {
|
if (menuState === 'open') {
|
||||||
getLists().then(setLists);
|
getLists().then(setLists);
|
||||||
}
|
}
|
||||||
|
@ -186,9 +189,11 @@ function NavMenu(props) {
|
||||||
<Icon icon="history2" size="l" />
|
<Icon icon="history2" size="l" />
|
||||||
<span>Catch-up</span>
|
<span>Catch-up</span>
|
||||||
</MenuLink>
|
</MenuLink>
|
||||||
<MenuLink to="/mentions">
|
{supports('@mastodon/mentions') && (
|
||||||
<Icon icon="at" size="l" /> <span>Mentions</span>
|
<MenuLink to="/mentions">
|
||||||
</MenuLink>
|
<Icon icon="at" size="l" /> <span>Mentions</span>
|
||||||
|
</MenuLink>
|
||||||
|
)}
|
||||||
<MenuLink to="/notifications">
|
<MenuLink to="/notifications">
|
||||||
<Icon icon="notification" size="l" /> <span>Notifications</span>
|
<Icon icon="notification" size="l" /> <span>Notifications</span>
|
||||||
{snapStates.notificationsShowNew && (
|
{snapStates.notificationsShowNew && (
|
||||||
|
@ -232,10 +237,12 @@ function NavMenu(props) {
|
||||||
)}
|
)}
|
||||||
</SubMenu2>
|
</SubMenu2>
|
||||||
) : (
|
) : (
|
||||||
<MenuLink to="/l">
|
supportsLists && (
|
||||||
<Icon icon="list" size="l" />
|
<MenuLink to="/l">
|
||||||
<span>Lists</span>
|
<Icon icon="list" size="l" />
|
||||||
</MenuLink>
|
<span>Lists</span>
|
||||||
|
</MenuLink>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
<MenuLink to="/b">
|
<MenuLink to="/b">
|
||||||
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
|
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
|
||||||
|
@ -260,10 +267,12 @@ function NavMenu(props) {
|
||||||
<span>Followed Hashtags</span>
|
<span>Followed Hashtags</span>
|
||||||
</MenuLink>
|
</MenuLink>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuLink to="/ft">
|
{supports('@mastodon/filters') && (
|
||||||
<Icon icon="filters" size="l" />
|
<MenuLink to="/ft">
|
||||||
Filters
|
<Icon icon="filters" size="l" />
|
||||||
</MenuLink>
|
Filters
|
||||||
|
</MenuLink>
|
||||||
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showGenericAccounts = {
|
states.showGenericAccounts = {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { memo } from 'preact/compat';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
import states, { statusKey } from '../utils/states';
|
import states, { statusKey } from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
import { getCurrentAccountID } from '../utils/store-utils';
|
||||||
import useTruncated from '../utils/useTruncated';
|
import useTruncated from '../utils/useTruncated';
|
||||||
|
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
|
@ -132,7 +133,7 @@ function Notification({
|
||||||
const actualStatus = status?.reblog || status;
|
const actualStatus = status?.reblog || status;
|
||||||
const actualStatusID = actualStatus?.id;
|
const actualStatusID = actualStatus?.id;
|
||||||
|
|
||||||
const currentAccount = store.session.get('currentAccount');
|
const currentAccount = getCurrentAccountID();
|
||||||
const isSelf = currentAccount === account?.id;
|
const isSelf = currentAccount === account?.id;
|
||||||
const isVoted = status?.poll?.voted;
|
const isVoted = status?.poll?.voted;
|
||||||
const isReplyToOthers =
|
const isReplyToOthers =
|
||||||
|
|
|
@ -19,6 +19,7 @@ import pmem from '../utils/pmem';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
import { getCurrentAccountID } from '../utils/store-utils';
|
||||||
|
|
||||||
import AsyncText from './AsyncText';
|
import AsyncText from './AsyncText';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
@ -787,7 +788,7 @@ function ImportExport({ shortcuts, onClose }) {
|
||||||
disabled={importUIState === 'cloud-downloading'}
|
disabled={importUIState === 'cloud-downloading'}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setImportUIState('cloud-downloading');
|
setImportUIState('cloud-downloading');
|
||||||
const currentAccount = store.session.get('currentAccount');
|
const currentAccount = getCurrentAccountID();
|
||||||
showToast(
|
showToast(
|
||||||
'Downloading saved shortcuts from instance server…',
|
'Downloading saved shortcuts from instance server…',
|
||||||
);
|
);
|
||||||
|
@ -1043,7 +1044,7 @@ function ImportExport({ shortcuts, onClose }) {
|
||||||
disabled={importUIState === 'cloud-uploading'}
|
disabled={importUIState === 'cloud-uploading'}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setImportUIState('cloud-uploading');
|
setImportUIState('cloud-uploading');
|
||||||
const currentAccount = store.session.get('currentAccount');
|
const currentAccount = getCurrentAccountID();
|
||||||
try {
|
try {
|
||||||
const relationships =
|
const relationships =
|
||||||
await masto.v1.accounts.relationships.fetch({
|
await masto.v1.accounts.relationships.fetch({
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
}
|
}
|
||||||
.visibility-direct {
|
.visibility-direct {
|
||||||
--yellow-stripes: repeating-linear-gradient(
|
--yellow-stripes: repeating-linear-gradient(
|
||||||
-45deg,
|
135deg,
|
||||||
var(--reply-to-faded-color),
|
var(--reply-to-faded-color),
|
||||||
var(--reply-to-faded-color) 10px,
|
var(--reply-to-faded-color) 10px,
|
||||||
var(--reply-to-faded-color) 10px,
|
var(--reply-to-faded-color) 10px,
|
||||||
|
@ -365,6 +365,10 @@
|
||||||
background-image: var(--yellow-stripes);
|
background-image: var(--yellow-stripes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-pre-meta + & {
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
transition: opacity 1s ease-out;
|
transition: opacity 1s ease-out;
|
||||||
|
@ -1320,11 +1324,22 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.skeleton .media-first-container {
|
.status.skeleton .media-first-container {
|
||||||
min-height: 3em;
|
min-height: 320px;
|
||||||
background-color: var(--outline-color);
|
background-color: var(--outline-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes media-carousel-slide {
|
||||||
|
0% {
|
||||||
|
transform: translateX(calc(var(--dots-count, 1) * 2.5px));
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(calc(var(--dots-count, 1) * -2.5px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status-media-first {
|
.status-media-first {
|
||||||
|
timeline-scope: --media-carousel;
|
||||||
|
|
||||||
.meta-name {
|
.meta-name {
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
transition: opacity 0.5s ease-in-out;
|
transition: opacity 0.5s ease-in-out;
|
||||||
|
@ -1356,76 +1371,20 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
||||||
.media-first-spoiler-button {
|
.media-first-spoiler-button {
|
||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-first-container {
|
.media-first-container {
|
||||||
margin-top: 8px;
|
|
||||||
display: flex;
|
|
||||||
max-height: 80vh;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
scroll-snap-type: x mandatory;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
user-select: none;
|
|
||||||
margin-inline: -16px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
scrollbar-width: none;
|
margin-top: 8px;
|
||||||
/* border: var(--hairline-width) solid var(--outline-color);
|
margin-inline: -16px;
|
||||||
border-inline-width: 0;
|
|
||||||
background-color: var(--bg-faded-color); */
|
|
||||||
|
|
||||||
@media (min-width: 40em) {
|
@media (min-width: 40em) {
|
||||||
margin-inline: 0;
|
margin-inline: 0;
|
||||||
/* border-radius: 4px; */
|
|
||||||
border-inline-width: var(--hairline-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .media-first-item {
|
|
||||||
scroll-snap-align: center;
|
|
||||||
scroll-snap-stop: always;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:not(:only-child) {
|
|
||||||
background-color: var(--bg-blur-color);
|
|
||||||
box-shadow: inset 0 0 0 var(--hairline-width) var(--outline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.media {
|
|
||||||
/* background-color: var(--average-color, var(--bg-faded-color)); */
|
|
||||||
width: var(--width);
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
min-height: var(--min-dimension);
|
|
||||||
/* max-height: min(var(--height), 80vh); */
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
object-fit: scale-down;
|
|
||||||
animation: none;
|
|
||||||
|
|
||||||
&:not([data-loaded='true']) {
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-carousel-controls {
|
.media-carousel-controls {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 100%;
|
position: absolute;
|
||||||
position: sticky;
|
inset: 0;
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -1443,7 +1402,7 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transition: opacity 1.5s ease-in-out;
|
transition: opacity 1s ease-in-out 0.3s;
|
||||||
border: var(--hairline-width) solid var(--media-outline-color);
|
border: var(--hairline-width) solid var(--media-outline-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1477,6 +1436,76 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-first-carousel {
|
||||||
|
display: flex;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
user-select: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* border: var(--hairline-width) solid var(--outline-color);
|
||||||
|
border-inline-width: 0;
|
||||||
|
background-color: var(--bg-faded-color); */
|
||||||
|
box-shadow: 0 0 0 var(--hairline-width) var(--outline-color);
|
||||||
|
scroll-timeline: --media-carousel x;
|
||||||
|
|
||||||
|
@media (min-width: 40em) {
|
||||||
|
/* margin-inline: 0; */
|
||||||
|
/* border-radius: 4px; */
|
||||||
|
/* border-inline-width: var(--hairline-width); */
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .media-first-item {
|
||||||
|
scroll-snap-align: center;
|
||||||
|
scroll-snap-stop: always;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:not(:only-child) {
|
||||||
|
background-color: var(--bg-blur-color);
|
||||||
|
/* box-shadow: inset 0 0 0 var(--hairline-width) var(--outline-color); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.media {
|
||||||
|
/* background-color: var(--average-color, var(--bg-faded-color)); */
|
||||||
|
width: var(--width, 100%);
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
min-height: var(--min-dimension);
|
||||||
|
/* max-height: min(var(--height), 80vh); */
|
||||||
|
|
||||||
|
&:has(img:not([data-loaded='true'])) {
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: none;
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
object-fit: scale-down;
|
||||||
|
animation: none;
|
||||||
|
|
||||||
|
&:not([data-loaded='true']) {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
:is(:hover, :focus) > & .carousel-indexer {
|
:is(:hover, :focus) > & .carousel-indexer {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
@ -1489,6 +1518,11 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
|
||||||
|
@supports (animation-timeline: scroll()) {
|
||||||
|
animation: media-carousel-slide 1s linear both;
|
||||||
|
animation-timeline: --media-carousel;
|
||||||
|
}
|
||||||
|
|
||||||
.carousel-dot {
|
.carousel-dot {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
|
@ -1497,6 +1531,7 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
||||||
background-color: var(--text-color);
|
background-color: var(--text-color);
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash';
|
import { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash';
|
||||||
import { shallowEqual } from 'fast-equals';
|
import { shallowEqual } from 'fast-equals';
|
||||||
import prettify from 'html-prettify';
|
import prettify from 'html-prettify';
|
||||||
|
import { Fragment } from 'preact';
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
|
@ -55,6 +56,8 @@ import { speak, supportsTTS } from '../utils/speech';
|
||||||
import states, { getStatus, saveStatus, statusKey } from '../utils/states';
|
import states, { getStatus, saveStatus, statusKey } from '../utils/states';
|
||||||
import statusPeek from '../utils/status-peek';
|
import statusPeek from '../utils/status-peek';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
import { getCurrentAccountID } from '../utils/store-utils';
|
||||||
|
import supports from '../utils/supports';
|
||||||
import unfurlMastodonLink from '../utils/unfurl-link';
|
import unfurlMastodonLink from '../utils/unfurl-link';
|
||||||
import useTruncated from '../utils/useTruncated';
|
import useTruncated from '../utils/useTruncated';
|
||||||
import visibilityIconsMap from '../utils/visibility-icons-map';
|
import visibilityIconsMap from '../utils/visibility-icons-map';
|
||||||
|
@ -148,6 +151,12 @@ const PostContent = memo(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const SIZE_CLASS = {
|
||||||
|
s: 'small',
|
||||||
|
m: 'medium',
|
||||||
|
l: 'large',
|
||||||
|
};
|
||||||
|
|
||||||
function Status({
|
function Status({
|
||||||
statusID,
|
statusID,
|
||||||
status,
|
status,
|
||||||
|
@ -173,7 +182,11 @@ function Status({
|
||||||
}) {
|
}) {
|
||||||
if (skeleton) {
|
if (skeleton) {
|
||||||
return (
|
return (
|
||||||
<div class={`status skeleton ${mediaFirst ? 'status-media-first' : ''}`}>
|
<div
|
||||||
|
class={`status skeleton ${
|
||||||
|
mediaFirst ? 'status-media-first small' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{!mediaFirst && <Avatar size="xxl" />}
|
{!mediaFirst && <Avatar size="xxl" />}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
@ -256,7 +269,7 @@ function Status({
|
||||||
if (mediaFirst && hasMediaAttachments) size = 's';
|
if (mediaFirst && hasMediaAttachments) size = 's';
|
||||||
|
|
||||||
const currentAccount = useMemo(() => {
|
const currentAccount = useMemo(() => {
|
||||||
return store.session.get('currentAccount');
|
return getCurrentAccountID();
|
||||||
}, []);
|
}, []);
|
||||||
const isSelf = useMemo(() => {
|
const isSelf = useMemo(() => {
|
||||||
return currentAccount && currentAccount === accountId;
|
return currentAccount && currentAccount === accountId;
|
||||||
|
@ -394,8 +407,8 @@ function Status({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check followedTags
|
// Check followedTags
|
||||||
if (showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length) {
|
const FollowedTagsParent = useCallback(
|
||||||
return (
|
({ children }) => (
|
||||||
<div
|
<div
|
||||||
data-state-post-id={sKey}
|
data-state-post-id={sKey}
|
||||||
class="status-followed-tags"
|
class="status-followed-tags"
|
||||||
|
@ -413,19 +426,15 @@ function Status({
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Status
|
{children}
|
||||||
status={statusID ? null : status}
|
|
||||||
statusID={statusID ? status.id : null}
|
|
||||||
instance={instance}
|
|
||||||
size={size}
|
|
||||||
contentTextWeight={contentTextWeight}
|
|
||||||
readOnly={readOnly}
|
|
||||||
enableCommentHint
|
|
||||||
mediaFirst={mediaFirst}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
),
|
||||||
}
|
[sKey, instance, snapStates.statusFollowedTags[sKey]],
|
||||||
|
);
|
||||||
|
const StatusParent =
|
||||||
|
showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length
|
||||||
|
? FollowedTagsParent
|
||||||
|
: Fragment;
|
||||||
|
|
||||||
const isSizeLarge = size === 'l';
|
const isSizeLarge = size === 'l';
|
||||||
|
|
||||||
|
@ -639,6 +648,7 @@ function Status({
|
||||||
};
|
};
|
||||||
|
|
||||||
const bookmarkStatus = async () => {
|
const bookmarkStatus = async () => {
|
||||||
|
if (!supports('@mastodon/post-bookmark')) return;
|
||||||
if (!sameInstance || !authenticated) {
|
if (!sameInstance || !authenticated) {
|
||||||
alert(unauthInteractionErrorMessage);
|
alert(unauthInteractionErrorMessage);
|
||||||
return false;
|
return false;
|
||||||
|
@ -826,13 +836,15 @@ function Status({
|
||||||
: 'Like'}
|
: 'Like'}
|
||||||
</span>
|
</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
{supports('@mastodon/post-bookmark') && (
|
||||||
onClick={bookmarkStatusNotify}
|
<MenuItem
|
||||||
className={`menu-bookmark ${bookmarked ? 'checked' : ''}`}
|
onClick={bookmarkStatusNotify}
|
||||||
>
|
className={`menu-bookmark ${bookmarked ? 'checked' : ''}`}
|
||||||
<Icon icon="bookmark" />
|
>
|
||||||
<span>{bookmarked ? 'Unbookmark' : 'Bookmark'}</span>
|
<Icon icon="bookmark" />
|
||||||
</MenuItem>
|
<span>{bookmarked ? 'Unbookmark' : 'Bookmark'}</span>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -1076,16 +1088,18 @@ function Status({
|
||||||
)}
|
)}
|
||||||
{isSelf && (
|
{isSelf && (
|
||||||
<div class="menu-horizontal">
|
<div class="menu-horizontal">
|
||||||
<MenuItem
|
{supports('@mastodon/post-edit') && (
|
||||||
onClick={() => {
|
<MenuItem
|
||||||
states.showCompose = {
|
onClick={() => {
|
||||||
editStatus: status,
|
states.showCompose = {
|
||||||
};
|
editStatus: status,
|
||||||
}}
|
};
|
||||||
>
|
}}
|
||||||
<Icon icon="pencil" />
|
>
|
||||||
<span>Edit</span>
|
<Icon icon="pencil" />
|
||||||
</MenuItem>
|
<span>Edit</span>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{isSizeLarge && (
|
{isSizeLarge && (
|
||||||
<MenuConfirm
|
<MenuConfirm
|
||||||
subMenu
|
subMenu
|
||||||
|
@ -1366,7 +1380,7 @@ function Status({
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StatusParent>
|
||||||
{showReplyParent && !!(inReplyToId && inReplyToAccountId) && (
|
{showReplyParent && !!(inReplyToId && inReplyToAccountId) && (
|
||||||
<StatusCompact sKey={sKey} />
|
<StatusCompact sKey={sKey} />
|
||||||
)}
|
)}
|
||||||
|
@ -1394,11 +1408,7 @@ function Status({
|
||||||
? 'status-reply-to'
|
? 'status-reply-to'
|
||||||
: ''
|
: ''
|
||||||
} visibility-${visibility} ${_pinned ? 'status-pinned' : ''} ${
|
} visibility-${visibility} ${_pinned ? 'status-pinned' : ''} ${
|
||||||
{
|
SIZE_CLASS[size]
|
||||||
s: 'small',
|
|
||||||
m: 'medium',
|
|
||||||
l: 'large',
|
|
||||||
}[size]
|
|
||||||
} ${_deleted ? 'status-deleted' : ''} ${quoted ? 'status-card' : ''} ${
|
} ${_deleted ? 'status-deleted' : ''} ${quoted ? 'status-card' : ''} ${
|
||||||
isContextMenuOpen ? 'status-menu-open' : ''
|
isContextMenuOpen ? 'status-menu-open' : ''
|
||||||
} ${mediaFirst && hasMediaAttachments ? 'status-media-first' : ''}`}
|
} ${mediaFirst && hasMediaAttachments ? 'status-media-first' : ''}`}
|
||||||
|
@ -2159,16 +2169,18 @@ function Status({
|
||||||
onClick={favouriteStatus}
|
onClick={favouriteStatus}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="action">
|
{supports('@mastodon/post-bookmark') && (
|
||||||
<StatusButton
|
<div class="action">
|
||||||
checked={bookmarked}
|
<StatusButton
|
||||||
title={['Bookmark', 'Unbookmark']}
|
checked={bookmarked}
|
||||||
alt={['Bookmark', 'Bookmarked']}
|
title={['Bookmark', 'Unbookmark']}
|
||||||
class="bookmark-button"
|
alt={['Bookmark', 'Bookmarked']}
|
||||||
icon="bookmark"
|
class="bookmark-button"
|
||||||
onClick={bookmarkStatus}
|
icon="bookmark"
|
||||||
/>
|
onClick={bookmarkStatus}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Menu2
|
<Menu2
|
||||||
portal={{
|
portal={{
|
||||||
target:
|
target:
|
||||||
|
@ -2236,7 +2248,7 @@ function Status({
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
</article>
|
</article>
|
||||||
</>
|
</StatusParent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2280,16 +2292,18 @@ function MediaFirstContainer(props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="media-first-container" ref={carouselRef}>
|
<div class="media-first-container">
|
||||||
{mediaAttachments.map((media, i) => (
|
<div class="media-first-carousel" ref={carouselRef}>
|
||||||
<div class="media-first-item" key={media.id}>
|
{mediaAttachments.map((media, i) => (
|
||||||
<Media
|
<div class="media-first-item" key={media.id}>
|
||||||
media={media}
|
<Media
|
||||||
lang={language}
|
media={media}
|
||||||
to={`/${instance}/s/${postID}?media-only=${i + 1}`}
|
lang={language}
|
||||||
/>
|
to={`/${instance}/s/${postID}?media=${i + 1}`}
|
||||||
</div>
|
/>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
{moreThanOne && (
|
{moreThanOne && (
|
||||||
<div class="media-carousel-controls">
|
<div class="media-carousel-controls">
|
||||||
<div class="carousel-indexer">
|
<div class="carousel-indexer">
|
||||||
|
@ -2335,7 +2349,12 @@ function MediaFirstContainer(props) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{moreThanOne && (
|
{moreThanOne && (
|
||||||
<div class="media-carousel-dots">
|
<div
|
||||||
|
class="media-carousel-dots"
|
||||||
|
style={{
|
||||||
|
'--dots-count': mediaAttachments.length,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{mediaAttachments.map((media, i) => (
|
{mediaAttachments.map((media, i) => (
|
||||||
<span
|
<span
|
||||||
key={media.id}
|
key={media.id}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import pmem from '../utils/pmem';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import { saveStatus } from '../utils/states';
|
import { saveStatus } from '../utils/states';
|
||||||
|
import { isMediaFirstInstance } from '../utils/store-utils';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
@ -68,6 +69,8 @@ function AccountStatuses() {
|
||||||
searchOffsetRef.current = 0;
|
searchOffsetRef.current = 0;
|
||||||
}, allSearchParams);
|
}, allSearchParams);
|
||||||
|
|
||||||
|
const mediaFirst = useMemo(() => isMediaFirstInstance(), []);
|
||||||
|
|
||||||
const sameCurrentInstance = useMemo(
|
const sameCurrentInstance = useMemo(
|
||||||
() => instance === currentInstance,
|
() => instance === currentInstance,
|
||||||
[instance, currentInstance],
|
[instance, currentInstance],
|
||||||
|
@ -186,7 +189,7 @@ function AccountStatuses() {
|
||||||
limit: LIMIT,
|
limit: LIMIT,
|
||||||
exclude_replies: excludeReplies,
|
exclude_replies: excludeReplies,
|
||||||
exclude_reblogs: excludeBoosts,
|
exclude_reblogs: excludeBoosts,
|
||||||
only_media: media,
|
only_media: media || undefined,
|
||||||
tagged,
|
tagged,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -270,17 +273,21 @@ function AccountStatuses() {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
try {
|
// No need, because the whole filter bar is hidden
|
||||||
const featuredTags = await masto.v1.accounts
|
// TODO: Revisit this
|
||||||
.$select(id)
|
if (!mediaFirst) {
|
||||||
.featuredTags.list();
|
try {
|
||||||
console.log({ featuredTags });
|
const featuredTags = await masto.v1.accounts
|
||||||
setFeaturedTags(featuredTags);
|
.$select(id)
|
||||||
} catch (e) {
|
.featuredTags.list();
|
||||||
console.error(e);
|
console.log({ featuredTags });
|
||||||
|
setFeaturedTags(featuredTags);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [id]);
|
}, [id, mediaFirst]);
|
||||||
|
|
||||||
const { displayName, acct, emojis } = account || {};
|
const { displayName, acct, emojis } = account || {};
|
||||||
|
|
||||||
|
@ -299,95 +306,126 @@ function AccountStatuses() {
|
||||||
authenticated={authenticated}
|
authenticated={authenticated}
|
||||||
standalone
|
standalone
|
||||||
/>
|
/>
|
||||||
<div
|
{!mediaFirst && (
|
||||||
class="filter-bar"
|
<div
|
||||||
ref={filterBarRef}
|
class="filter-bar"
|
||||||
style={{
|
ref={filterBarRef}
|
||||||
position: 'relative',
|
style={{
|
||||||
}}
|
position: 'relative',
|
||||||
>
|
}}
|
||||||
{filtered ? (
|
>
|
||||||
|
{filtered ? (
|
||||||
|
<Link
|
||||||
|
to={`/${instance}/a/${id}`}
|
||||||
|
class="insignificant filter-clear"
|
||||||
|
title="Clear filters"
|
||||||
|
key="clear-filters"
|
||||||
|
>
|
||||||
|
<Icon icon="x" size="l" />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Icon icon="filter" class="insignificant" size="l" />
|
||||||
|
)}
|
||||||
<Link
|
<Link
|
||||||
to={`/${instance}/a/${id}`}
|
to={`/${instance}/a/${id}${excludeReplies ? '?replies=1' : ''}`}
|
||||||
class="insignificant filter-clear"
|
|
||||||
title="Clear filters"
|
|
||||||
key="clear-filters"
|
|
||||||
>
|
|
||||||
<Icon icon="x" size="l" />
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Icon icon="filter" class="insignificant" size="l" />
|
|
||||||
)}
|
|
||||||
<Link
|
|
||||||
to={`/${instance}/a/${id}${excludeReplies ? '?replies=1' : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (excludeReplies) {
|
|
||||||
showToast('Showing post with replies');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
class={excludeReplies ? '' : 'is-active'}
|
|
||||||
>
|
|
||||||
+ Replies
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to={`/${instance}/a/${id}${excludeBoosts ? '' : '?boosts=0'}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (!excludeBoosts) {
|
|
||||||
showToast('Showing posts without boosts');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
class={!excludeBoosts ? '' : 'is-active'}
|
|
||||||
>
|
|
||||||
- Boosts
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to={`/${instance}/a/${id}${media ? '' : '?media=1'}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (!media) {
|
|
||||||
showToast('Showing posts with media');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
class={media ? 'is-active' : ''}
|
|
||||||
>
|
|
||||||
Media
|
|
||||||
</Link>
|
|
||||||
{featuredTags.map((tag) => (
|
|
||||||
<Link
|
|
||||||
key={tag.id}
|
|
||||||
to={`/${instance}/a/${id}${
|
|
||||||
tagged === tag.name
|
|
||||||
? ''
|
|
||||||
: `?tagged=${encodeURIComponent(tag.name)}`
|
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (tagged !== tag.name) {
|
if (excludeReplies) {
|
||||||
showToast(`Showing posts tagged with #${tag.name}`);
|
showToast('Showing post with replies');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
class={tagged === tag.name ? 'is-active' : ''}
|
class={excludeReplies ? '' : 'is-active'}
|
||||||
>
|
>
|
||||||
<span>
|
+ Replies
|
||||||
<span class="more-insignificant">#</span>
|
|
||||||
{tag.name}
|
|
||||||
</span>
|
|
||||||
{
|
|
||||||
// The count differs based on instance 😅
|
|
||||||
}
|
|
||||||
{/* <span class="filter-count">{tag.statusesCount}</span> */}
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
<Link
|
||||||
{searchEnabled &&
|
to={`/${instance}/a/${id}${excludeBoosts ? '' : '?boosts=0'}`}
|
||||||
(supportsInputMonth ? (
|
onClick={() => {
|
||||||
<label class={`filter-field ${month ? 'is-active' : ''}`}>
|
if (!excludeBoosts) {
|
||||||
<Icon icon="month" size="l" />
|
showToast('Showing posts without boosts');
|
||||||
<input
|
}
|
||||||
type="month"
|
}}
|
||||||
|
class={!excludeBoosts ? '' : 'is-active'}
|
||||||
|
>
|
||||||
|
- Boosts
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to={`/${instance}/a/${id}${media ? '' : '?media=1'}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (!media) {
|
||||||
|
showToast('Showing posts with media');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
class={media ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
Media
|
||||||
|
</Link>
|
||||||
|
{featuredTags.map((tag) => (
|
||||||
|
<Link
|
||||||
|
key={tag.id}
|
||||||
|
to={`/${instance}/a/${id}${
|
||||||
|
tagged === tag.name
|
||||||
|
? ''
|
||||||
|
: `?tagged=${encodeURIComponent(tag.name)}`
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (tagged !== tag.name) {
|
||||||
|
showToast(`Showing posts tagged with #${tag.name}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
class={tagged === tag.name ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<span class="more-insignificant">#</span>
|
||||||
|
{tag.name}
|
||||||
|
</span>
|
||||||
|
{
|
||||||
|
// The count differs based on instance 😅
|
||||||
|
}
|
||||||
|
{/* <span class="filter-count">{tag.statusesCount}</span> */}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
{searchEnabled &&
|
||||||
|
(supportsInputMonth ? (
|
||||||
|
<label class={`filter-field ${month ? 'is-active' : ''}`}>
|
||||||
|
<Icon icon="month" size="l" />
|
||||||
|
<input
|
||||||
|
type="month"
|
||||||
|
disabled={!account?.acct}
|
||||||
|
value={month || ''}
|
||||||
|
min={MIN_YEAR_MONTH}
|
||||||
|
max={new Date().toISOString().slice(0, 7)}
|
||||||
|
onInput={(e) => {
|
||||||
|
const { value, validity } = e.currentTarget;
|
||||||
|
if (!validity.valid) return;
|
||||||
|
setSearchParams(
|
||||||
|
value
|
||||||
|
? {
|
||||||
|
month: value,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
);
|
||||||
|
const [year, month] = value.split('-');
|
||||||
|
const monthIndex = parseInt(month, 10) - 1;
|
||||||
|
const date = new Date(year, monthIndex);
|
||||||
|
showToast(
|
||||||
|
`Showing posts in ${date.toLocaleString('default', {
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
) : (
|
||||||
|
// Fallback to <select> for month and <input type="number"> for year
|
||||||
|
<MonthPicker
|
||||||
|
class={`filter-field ${month ? 'is-active' : ''}`}
|
||||||
disabled={!account?.acct}
|
disabled={!account?.acct}
|
||||||
value={month || ''}
|
value={month || ''}
|
||||||
min={MIN_YEAR_MONTH}
|
min={MIN_YEAR_MONTH}
|
||||||
max={new Date().toISOString().slice(0, 7)}
|
max={new Date().toISOString().slice(0, 7)}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const { value, validity } = e.currentTarget;
|
const { value, validity } = e;
|
||||||
if (!validity.valid) return;
|
if (!validity.valid) return;
|
||||||
setSearchParams(
|
setSearchParams(
|
||||||
value
|
value
|
||||||
|
@ -396,40 +434,11 @@ function AccountStatuses() {
|
||||||
}
|
}
|
||||||
: {},
|
: {},
|
||||||
);
|
);
|
||||||
const [year, month] = value.split('-');
|
|
||||||
const monthIndex = parseInt(month, 10) - 1;
|
|
||||||
const date = new Date(year, monthIndex);
|
|
||||||
showToast(
|
|
||||||
`Showing posts in ${date.toLocaleString('default', {
|
|
||||||
month: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
})}`,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</label>
|
))}
|
||||||
) : (
|
</div>
|
||||||
// Fallback to <select> for month and <input type="number"> for year
|
)}
|
||||||
<MonthPicker
|
|
||||||
class={`filter-field ${month ? 'is-active' : ''}`}
|
|
||||||
disabled={!account?.acct}
|
|
||||||
value={month || ''}
|
|
||||||
min={MIN_YEAR_MONTH}
|
|
||||||
max={new Date().toISOString().slice(0, 7)}
|
|
||||||
onInput={(e) => {
|
|
||||||
const { value, validity } = e;
|
|
||||||
if (!validity.valid) return;
|
|
||||||
setSearchParams(
|
|
||||||
value
|
|
||||||
? {
|
|
||||||
month: value,
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
@ -492,7 +501,7 @@ function AccountStatuses() {
|
||||||
errorText="Unable to load posts"
|
errorText="Unable to load posts"
|
||||||
fetchItems={fetchAccountStatuses}
|
fetchItems={fetchAccountStatuses}
|
||||||
useItemID
|
useItemID
|
||||||
view={media ? 'media' : undefined}
|
view={media || mediaFirst ? 'media' : undefined}
|
||||||
boostsCarousel={snapStates.settings.boostsCarousel}
|
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||||
timelineStart={TimelineStart}
|
timelineStart={TimelineStart}
|
||||||
refresh={[
|
refresh={[
|
||||||
|
|
|
@ -13,12 +13,13 @@ import NameText from '../components/name-text';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
import { getCurrentAccountID, setCurrentAccountID } from '../utils/store-utils';
|
||||||
|
|
||||||
function Accounts({ onClose }) {
|
function Accounts({ onClose }) {
|
||||||
const { masto } = api();
|
const { masto } = api();
|
||||||
// Accounts
|
// Accounts
|
||||||
const accounts = store.local.getJSON('accounts');
|
const accounts = store.local.getJSON('accounts');
|
||||||
const currentAccount = store.session.get('currentAccount');
|
const currentAccount = getCurrentAccountID();
|
||||||
const moreThanOneAccount = accounts.length > 1;
|
const moreThanOneAccount = accounts.length > 1;
|
||||||
|
|
||||||
const [_, reload] = useReducer((x) => x + 1, 0);
|
const [_, reload] = useReducer((x) => x + 1, 0);
|
||||||
|
@ -81,7 +82,7 @@ function Accounts({ onClose }) {
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
states.showAccount = `${account.info.username}@${account.instanceURL}`;
|
states.showAccount = `${account.info.username}@${account.instanceURL}`;
|
||||||
} else {
|
} else {
|
||||||
store.session.set('currentAccount', account.info.id);
|
setCurrentAccountID(account.info.id);
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -614,7 +614,7 @@
|
||||||
}
|
}
|
||||||
&.visibility-direct {
|
&.visibility-direct {
|
||||||
--yellow-stripes: repeating-linear-gradient(
|
--yellow-stripes: repeating-linear-gradient(
|
||||||
-45deg,
|
135deg,
|
||||||
var(--reply-to-faded-color),
|
var(--reply-to-faded-color),
|
||||||
var(--reply-to-faded-color) 10px,
|
var(--reply-to-faded-color) 10px,
|
||||||
var(--reply-to-faded-color) 10px,
|
var(--reply-to-faded-color) 10px,
|
||||||
|
|
|
@ -40,7 +40,7 @@ import showToast from '../utils/show-toast';
|
||||||
import states, { statusKey } from '../utils/states';
|
import states, { statusKey } from '../utils/states';
|
||||||
import statusPeek from '../utils/status-peek';
|
import statusPeek from '../utils/status-peek';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
import { getCurrentAccountNS } from '../utils/store-utils';
|
import { getCurrentAccountID, getCurrentAccountNS } from '../utils/store-utils';
|
||||||
import { assignFollowedTags } from '../utils/timeline-utils';
|
import { assignFollowedTags } from '../utils/timeline-utils';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ function Catchup() {
|
||||||
const [showTopLinks, setShowTopLinks] = useState(false);
|
const [showTopLinks, setShowTopLinks] = useState(false);
|
||||||
|
|
||||||
const currentAccount = useMemo(() => {
|
const currentAccount = useMemo(() => {
|
||||||
return store.session.get('currentAccount');
|
return getCurrentAccountID();
|
||||||
}, []);
|
}, []);
|
||||||
const isSelf = (accountID) => accountID === currentAccount;
|
const isSelf = (accountID) => accountID === currentAccount;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
MenuHeader,
|
MenuHeader,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@szhsin/react-menu';
|
} from '@szhsin/react-menu';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
|
@ -18,6 +18,7 @@ import { filteredItems } from '../utils/filters';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import { saveStatus } from '../utils/states';
|
import { saveStatus } from '../utils/states';
|
||||||
|
import { isMediaFirstInstance } from '../utils/store-utils';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
@ -55,6 +56,8 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
|
||||||
useTitle(title, `/:instance?/t/:hashtag`);
|
useTitle(title, `/:instance?/t/:hashtag`);
|
||||||
const latestItem = useRef();
|
const latestItem = useRef();
|
||||||
|
|
||||||
|
const mediaFirst = useMemo(() => isMediaFirstInstance(), []);
|
||||||
|
|
||||||
// const hashtagsIterator = useRef();
|
// const hashtagsIterator = useRef();
|
||||||
const maxID = useRef(undefined);
|
const maxID = useRef(undefined);
|
||||||
async function fetchHashtags(firstLoad) {
|
async function fetchHashtags(firstLoad) {
|
||||||
|
@ -73,7 +76,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
|
||||||
limit: LIMIT,
|
limit: LIMIT,
|
||||||
any: hashtags.slice(1),
|
any: hashtags.slice(1),
|
||||||
maxId: firstLoad ? undefined : maxID.current,
|
maxId: firstLoad ? undefined : maxID.current,
|
||||||
onlyMedia: media,
|
onlyMedia: media ? true : undefined,
|
||||||
})
|
})
|
||||||
.next();
|
.next();
|
||||||
let { value } = results;
|
let { value } = results;
|
||||||
|
@ -85,7 +88,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
|
||||||
// value = filteredItems(value, 'public');
|
// value = filteredItems(value, 'public');
|
||||||
value.forEach((item) => {
|
value.forEach((item) => {
|
||||||
saveStatus(item, instance, {
|
saveStatus(item, instance, {
|
||||||
skipThreading: media, // If media view, no need to form threads
|
skipThreading: media || mediaFirst, // If media view, no need to form threads
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,7 +159,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
|
||||||
fetchItems={fetchHashtags}
|
fetchItems={fetchHashtags}
|
||||||
checkForUpdates={checkForUpdates}
|
checkForUpdates={checkForUpdates}
|
||||||
useItemID
|
useItemID
|
||||||
view={media ? 'media' : undefined}
|
view={media || mediaFirst ? 'media' : undefined}
|
||||||
refresh={media}
|
refresh={media}
|
||||||
// allowFilters
|
// allowFilters
|
||||||
filterContext="public"
|
filterContext="public"
|
||||||
|
@ -233,23 +236,27 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<MenuHeader className="plain">Filters</MenuHeader>
|
{!mediaFirst && (
|
||||||
<MenuItem
|
<>
|
||||||
type="checkbox"
|
<MenuHeader className="plain">Filters</MenuHeader>
|
||||||
checked={!!media}
|
<MenuItem
|
||||||
onClick={() => {
|
type="checkbox"
|
||||||
if (media) {
|
checked={!!media}
|
||||||
searchParams.delete('media');
|
onClick={() => {
|
||||||
} else {
|
if (media) {
|
||||||
searchParams.set('media', '1');
|
searchParams.delete('media');
|
||||||
}
|
} else {
|
||||||
setSearchParams(searchParams);
|
searchParams.set('media', '1');
|
||||||
}}
|
}
|
||||||
>
|
setSearchParams(searchParams);
|
||||||
<Icon icon="check-circle" />{' '}
|
}}
|
||||||
<span class="menu-grow">Media only</span>
|
>
|
||||||
</MenuItem>
|
<Icon icon="check-circle" />{' '}
|
||||||
<MenuDivider />
|
<span class="menu-grow">Media only</span>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuDivider />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<FocusableItem className="menu-field" disabled={reachLimit}>
|
<FocusableItem className="menu-field" disabled={reachLimit}>
|
||||||
{({ ref }) => (
|
{({ ref }) => (
|
||||||
<form
|
<form
|
||||||
|
|
|
@ -72,6 +72,13 @@ function Notifications({ columnMode }) {
|
||||||
excludeTypes: ['follow_request'],
|
excludeTypes: ['follow_request'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (/max_id=($|&)/i.test(notificationsIterator.current?.nextParams)) {
|
||||||
|
// Pixelfed returns next paginationed link with empty max_id
|
||||||
|
// I assume, it's done (end of list)
|
||||||
|
return {
|
||||||
|
done: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
const allNotifications = await notificationsIterator.current.next();
|
const allNotifications = await notificationsIterator.current.next();
|
||||||
const notifications = allNotifications.value;
|
const notifications = allNotifications.value;
|
||||||
|
|
||||||
|
@ -82,6 +89,21 @@ function Notifications({ columnMode }) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TEST: Slot in a fake notification to test 'severed_relationships'
|
||||||
|
// notifications.unshift({
|
||||||
|
// id: '123123',
|
||||||
|
// type: 'severed_relationships',
|
||||||
|
// createdAt: '2024-03-22T19:20:08.316Z',
|
||||||
|
// event: {
|
||||||
|
// type: 'account_suspension',
|
||||||
|
// targetName: 'mastodon.dev',
|
||||||
|
// followersCount: 0,
|
||||||
|
// followingCount: 0,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log({ notifications });
|
||||||
|
|
||||||
const groupedNotifications = groupNotifications(notifications);
|
const groupedNotifications = groupNotifications(notifications);
|
||||||
|
|
||||||
if (firstLoad) {
|
if (firstLoad) {
|
||||||
|
|
|
@ -33,6 +33,7 @@ function Public({ local, columnMode, ...props }) {
|
||||||
publicIterator.current = masto.v1.timelines.public.list({
|
publicIterator.current = masto.v1.timelines.public.list({
|
||||||
limit: LIMIT,
|
limit: LIMIT,
|
||||||
local: isLocal,
|
local: isLocal,
|
||||||
|
remote: !isLocal, // Pixelfed
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const results = await publicIterator.current.next();
|
const results = await publicIterator.current.next();
|
||||||
|
|
|
@ -19,6 +19,7 @@ import pmem from '../utils/pmem';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import { saveStatus } from '../utils/states';
|
import { saveStatus } from '../utils/states';
|
||||||
|
import supports from '../utils/supports';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
@ -33,6 +34,17 @@ const fetchLinks = pmem(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function fetchTrends(masto) {
|
||||||
|
if (supports('@pixelfed/trending')) {
|
||||||
|
return masto.pixelfed.v2.discover.posts.trending.list({
|
||||||
|
range: 'daily',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return masto.v1.trends.statuses.list({
|
||||||
|
limit: LIMIT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function Trending({ columnMode, ...props }) {
|
function Trending({ columnMode, ...props }) {
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const params = columnMode ? {} : useParams();
|
const params = columnMode ? {} : useParams();
|
||||||
|
@ -48,36 +60,39 @@ function Trending({ columnMode, ...props }) {
|
||||||
const [hashtags, setHashtags] = useState([]);
|
const [hashtags, setHashtags] = useState([]);
|
||||||
const [links, setLinks] = useState([]);
|
const [links, setLinks] = useState([]);
|
||||||
const trendIterator = useRef();
|
const trendIterator = useRef();
|
||||||
|
|
||||||
async function fetchTrend(firstLoad) {
|
async function fetchTrend(firstLoad) {
|
||||||
if (firstLoad || !trendIterator.current) {
|
if (firstLoad || !trendIterator.current) {
|
||||||
trendIterator.current = masto.v1.trends.statuses.list({
|
trendIterator.current = fetchTrends(masto);
|
||||||
limit: LIMIT,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get hashtags
|
// Get hashtags
|
||||||
try {
|
if (supports('@mastodon/trending-hashtags')) {
|
||||||
const iterator = masto.v1.trends.tags.list();
|
try {
|
||||||
const { value: tags } = await iterator.next();
|
const iterator = masto.v1.trends.tags.list();
|
||||||
console.log('tags', tags);
|
const { value: tags } = await iterator.next();
|
||||||
if (tags?.length) {
|
console.log('tags', tags);
|
||||||
setHashtags(tags);
|
if (tags?.length) {
|
||||||
|
setHashtags(tags);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get links
|
// Get links
|
||||||
try {
|
if (supports('@mastodon/trending-links')) {
|
||||||
const { value } = await fetchLinks(masto, instance);
|
try {
|
||||||
// 4 types available: link, photo, video, rich
|
const { value } = await fetchLinks(masto, instance);
|
||||||
// Only want links for now
|
// 4 types available: link, photo, video, rich
|
||||||
const links = value?.filter?.((link) => link.type === 'link');
|
// Only want links for now
|
||||||
console.log('links', links);
|
const links = value?.filter?.((link) => link.type === 'link');
|
||||||
if (links?.length) {
|
console.log('links', links);
|
||||||
setLinks(links);
|
if (links?.length) {
|
||||||
|
setLinks(links);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const results = await trendIterator.current.next();
|
const results = await trendIterator.current.next();
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
getAccountByInstance,
|
getAccountByInstance,
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
saveAccount,
|
saveAccount,
|
||||||
|
setCurrentAccountID,
|
||||||
} from './store-utils';
|
} from './store-utils';
|
||||||
|
|
||||||
const { PHANPY_SCHEME: SCHEME = 'https' } = import.meta.env;
|
const { PHANPY_SCHEME: SCHEME = 'https' } = import.meta.env;
|
||||||
|
@ -122,7 +123,7 @@ export async function initAccount(client, instance, accessToken, vapidKey) {
|
||||||
const mastoAccount = await masto.v1.accounts.verifyCredentials();
|
const mastoAccount = await masto.v1.accounts.verifyCredentials();
|
||||||
|
|
||||||
console.log('CURRENTACCOUNT SET', mastoAccount.id);
|
console.log('CURRENTACCOUNT SET', mastoAccount.id);
|
||||||
store.session.set('currentAccount', mastoAccount.id);
|
setCurrentAccountID(mastoAccount.id);
|
||||||
|
|
||||||
saveAccount({
|
saveAccount({
|
||||||
info: mastoAccount,
|
info: mastoAccount,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import mem from './mem';
|
import mem from './mem';
|
||||||
import store from './store';
|
import { getCurrentAccountID } from './store-utils';
|
||||||
|
|
||||||
function _isFiltered(filtered, filterContext) {
|
function _isFiltered(filtered, filterContext) {
|
||||||
if (!filtered?.length) return false;
|
if (!filtered?.length) return false;
|
||||||
|
@ -43,7 +43,7 @@ export function filteredItem(item, filterContext, currentAccountID) {
|
||||||
export function filteredItems(items, filterContext) {
|
export function filteredItems(items, filterContext) {
|
||||||
if (!items?.length) return [];
|
if (!items?.length) return [];
|
||||||
if (!filterContext) return items;
|
if (!filterContext) return items;
|
||||||
const currentAccountID = store.session.get('currentAccount');
|
const currentAccountID = getCurrentAccountID();
|
||||||
return items.filter((item) =>
|
return items.filter((item) =>
|
||||||
filteredItem(item, filterContext, currentAccountID),
|
filteredItem(item, filterContext, currentAccountID),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { api } from './api';
|
import { api } from './api';
|
||||||
import store from './store';
|
import { getCurrentAccountID } from './store-utils';
|
||||||
|
|
||||||
export async function fetchRelationships(accounts, relationshipsMap = {}) {
|
export async function fetchRelationships(accounts, relationshipsMap = {}) {
|
||||||
if (!accounts?.length) return;
|
if (!accounts?.length) return;
|
||||||
const { masto } = api();
|
const { masto } = api();
|
||||||
|
|
||||||
const currentAccount = store.session.get('currentAccount');
|
const currentAccount = getCurrentAccountID();
|
||||||
const uniqueAccountIds = accounts.reduce((acc, a) => {
|
const uniqueAccountIds = accounts.reduce((acc, a) => {
|
||||||
// 1. Ignore duplicate accounts
|
// 1. Ignore duplicate accounts
|
||||||
// 2. Ignore accounts that are already inside relationshipsMap
|
// 2. Ignore accounts that are already inside relationshipsMap
|
||||||
|
|
|
@ -16,13 +16,40 @@ export function getAccountByInstance(instance) {
|
||||||
return accounts.find((a) => a.instanceURL === instance);
|
return accounts.find((a) => a.instanceURL === instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const standaloneMQ = window.matchMedia('(display-mode: standalone)');
|
||||||
|
|
||||||
|
export function getCurrentAccountID() {
|
||||||
|
try {
|
||||||
|
const id = store.session.get('currentAccount');
|
||||||
|
if (id) return id;
|
||||||
|
} catch (e) {}
|
||||||
|
if (standaloneMQ.matches) {
|
||||||
|
try {
|
||||||
|
const id = store.local.get('currentAccount');
|
||||||
|
if (id) return id;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCurrentAccountID(id) {
|
||||||
|
try {
|
||||||
|
store.session.set('currentAccount', id);
|
||||||
|
} catch (e) {}
|
||||||
|
if (standaloneMQ.matches) {
|
||||||
|
try {
|
||||||
|
store.local.set('currentAccount', id);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getCurrentAccount() {
|
export function getCurrentAccount() {
|
||||||
if (!window.__IGNORE_GET_ACCOUNT_ERROR__) {
|
if (!window.__IGNORE_GET_ACCOUNT_ERROR__) {
|
||||||
// Track down getCurrentAccount() calls before account-based states are initialized
|
// Track down getCurrentAccount() calls before account-based states are initialized
|
||||||
console.error('getCurrentAccount() called before states are initialized');
|
console.error('getCurrentAccount() called before states are initialized');
|
||||||
if (import.meta.env.DEV) console.trace();
|
if (import.meta.env.DEV) console.trace();
|
||||||
}
|
}
|
||||||
const currentAccount = store.session.get('currentAccount');
|
const currentAccount = getCurrentAccountID();
|
||||||
const account = getAccount(currentAccount);
|
const account = getAccount(currentAccount);
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +75,7 @@ export function saveAccount(account) {
|
||||||
accounts.push(account);
|
accounts.push(account);
|
||||||
}
|
}
|
||||||
store.local.setJSON('accounts', accounts);
|
store.local.setJSON('accounts', accounts);
|
||||||
store.session.set('currentAccount', account.info.id);
|
setCurrentAccountID(account.info.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateAccount(accountInfo) {
|
export function updateAccount(accountInfo) {
|
||||||
|
|
|
@ -4,6 +4,21 @@ import features from '../data/features.json';
|
||||||
|
|
||||||
import { getCurrentInstance } from './store-utils';
|
import { getCurrentInstance } from './store-utils';
|
||||||
|
|
||||||
|
// Non-semver(?) UA string detection
|
||||||
|
const containPixelfed = /pixelfed/i;
|
||||||
|
const notContainPixelfed = /^(?!.*pixelfed).*$/i;
|
||||||
|
const platformFeatures = {
|
||||||
|
'@mastodon/lists': notContainPixelfed,
|
||||||
|
'@mastodon/filters': notContainPixelfed,
|
||||||
|
'@mastodon/mentions': notContainPixelfed,
|
||||||
|
'@mastodon/trending-hashtags': notContainPixelfed,
|
||||||
|
'@mastodon/trending-links': notContainPixelfed,
|
||||||
|
'@mastodon/post-bookmark': notContainPixelfed,
|
||||||
|
'@mastodon/post-edit': notContainPixelfed,
|
||||||
|
'@mastodon/profile-edit': notContainPixelfed,
|
||||||
|
'@mastodon/profile-private-note': notContainPixelfed,
|
||||||
|
'@pixelfed/trending': containPixelfed,
|
||||||
|
};
|
||||||
const supportsCache = {};
|
const supportsCache = {};
|
||||||
|
|
||||||
function supports(feature) {
|
function supports(feature) {
|
||||||
|
@ -11,6 +26,11 @@ function supports(feature) {
|
||||||
const { version, domain } = getCurrentInstance();
|
const { version, domain } = getCurrentInstance();
|
||||||
const key = `${domain}-${feature}`;
|
const key = `${domain}-${feature}`;
|
||||||
if (supportsCache[key]) return supportsCache[key];
|
if (supportsCache[key]) return supportsCache[key];
|
||||||
|
|
||||||
|
if (platformFeatures[feature]) {
|
||||||
|
return (supportsCache[key] = platformFeatures[feature].test(version));
|
||||||
|
}
|
||||||
|
|
||||||
const range = features[feature];
|
const range = features[feature];
|
||||||
if (!range) return false;
|
if (!range) return false;
|
||||||
return (supportsCache[key] = satisfies(version, range, {
|
return (supportsCache[key] = satisfies(version, range, {
|
||||||
|
|
Ładowanie…
Reference in New Issue