Porównaj commity

...

33 Commity

Autor SHA1 Wiadomość Data
Helge 6139d97271
Merge 99e5c1036d into 55ad6500bc 2024-04-16 20:08:02 -04:00
Lim Chee Aun 55ad6500bc Fix margins 2024-04-16 23:21:46 +08:00
Lim Chee Aun f4b95d254c Maybe this helps? 2024-04-16 20:18:18 +08:00
Lim Chee Aun effbe189e1 Revert "Test upgrade react-hotkeys-hook for the keys fix"
This reverts commit 9285a0ba9a.
2024-04-16 00:09:53 +08:00
Lim Chee Aun 44e910b8c9 Fix wrong carousel math 2024-04-15 23:34:58 +08:00
Lim Chee Aun a68dccd7cf Fix rerender bug with followed hashtag parent
And… somehow memoize it?
2024-04-15 21:37:03 +08:00
Lim Chee Aun 9a6364a674 Obviously got to flex my scroll-driven animation CSSkillz 2024-04-15 19:59:57 +08:00
Lim Chee Aun e2f39596f0 Might as well add more supports 2024-04-15 19:58:59 +08:00
Lim Chee Aun 701b9e99b3 More media-first styling changes 2024-04-15 17:07:34 +08:00
Lim Chee Aun 294ab2bf00 Just put in this commented test notification
Good for reference in the future
2024-04-15 17:07:20 +08:00
Lim Chee Aun 304ce5a3e8 Experiment dynamic change of parent
This might prevent double renders
2024-04-15 17:06:44 +08:00
Lim Chee Aun 57390a291b No need background if there's pre-meta before it 2024-04-15 10:10:49 +08:00
Lim Chee Aun cd5920114f Undo back to -45deg, not everything need 135deg 2024-04-15 07:26:45 +08:00
Lim Chee Aun 06c6360cae More support for Pixelfed 2024-04-14 17:20:18 +08:00
Lim Chee Aun afdfdb86da Media-first style adjustments 2024-04-14 17:18:52 +08:00
Lim Chee Aun 6f8f3e4fd0 Change -35deg to 145deg prevents stripes animation
When dynamically changing dimension (height), repeating linear gradient seems to animate. This prevents it.
https://stackoverflow.com/a/76285775/20838
2024-04-14 14:08:50 +08:00
Lim Chee Aun 342ff20986 Document `PHANPY_IMG_ALT_API_URL` 2024-04-14 08:14:34 +08:00
Lim Chee Aun 94996d098e Fix width issue 2024-04-13 23:08:25 +08:00
Lim Chee Aun c286562ee8 Media-first style adjustments 2024-04-13 19:21:48 +08:00
Lim Chee Aun 5babdc9d63 Fix width/height not set 2024-04-13 19:21:20 +08:00
Lim Chee Aun 260bb8746d More media-first adjustments 2024-04-13 17:10:13 +08:00
Lim Chee Aun 7be620808f Fix notifications for Pixelfed 2024-04-13 17:09:56 +08:00
Lim Chee Aun df3aca70fa Open media + post view for wider viewports 2024-04-13 17:09:00 +08:00
Lim Chee Aun ec65163c89 More breathing space 2024-04-13 17:08:39 +08:00
Lim Chee Aun 6f22ec3842 Fix missing idempotency key 2024-04-13 17:07:28 +08:00
Lim Chee Aun 2faf9b4c20 Pixelfed needs remote which is opposite of local lol 2024-04-13 00:11:00 +08:00
Lim Chee Aun 501e43207b Don't set onlyMedia if not set
This defaults to false for Mastodon, but true for Pixelfed
2024-04-13 00:11:00 +08:00
Lim Chee Aun e782cc0dde Refactor set/get current account ID
And add fallback for standalone mode where session storage is not enough
2024-04-13 00:11:00 +08:00
Lim Chee Aun aefda31c2a Temporary quick fix, remove dash from hashtag regex 2024-04-13 00:11:00 +08:00
Lim Chee Aun 9285a0ba9a Test upgrade react-hotkeys-hook for the keys fix 2024-04-13 00:11:00 +08:00
Chee Aun 7fb56d9f6c
Merge pull request #493 from ultramookie/ultramookie-patch-1
Adding new self-hosted instance of Phanpy
2024-04-12 17:41:55 +08:00
steve mookie kong f7c69e56e9
Adding new self-hosted instance of Phanpy
Added new self-hosted instance of Phanpy, halo.mookiesplace.com
2024-04-11 21:28:38 -07:00
Helge 99e5c1036d Enable running using http
This means that by adding PHANPY_SCHEME=http to the file ".env"
phanpy will use http instead of https to connect to remote instances.

This is useful to test local versions of various Fediverse applications.
These can be created following the instructions on

https://funfedi.dev/quickstart/#running-an-application-from-the-fediverse-pasture
2024-03-09 17:31:36 +01:00
28 zmienionych plików z 582 dodań i 377 usunięć

Wyświetl plik

@ -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)
- List of fallback instances hard-coded in `/.env`
- [↗️ 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):
- 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.
@ -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.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)
- [halo.mookiesplace.com](https://halo.mookiesplace.com) by [@mookie@mookiesplace.com](https://mookiesplace.com/@mookie)
> Note: Add yours by creating a pull request.

Wyświetl plik

@ -306,13 +306,20 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
.timeline {
> li:not(.timeline-item-carousel, .timeline-item-container) {
&:has(.status-media-first) {
width: fit-content;
@media (min-width: 40em) {
width: fit-content;
max-width: min(480px, 100%);
}
background-color: transparent !important;
border: 0 !important;
box-shadow: none !important;
max-width: min(480px, 100%);
margin-inline: auto !important;
&:not(:first-child) {
margin-block: 32px;
}
&:has(.skeleton) {
width: 100%;
}
@ -1910,7 +1917,8 @@ body > .szh-menu-container {
/* two columns only */
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;
}
.szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):first-child,

Wyświetl plik

@ -53,7 +53,7 @@ import { getAccessToken } from './utils/auth';
import focusDeck from './utils/focus-deck';
import states, { initStates, statusKey } from './utils/states';
import store from './utils/store';
import { getCurrentAccount } from './utils/store-utils';
import { getCurrentAccount, setCurrentAccountID } from './utils/store-utils';
import './utils/toast-alert';
window.__STATES__ = states;
@ -338,7 +338,7 @@ function App() {
window.__IGNORE_GET_ACCOUNT_ERROR__ = true;
const account = getCurrentAccount();
if (account) {
store.session.set('currentAccount', account.info.id);
setCurrentAccountID(account.info.id);
const { client } = api({ account });
const { instance } = client;
// console.log('masto', masto);

Wyświetl plik

@ -22,7 +22,8 @@ import shortenNumber from '../utils/shorten-number';
import showToast from '../utils/show-toast';
import states, { hideAllModals } from '../utils/states';
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 Avatar from './avatar';
@ -198,10 +199,7 @@ function AccountInfo({
}
}
const isSelf = useMemo(
() => id === store.session.get('currentAccount'),
[id],
);
const isSelf = useMemo(() => id === getCurrentAccountID(), [id]);
useEffect(() => {
const infoHasEssentials = !!(
@ -920,7 +918,7 @@ function RelatedActions({
useEffect(() => {
if (info) {
const currentAccount = store.session.get('currentAccount');
const currentAccount = getCurrentAccountID();
let currentID;
(async () => {
if (sameInstance && authenticated) {
@ -1094,16 +1092,18 @@ function RelatedActions({
<Icon icon="translate" />
<span>Translate bio</span>
</MenuItem>
<MenuItem
onClick={() => {
setShowPrivateNoteModal(true);
}}
>
<Icon icon="pencil" />
<span>
{privateNote ? 'Edit private note' : 'Add private note'}
</span>
</MenuItem>
{supports('@mastodon/profile-private-note') && (
<MenuItem
onClick={() => {
setShowPrivateNoteModal(true);
}}
>
<Icon icon="pencil" />
<span>
{privateNote ? 'Edit private note' : 'Add private note'}
</span>
</MenuItem>
)}
{following && !!relationship && (
<>
<MenuItem
@ -1452,19 +1452,22 @@ function RelatedActions({
</MenuItem>
</>
)}
{currentAuthenticated && isSelf && standalone && (
<>
<MenuDivider />
<MenuItem
onClick={() => {
setShowEditProfile(true);
}}
>
<Icon icon="pencil" />
<span>Edit profile</span>
</MenuItem>
</>
)}
{currentAuthenticated &&
isSelf &&
standalone &&
supports('@mastodon/profile-edit') && (
<>
<MenuDivider />
<MenuItem
onClick={() => {
setShowEditProfile(true);
}}
>
<Icon icon="pencil" />
<span>Edit profile</span>
</MenuItem>
</>
)}
{import.meta.env.DEV && currentAuthenticated && isSelf && (
<>
<MenuDivider />

Wyświetl plik

@ -310,7 +310,7 @@
#compose-container .form-visibility-direct {
--yellow-stripes: repeating-linear-gradient(
-45deg,
135deg,
var(--reply-to-faded-color),
var(--reply-to-faded-color) 10px,
var(--reply-to-faded-color) 10px,

Wyświetl plik

@ -124,7 +124,7 @@ const MENTION_RE = new RegExp(
// AI-generated, all other regexes are too complicated
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',
);
@ -988,7 +988,11 @@ function Compose({
} else {
try {
newStatus = await masto.v1.statuses.create(params, {
idempotencyKey: UID.current,
requestInit: {
headers: {
'Idempotency-Key': UID.current,
},
},
});
} catch (_) {
// If idempotency key fails, try again without it

Wyświetl plik

@ -8,6 +8,7 @@ import FilterContext from '../utils/filter-context';
import { isFiltered } from '../utils/filters';
import states, { statusKey } from '../utils/states';
import store from '../utils/store';
import { getCurrentAccountID } from '../utils/store-utils';
import Media from './media';
@ -88,7 +89,7 @@ function MediaPost({
};
const currentAccount = useMemo(() => {
return store.session.get('currentAccount');
return getCurrentAccountID();
}, []);
const isSelf = useMemo(() => {
return currentAccount && currentAccount === accountId;

Wyświetl plik

@ -341,13 +341,15 @@ function Media({
if (!hasDimensions) {
const $media = e.target.closest('.media');
if ($media) {
const { naturalWidth, naturalHeight } = e.target;
$media.dataset.orientation =
e.target.naturalWidth > e.target.naturalHeight
? 'landscape'
: 'portrait';
$media.style['--width'] = `${e.target.naturalWidth}px`;
$media.style['--height'] = `${e.target.naturalHeight}px`;
$media.style.aspectRatio = `${e.target.naturalWidth}/${e.target.naturalHeight}`;
naturalWidth > naturalHeight ? 'landscape' : 'portrait';
$media.style.setProperty('--width', `${naturalWidth}px`);
$media.style.setProperty(
'--height',
`${naturalHeight}px`,
);
$media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`;
}
}
}}
@ -511,19 +513,25 @@ function Media({
height={height}
data-orientation={orientation}
loading="lazy"
decoding="async"
onLoad={(e) => {
if (!hasDimensions) {
const $media = e.target.closest('.media');
if ($media) {
const { naturalHeight, naturalWidth } = e.target;
$media.dataset.orientation =
e.target.naturalWidth > e.target.naturalHeight
naturalWidth > naturalHeight
? 'landscape'
: 'portrait';
$media.style['--width'] = `${e.target.naturalWidth}px`;
$media.style[
'--height'
] = `${e.target.naturalHeight}px`;
$media.style.aspectRatio = `${e.target.naturalWidth}/${e.target.naturalHeight}`;
$media.style.setProperty(
'--width',
`${naturalWidth}px`,
);
$media.style.setProperty(
'--height',
`${naturalHeight}px`,
);
$media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`;
}
}
}}

Wyświetl plik

@ -11,6 +11,8 @@ import { getLists } from '../utils/lists';
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
import states from '../utils/states';
import store from '../utils/store';
import { getCurrentAccountID } from '../utils/store-utils';
import supports from '../utils/supports';
import Avatar from './avatar';
import Icon from './icon';
@ -24,9 +26,8 @@ function NavMenu(props) {
const [currentAccount, moreThanOneAccount] = useMemo(() => {
const accounts = store.local.getJSON('accounts') || [];
const acc =
accounts.find(
(account) => account.info.id === store.session.get('currentAccount'),
) || accounts[0];
accounts.find((account) => account.info.id === getCurrentAccountID()) ||
accounts[0];
return [acc, accounts.length > 1];
}, []);
@ -83,8 +84,10 @@ function NavMenu(props) {
return results;
}
const supportsLists = supports('@mastodon/lists');
const [lists, setLists] = useState([]);
useEffect(() => {
if (!supportsLists) return;
if (menuState === 'open') {
getLists().then(setLists);
}
@ -186,9 +189,11 @@ function NavMenu(props) {
<Icon icon="history2" size="l" />
<span>Catch-up</span>
</MenuLink>
<MenuLink to="/mentions">
<Icon icon="at" size="l" /> <span>Mentions</span>
</MenuLink>
{supports('@mastodon/mentions') && (
<MenuLink to="/mentions">
<Icon icon="at" size="l" /> <span>Mentions</span>
</MenuLink>
)}
<MenuLink to="/notifications">
<Icon icon="notification" size="l" /> <span>Notifications</span>
{snapStates.notificationsShowNew && (
@ -232,10 +237,12 @@ function NavMenu(props) {
)}
</SubMenu2>
) : (
<MenuLink to="/l">
<Icon icon="list" size="l" />
<span>Lists</span>
</MenuLink>
supportsLists && (
<MenuLink to="/l">
<Icon icon="list" size="l" />
<span>Lists</span>
</MenuLink>
)
)}
<MenuLink to="/b">
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
@ -260,10 +267,12 @@ function NavMenu(props) {
<span>Followed Hashtags</span>
</MenuLink>
<MenuDivider />
<MenuLink to="/ft">
<Icon icon="filters" size="l" />
Filters
</MenuLink>
{supports('@mastodon/filters') && (
<MenuLink to="/ft">
<Icon icon="filters" size="l" />
Filters
</MenuLink>
)}
<MenuItem
onClick={() => {
states.showGenericAccounts = {

Wyświetl plik

@ -4,6 +4,7 @@ import { memo } from 'preact/compat';
import shortenNumber from '../utils/shorten-number';
import states, { statusKey } from '../utils/states';
import store from '../utils/store';
import { getCurrentAccountID } from '../utils/store-utils';
import useTruncated from '../utils/useTruncated';
import Avatar from './avatar';
@ -132,7 +133,7 @@ function Notification({
const actualStatus = status?.reblog || status;
const actualStatusID = actualStatus?.id;
const currentAccount = store.session.get('currentAccount');
const currentAccount = getCurrentAccountID();
const isSelf = currentAccount === account?.id;
const isVoted = status?.poll?.voted;
const isReplyToOthers =

Wyświetl plik

@ -19,6 +19,7 @@ import pmem from '../utils/pmem';
import showToast from '../utils/show-toast';
import states from '../utils/states';
import store from '../utils/store';
import { getCurrentAccountID } from '../utils/store-utils';
import AsyncText from './AsyncText';
import Icon from './icon';
@ -787,7 +788,7 @@ function ImportExport({ shortcuts, onClose }) {
disabled={importUIState === 'cloud-downloading'}
onClick={async () => {
setImportUIState('cloud-downloading');
const currentAccount = store.session.get('currentAccount');
const currentAccount = getCurrentAccountID();
showToast(
'Downloading saved shortcuts from instance server…',
);
@ -1043,7 +1044,7 @@ function ImportExport({ shortcuts, onClose }) {
disabled={importUIState === 'cloud-uploading'}
onClick={async () => {
setImportUIState('cloud-uploading');
const currentAccount = store.session.get('currentAccount');
const currentAccount = getCurrentAccountID();
try {
const relationships =
await masto.v1.accounts.relationships.fetch({

Wyświetl plik

@ -47,7 +47,7 @@
}
.visibility-direct {
--yellow-stripes: repeating-linear-gradient(
-45deg,
135deg,
var(--reply-to-faded-color),
var(--reply-to-faded-color) 10px,
var(--reply-to-faded-color) 10px,
@ -365,6 +365,10 @@
background-image: var(--yellow-stripes);
}
.status-pre-meta + & {
background-image: none;
}
> * {
opacity: 0.65;
transition: opacity 1s ease-out;
@ -1320,11 +1324,22 @@ body:has(#modal-container .carousel) .status .media img:hover {
}
.status.skeleton .media-first-container {
min-height: 3em;
min-height: 320px;
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 {
timeline-scope: --media-carousel;
.meta-name {
opacity: 0.65;
transition: opacity 0.5s ease-in-out;
@ -1356,76 +1371,20 @@ body:has(#modal-container .carousel) .status .media img:hover {
.media-first-spoiler-button {
display: inline-flex !important;
}
.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;
scrollbar-width: none;
/* border: var(--hairline-width) solid var(--outline-color);
border-inline-width: 0;
background-color: var(--bg-faded-color); */
margin-top: 8px;
margin-inline: -16px;
@media (min-width: 40em) {
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 {
flex-shrink: 0;
width: 100%;
position: sticky;
right: 0;
left: 0;
position: absolute;
inset: 0;
pointer-events: none;
display: flex;
justify-content: space-between;
@ -1443,7 +1402,7 @@ body:has(#modal-container .carousel) .status .media img:hover {
font-size: 0.8em;
font-variant-numeric: tabular-nums;
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);
}
@ -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 {
opacity: 0;
}
@ -1489,6 +1518,11 @@ body:has(#modal-container .carousel) .status .media img:hover {
margin-top: 8px;
padding: 8px;
@supports (animation-timeline: scroll()) {
animation: media-carousel-slide 1s linear both;
animation-timeline: --media-carousel;
}
.carousel-dot {
display: inline-block;
width: 5px;
@ -1497,6 +1531,7 @@ body:has(#modal-container .carousel) .status .media img:hover {
background-color: var(--text-color);
transition: all 0.3s ease-in-out;
opacity: 0.3;
flex-shrink: 0;
&.active {
opacity: 1;

Wyświetl plik

@ -11,6 +11,7 @@ import {
import { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash';
import { shallowEqual } from 'fast-equals';
import prettify from 'html-prettify';
import { Fragment } from 'preact';
import { memo } from 'preact/compat';
import {
useCallback,
@ -55,6 +56,8 @@ import { speak, supportsTTS } from '../utils/speech';
import states, { getStatus, saveStatus, statusKey } from '../utils/states';
import statusPeek from '../utils/status-peek';
import store from '../utils/store';
import { getCurrentAccountID } from '../utils/store-utils';
import supports from '../utils/supports';
import unfurlMastodonLink from '../utils/unfurl-link';
import useTruncated from '../utils/useTruncated';
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({
statusID,
status,
@ -173,7 +182,11 @@ function Status({
}) {
if (skeleton) {
return (
<div class={`status skeleton ${mediaFirst ? 'status-media-first' : ''}`}>
<div
class={`status skeleton ${
mediaFirst ? 'status-media-first small' : ''
}`}
>
{!mediaFirst && <Avatar size="xxl" />}
<div class="container">
<div class="meta">
@ -256,7 +269,7 @@ function Status({
if (mediaFirst && hasMediaAttachments) size = 's';
const currentAccount = useMemo(() => {
return store.session.get('currentAccount');
return getCurrentAccountID();
}, []);
const isSelf = useMemo(() => {
return currentAccount && currentAccount === accountId;
@ -394,8 +407,8 @@ function Status({
}
// Check followedTags
if (showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length) {
return (
const FollowedTagsParent = useCallback(
({ children }) => (
<div
data-state-post-id={sKey}
class="status-followed-tags"
@ -413,19 +426,15 @@ function Status({
</Link>
))}
</div>
<Status
status={statusID ? null : status}
statusID={statusID ? status.id : null}
instance={instance}
size={size}
contentTextWeight={contentTextWeight}
readOnly={readOnly}
enableCommentHint
mediaFirst={mediaFirst}
/>
{children}
</div>
);
}
),
[sKey, instance, snapStates.statusFollowedTags[sKey]],
);
const StatusParent =
showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length
? FollowedTagsParent
: Fragment;
const isSizeLarge = size === 'l';
@ -639,6 +648,7 @@ function Status({
};
const bookmarkStatus = async () => {
if (!supports('@mastodon/post-bookmark')) return;
if (!sameInstance || !authenticated) {
alert(unauthInteractionErrorMessage);
return false;
@ -826,13 +836,15 @@ function Status({
: 'Like'}
</span>
</MenuItem>
<MenuItem
onClick={bookmarkStatusNotify}
className={`menu-bookmark ${bookmarked ? 'checked' : ''}`}
>
<Icon icon="bookmark" />
<span>{bookmarked ? 'Unbookmark' : 'Bookmark'}</span>
</MenuItem>
{supports('@mastodon/post-bookmark') && (
<MenuItem
onClick={bookmarkStatusNotify}
className={`menu-bookmark ${bookmarked ? 'checked' : ''}`}
>
<Icon icon="bookmark" />
<span>{bookmarked ? 'Unbookmark' : 'Bookmark'}</span>
</MenuItem>
)}
</div>
</>
)}
@ -1076,16 +1088,18 @@ function Status({
)}
{isSelf && (
<div class="menu-horizontal">
<MenuItem
onClick={() => {
states.showCompose = {
editStatus: status,
};
}}
>
<Icon icon="pencil" />
<span>Edit</span>
</MenuItem>
{supports('@mastodon/post-edit') && (
<MenuItem
onClick={() => {
states.showCompose = {
editStatus: status,
};
}}
>
<Icon icon="pencil" />
<span>Edit</span>
</MenuItem>
)}
{isSizeLarge && (
<MenuConfirm
subMenu
@ -1366,7 +1380,7 @@ function Status({
]);
return (
<>
<StatusParent>
{showReplyParent && !!(inReplyToId && inReplyToAccountId) && (
<StatusCompact sKey={sKey} />
)}
@ -1394,11 +1408,7 @@ function Status({
? 'status-reply-to'
: ''
} visibility-${visibility} ${_pinned ? 'status-pinned' : ''} ${
{
s: 'small',
m: 'medium',
l: 'large',
}[size]
SIZE_CLASS[size]
} ${_deleted ? 'status-deleted' : ''} ${quoted ? 'status-card' : ''} ${
isContextMenuOpen ? 'status-menu-open' : ''
} ${mediaFirst && hasMediaAttachments ? 'status-media-first' : ''}`}
@ -2159,16 +2169,18 @@ function Status({
onClick={favouriteStatus}
/>
</div>
<div class="action">
<StatusButton
checked={bookmarked}
title={['Bookmark', 'Unbookmark']}
alt={['Bookmark', 'Bookmarked']}
class="bookmark-button"
icon="bookmark"
onClick={bookmarkStatus}
/>
</div>
{supports('@mastodon/post-bookmark') && (
<div class="action">
<StatusButton
checked={bookmarked}
title={['Bookmark', 'Unbookmark']}
alt={['Bookmark', 'Bookmarked']}
class="bookmark-button"
icon="bookmark"
onClick={bookmarkStatus}
/>
</div>
)}
<Menu2
portal={{
target:
@ -2236,7 +2248,7 @@ function Status({
</Modal>
)}
</article>
</>
</StatusParent>
);
}
@ -2280,16 +2292,18 @@ function MediaFirstContainer(props) {
return (
<>
<div class="media-first-container" ref={carouselRef}>
{mediaAttachments.map((media, i) => (
<div class="media-first-item" key={media.id}>
<Media
media={media}
lang={language}
to={`/${instance}/s/${postID}?media-only=${i + 1}`}
/>
</div>
))}
<div class="media-first-container">
<div class="media-first-carousel" ref={carouselRef}>
{mediaAttachments.map((media, i) => (
<div class="media-first-item" key={media.id}>
<Media
media={media}
lang={language}
to={`/${instance}/s/${postID}?media=${i + 1}`}
/>
</div>
))}
</div>
{moreThanOne && (
<div class="media-carousel-controls">
<div class="carousel-indexer">
@ -2335,7 +2349,12 @@ function MediaFirstContainer(props) {
)}
</div>
{moreThanOne && (
<div class="media-carousel-dots">
<div
class="media-carousel-dots"
style={{
'--dots-count': mediaAttachments.length,
}}
>
{mediaAttachments.map((media, i) => (
<span
key={media.id}

Wyświetl plik

@ -21,6 +21,7 @@ import pmem from '../utils/pmem';
import showToast from '../utils/show-toast';
import states from '../utils/states';
import { saveStatus } from '../utils/states';
import { isMediaFirstInstance } from '../utils/store-utils';
import useTitle from '../utils/useTitle';
const LIMIT = 20;
@ -68,6 +69,8 @@ function AccountStatuses() {
searchOffsetRef.current = 0;
}, allSearchParams);
const mediaFirst = useMemo(() => isMediaFirstInstance(), []);
const sameCurrentInstance = useMemo(
() => instance === currentInstance,
[instance, currentInstance],
@ -186,7 +189,7 @@ function AccountStatuses() {
limit: LIMIT,
exclude_replies: excludeReplies,
exclude_reblogs: excludeBoosts,
only_media: media,
only_media: media || undefined,
tagged,
});
}
@ -270,17 +273,21 @@ function AccountStatuses() {
} catch (e) {
console.error(e);
}
try {
const featuredTags = await masto.v1.accounts
.$select(id)
.featuredTags.list();
console.log({ featuredTags });
setFeaturedTags(featuredTags);
} catch (e) {
console.error(e);
// No need, because the whole filter bar is hidden
// TODO: Revisit this
if (!mediaFirst) {
try {
const featuredTags = await masto.v1.accounts
.$select(id)
.featuredTags.list();
console.log({ featuredTags });
setFeaturedTags(featuredTags);
} catch (e) {
console.error(e);
}
}
})();
}, [id]);
}, [id, mediaFirst]);
const { displayName, acct, emojis } = account || {};
@ -299,95 +306,126 @@ function AccountStatuses() {
authenticated={authenticated}
standalone
/>
<div
class="filter-bar"
ref={filterBarRef}
style={{
position: 'relative',
}}
>
{filtered ? (
{!mediaFirst && (
<div
class="filter-bar"
ref={filterBarRef}
style={{
position: 'relative',
}}
>
{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
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
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)}`
}`}
to={`/${instance}/a/${id}${excludeReplies ? '?replies=1' : ''}`}
onClick={() => {
if (tagged !== tag.name) {
showToast(`Showing posts tagged with #${tag.name}`);
if (excludeReplies) {
showToast('Showing post with replies');
}
}}
class={tagged === tag.name ? 'is-active' : ''}
class={excludeReplies ? '' : 'is-active'}
>
<span>
<span class="more-insignificant">#</span>
{tag.name}
</span>
{
// The count differs based on instance 😅
}
{/* <span class="filter-count">{tag.statusesCount}</span> */}
+ Replies
</Link>
))}
{searchEnabled &&
(supportsInputMonth ? (
<label class={`filter-field ${month ? 'is-active' : ''}`}>
<Icon icon="month" size="l" />
<input
type="month"
<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={() => {
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}
value={month || ''}
min={MIN_YEAR_MONTH}
max={new Date().toISOString().slice(0, 7)}
onInput={(e) => {
const { value, validity } = e.currentTarget;
const { value, validity } = e;
if (!validity.valid) return;
setSearchParams(
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>
) : (
// 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>
))}
</div>
)}
</>
);
}, [
@ -492,7 +501,7 @@ function AccountStatuses() {
errorText="Unable to load posts"
fetchItems={fetchAccountStatuses}
useItemID
view={media ? 'media' : undefined}
view={media || mediaFirst ? 'media' : undefined}
boostsCarousel={snapStates.settings.boostsCarousel}
timelineStart={TimelineStart}
refresh={[

Wyświetl plik

@ -13,12 +13,13 @@ import NameText from '../components/name-text';
import { api } from '../utils/api';
import states from '../utils/states';
import store from '../utils/store';
import { getCurrentAccountID, setCurrentAccountID } from '../utils/store-utils';
function Accounts({ onClose }) {
const { masto } = api();
// Accounts
const accounts = store.local.getJSON('accounts');
const currentAccount = store.session.get('currentAccount');
const currentAccount = getCurrentAccountID();
const moreThanOneAccount = accounts.length > 1;
const [_, reload] = useReducer((x) => x + 1, 0);
@ -81,7 +82,7 @@ function Accounts({ onClose }) {
if (isCurrent) {
states.showAccount = `${account.info.username}@${account.instanceURL}`;
} else {
store.session.set('currentAccount', account.info.id);
setCurrentAccountID(account.info.id);
location.reload();
}
}}

Wyświetl plik

@ -614,7 +614,7 @@
}
&.visibility-direct {
--yellow-stripes: repeating-linear-gradient(
-45deg,
135deg,
var(--reply-to-faded-color),
var(--reply-to-faded-color) 10px,
var(--reply-to-faded-color) 10px,

Wyświetl plik

@ -40,7 +40,7 @@ import showToast from '../utils/show-toast';
import states, { statusKey } from '../utils/states';
import statusPeek from '../utils/status-peek';
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 useTitle from '../utils/useTitle';
@ -112,7 +112,7 @@ function Catchup() {
const [showTopLinks, setShowTopLinks] = useState(false);
const currentAccount = useMemo(() => {
return store.session.get('currentAccount');
return getCurrentAccountID();
}, []);
const isSelf = (accountID) => accountID === currentAccount;

Wyświetl plik

@ -5,7 +5,7 @@ import {
MenuHeader,
MenuItem,
} 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 Icon from '../components/icon';
@ -18,6 +18,7 @@ import { filteredItems } from '../utils/filters';
import showToast from '../utils/show-toast';
import states from '../utils/states';
import { saveStatus } from '../utils/states';
import { isMediaFirstInstance } from '../utils/store-utils';
import useTitle from '../utils/useTitle';
const LIMIT = 20;
@ -55,6 +56,8 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
useTitle(title, `/:instance?/t/:hashtag`);
const latestItem = useRef();
const mediaFirst = useMemo(() => isMediaFirstInstance(), []);
// const hashtagsIterator = useRef();
const maxID = useRef(undefined);
async function fetchHashtags(firstLoad) {
@ -73,7 +76,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
limit: LIMIT,
any: hashtags.slice(1),
maxId: firstLoad ? undefined : maxID.current,
onlyMedia: media,
onlyMedia: media ? true : undefined,
})
.next();
let { value } = results;
@ -85,7 +88,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
// value = filteredItems(value, 'public');
value.forEach((item) => {
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}
checkForUpdates={checkForUpdates}
useItemID
view={media ? 'media' : undefined}
view={media || mediaFirst ? 'media' : undefined}
refresh={media}
// allowFilters
filterContext="public"
@ -233,23 +236,27 @@ function Hashtags({ media: mediaView, columnMode, ...props }) {
<MenuDivider />
</>
)}
<MenuHeader className="plain">Filters</MenuHeader>
<MenuItem
type="checkbox"
checked={!!media}
onClick={() => {
if (media) {
searchParams.delete('media');
} else {
searchParams.set('media', '1');
}
setSearchParams(searchParams);
}}
>
<Icon icon="check-circle" />{' '}
<span class="menu-grow">Media only</span>
</MenuItem>
<MenuDivider />
{!mediaFirst && (
<>
<MenuHeader className="plain">Filters</MenuHeader>
<MenuItem
type="checkbox"
checked={!!media}
onClick={() => {
if (media) {
searchParams.delete('media');
} else {
searchParams.set('media', '1');
}
setSearchParams(searchParams);
}}
>
<Icon icon="check-circle" />{' '}
<span class="menu-grow">Media only</span>
</MenuItem>
<MenuDivider />
</>
)}
<FocusableItem className="menu-field" disabled={reachLimit}>
{({ ref }) => (
<form

Wyświetl plik

@ -12,7 +12,7 @@ import { getAuthorizationURL, registerApplication } from '../utils/auth';
import store from '../utils/store';
import useTitle from '../utils/useTitle';
const { PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE } = import.meta.env;
const { PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE, PHANPY_SCHEME: SCHEME = 'https' } = import.meta.env;
function Login() {
useTitle('Log in');
@ -85,9 +85,11 @@ function Login() {
.replace(/^@?[^@]+@/, '') // Remove @?acct@
.trim()
: null;
const instanceTextLooksLikeDomain =
/[^\s\r\n\t\/\\]+\.[^\s\r\n\t\/\\]+/.test(cleanInstanceText) &&
!/[\s\/\\@]/.test(cleanInstanceText);
const instanceTextLooksLikeDomain =
(/[^\s\r\n\t\/\\]+\.[^\s\r\n\t\/\\]+/.test(cleanInstanceText) &&
!/[\s\/\\@]/.test(cleanInstanceText)) || SCHEME === "http";
console.log(SCHEME)
const instancesSuggestions = cleanInstanceText
? instancesList

Wyświetl plik

@ -72,6 +72,13 @@ function Notifications({ columnMode }) {
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 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);
if (firstLoad) {

Wyświetl plik

@ -33,6 +33,7 @@ function Public({ local, columnMode, ...props }) {
publicIterator.current = masto.v1.timelines.public.list({
limit: LIMIT,
local: isLocal,
remote: !isLocal, // Pixelfed
});
}
const results = await publicIterator.current.next();

Wyświetl plik

@ -19,6 +19,7 @@ import pmem from '../utils/pmem';
import shortenNumber from '../utils/shorten-number';
import states from '../utils/states';
import { saveStatus } from '../utils/states';
import supports from '../utils/supports';
import useTitle from '../utils/useTitle';
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 }) {
const snapStates = useSnapshot(states);
const params = columnMode ? {} : useParams();
@ -48,36 +60,39 @@ function Trending({ columnMode, ...props }) {
const [hashtags, setHashtags] = useState([]);
const [links, setLinks] = useState([]);
const trendIterator = useRef();
async function fetchTrend(firstLoad) {
if (firstLoad || !trendIterator.current) {
trendIterator.current = masto.v1.trends.statuses.list({
limit: LIMIT,
});
trendIterator.current = fetchTrends(masto);
// Get hashtags
try {
const iterator = masto.v1.trends.tags.list();
const { value: tags } = await iterator.next();
console.log('tags', tags);
if (tags?.length) {
setHashtags(tags);
if (supports('@mastodon/trending-hashtags')) {
try {
const iterator = masto.v1.trends.tags.list();
const { value: tags } = await iterator.next();
console.log('tags', tags);
if (tags?.length) {
setHashtags(tags);
}
} catch (e) {
console.error(e);
}
} catch (e) {
console.error(e);
}
// Get links
try {
const { value } = await fetchLinks(masto, instance);
// 4 types available: link, photo, video, rich
// Only want links for now
const links = value?.filter?.((link) => link.type === 'link');
console.log('links', links);
if (links?.length) {
setLinks(links);
if (supports('@mastodon/trending-links')) {
try {
const { value } = await fetchLinks(masto, instance);
// 4 types available: link, photo, video, rich
// Only want links for now
const links = value?.filter?.((link) => link.type === 'link');
console.log('links', links);
if (links?.length) {
setLinks(links);
}
} catch (e) {
console.error(e);
}
} catch (e) {
console.error(e);
}
}
const results = await trendIterator.current.next();

Wyświetl plik

@ -7,8 +7,11 @@ import {
getAccountByInstance,
getCurrentAccount,
saveAccount,
setCurrentAccountID,
} from './store-utils';
const { PHANPY_SCHEME: SCHEME = 'https' } = import.meta.env;
// Default *fallback* instance
const DEFAULT_INSTANCE = 'mastodon.social';
@ -36,7 +39,9 @@ export function initClient({ instance, accessToken }) {
.replace(/\/+$/, '')
.toLowerCase();
}
const url = instance ? `https://${instance}` : `https://${DEFAULT_INSTANCE}`;
const url = instance
? `${SCHEME}://${instance}`
: `${SCHEME}://${DEFAULT_INSTANCE}`;
const masto = createRestAPIClient({
url,
@ -118,7 +123,7 @@ export async function initAccount(client, instance, accessToken, vapidKey) {
const mastoAccount = await masto.v1.accounts.verifyCredentials();
console.log('CURRENTACCOUNT SET', mastoAccount.id);
store.session.set('currentAccount', mastoAccount.id);
setCurrentAccountID(mastoAccount.id);
saveAccount({
info: mastoAccount,

Wyświetl plik

@ -1,5 +1,8 @@
const { PHANPY_CLIENT_NAME: CLIENT_NAME, PHANPY_WEBSITE: WEBSITE } = import.meta
.env;
const {
PHANPY_CLIENT_NAME: CLIENT_NAME,
PHANPY_WEBSITE: WEBSITE,
PHANPY_SCHEME: SCHEME = 'https',
} = import.meta.env;
const SCOPES = 'read write follow push';
@ -11,7 +14,7 @@ export async function registerApplication({ instanceURL }) {
website: WEBSITE,
});
const registrationResponse = await fetch(
`https://${instanceURL}/api/v1/apps`,
`${SCHEME}://${instanceURL}/api/v1/apps`,
{
method: 'POST',
headers: {
@ -33,7 +36,7 @@ export async function getAuthorizationURL({ instanceURL, client_id }) {
// redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
response_type: 'code',
});
const authorizationURL = `https://${instanceURL}/oauth/authorize?${authorizationParams.toString()}`;
const authorizationURL = `${SCHEME}://${instanceURL}/oauth/authorize?${authorizationParams.toString()}`;
return authorizationURL;
}
@ -51,7 +54,7 @@ export async function getAccessToken({
code,
scope: SCOPES,
});
const tokenResponse = await fetch(`https://${instanceURL}/oauth/token`, {
const tokenResponse = await fetch(`${SCHEME}://${instanceURL}/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',

Wyświetl plik

@ -1,5 +1,5 @@
import mem from './mem';
import store from './store';
import { getCurrentAccountID } from './store-utils';
function _isFiltered(filtered, filterContext) {
if (!filtered?.length) return false;
@ -43,7 +43,7 @@ export function filteredItem(item, filterContext, currentAccountID) {
export function filteredItems(items, filterContext) {
if (!items?.length) return [];
if (!filterContext) return items;
const currentAccountID = store.session.get('currentAccount');
const currentAccountID = getCurrentAccountID();
return items.filter((item) =>
filteredItem(item, filterContext, currentAccountID),
);

Wyświetl plik

@ -1,11 +1,11 @@
import { api } from './api';
import store from './store';
import { getCurrentAccountID } from './store-utils';
export async function fetchRelationships(accounts, relationshipsMap = {}) {
if (!accounts?.length) return;
const { masto } = api();
const currentAccount = store.session.get('currentAccount');
const currentAccount = getCurrentAccountID();
const uniqueAccountIds = accounts.reduce((acc, a) => {
// 1. Ignore duplicate accounts
// 2. Ignore accounts that are already inside relationshipsMap

Wyświetl plik

@ -16,13 +16,40 @@ export function getAccountByInstance(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() {
if (!window.__IGNORE_GET_ACCOUNT_ERROR__) {
// Track down getCurrentAccount() calls before account-based states are initialized
console.error('getCurrentAccount() called before states are initialized');
if (import.meta.env.DEV) console.trace();
}
const currentAccount = store.session.get('currentAccount');
const currentAccount = getCurrentAccountID();
const account = getAccount(currentAccount);
return account;
}
@ -48,7 +75,7 @@ export function saveAccount(account) {
accounts.push(account);
}
store.local.setJSON('accounts', accounts);
store.session.set('currentAccount', account.info.id);
setCurrentAccountID(account.info.id);
}
export function updateAccount(accountInfo) {

Wyświetl plik

@ -4,6 +4,21 @@ import features from '../data/features.json';
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 = {};
function supports(feature) {
@ -11,6 +26,11 @@ function supports(feature) {
const { version, domain } = getCurrentInstance();
const key = `${domain}-${feature}`;
if (supportsCache[key]) return supportsCache[key];
if (platformFeatures[feature]) {
return (supportsCache[key] = platformFeatures[feature].test(version));
}
const range = features[feature];
if (!range) return false;
return (supportsCache[key] = satisfies(version, range, {