mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-11-24 10:15:37 +03:00
First small step to resolving mastodon links
And open them inside Phanpy instead of like an external link
This commit is contained in:
parent
4b88c6ca65
commit
e6d6adb480
5 changed files with 125 additions and 16 deletions
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -20,6 +20,7 @@
|
|||
"masto": "~5.10.0",
|
||||
"mem": "~9.0.2",
|
||||
"p-retry": "~5.1.2",
|
||||
"p-throttle": "~5.0.0",
|
||||
"preact": "~10.12.1",
|
||||
"react-hotkeys-hook": "~4.3.7",
|
||||
"react-intersection-observer": "~9.4.2",
|
||||
|
@ -4985,6 +4986,17 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-throttle": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.0.0.tgz",
|
||||
"integrity": "sha512-iXBFjW4kP/5Ivw7uC9EDnj+/xo3pNn4Rws3zgMGPwXnWTv1M3P0LVdZxLrqRUI5JK0Fp3Du0bt6lCaVrI3WF7g==",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/param-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
|
||||
|
@ -10482,6 +10494,11 @@
|
|||
"retry": "^0.13.1"
|
||||
}
|
||||
},
|
||||
"p-throttle": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.0.0.tgz",
|
||||
"integrity": "sha512-iXBFjW4kP/5Ivw7uC9EDnj+/xo3pNn4Rws3zgMGPwXnWTv1M3P0LVdZxLrqRUI5JK0Fp3Du0bt6lCaVrI3WF7g=="
|
||||
},
|
||||
"param-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"masto": "~5.10.0",
|
||||
"mem": "~9.0.2",
|
||||
"p-retry": "~5.1.2",
|
||||
"p-throttle": "~5.0.0",
|
||||
"preact": "~10.12.1",
|
||||
"react-hotkeys-hook": "~4.3.7",
|
||||
"react-intersection-observer": "~9.4.2",
|
||||
|
|
|
@ -2,6 +2,7 @@ import './status.css';
|
|||
|
||||
import { Menu, MenuItem } from '@szhsin/react-menu';
|
||||
import mem from 'mem';
|
||||
import pThrottle from 'p-throttle';
|
||||
import { memo } from 'preact/compat';
|
||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import 'swiped-events';
|
||||
|
@ -26,6 +27,11 @@ import Link from './link';
|
|||
import Media from './media';
|
||||
import RelativeTime from './relative-time';
|
||||
|
||||
const throttle = pThrottle({
|
||||
limit: 1,
|
||||
interval: 1000,
|
||||
});
|
||||
|
||||
function fetchAccount(id, masto) {
|
||||
try {
|
||||
return masto.v1.accounts.fetch(id);
|
||||
|
@ -374,14 +380,26 @@ function Status({
|
|||
__html: enhanceContent(content, {
|
||||
emojis,
|
||||
postEnhanceDOM: (dom) => {
|
||||
// Remove target="_blank" from links
|
||||
dom
|
||||
.querySelectorAll('a.u-url[target="_blank"]')
|
||||
.forEach((a) => {
|
||||
// Remove target="_blank" from links
|
||||
if (!/http/i.test(a.innerText.trim())) {
|
||||
a.removeAttribute('target');
|
||||
}
|
||||
});
|
||||
// Unfurl Mastodon links
|
||||
dom
|
||||
.querySelectorAll(
|
||||
'a[href]:not(.u-url):not(.mention):not(.hashtag)',
|
||||
)
|
||||
.forEach((a) => {
|
||||
if (isMastodonLinkMaybe(a.href)) {
|
||||
unfurlMastodonLink(currentInstance, a.href).then(() => {
|
||||
a.removeAttribute('target');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
}),
|
||||
}}
|
||||
|
@ -463,7 +481,9 @@ function Status({
|
|||
!sensitive &&
|
||||
!spoilerText &&
|
||||
!poll &&
|
||||
!mediaAttachments.length && <Card card={card} />}
|
||||
!mediaAttachments.length && (
|
||||
<Card card={card} instance={currentInstance} />
|
||||
)}
|
||||
</div>
|
||||
{size === 'l' && (
|
||||
<>
|
||||
|
@ -702,7 +722,7 @@ function Status({
|
|||
);
|
||||
}
|
||||
|
||||
function Card({ card }) {
|
||||
function Card({ card, instance }) {
|
||||
const {
|
||||
blurhash,
|
||||
title,
|
||||
|
@ -729,12 +749,38 @@ function Card({ card }) {
|
|||
const isLandscape = width / height >= 1.2;
|
||||
const size = isLandscape ? 'large' : '';
|
||||
|
||||
const [cardStatusURL, setCardStatusURL] = useState(null);
|
||||
// const [cardStatusID, setCardStatusID] = useState(null);
|
||||
useEffect(() => {
|
||||
if (hasText && image && isMastodonLinkMaybe(url)) {
|
||||
unfurlMastodonLink(instance, url).then((result) => {
|
||||
if (!result) return;
|
||||
const { id, url } = result;
|
||||
setCardStatusURL('#' + url);
|
||||
|
||||
// NOTE: This is for quote post
|
||||
// (async () => {
|
||||
// const { masto } = api({ instance });
|
||||
// const status = await masto.v1.statuses.fetch(id);
|
||||
// saveStatus(status, instance);
|
||||
// setCardStatusID(id);
|
||||
// })();
|
||||
});
|
||||
}
|
||||
}, [hasText, image]);
|
||||
|
||||
// if (cardStatusID) {
|
||||
// return (
|
||||
// <Status statusID={cardStatusID} instance={instance} size="s" readOnly />
|
||||
// );
|
||||
// }
|
||||
|
||||
if (hasText && image) {
|
||||
const domain = new URL(url).hostname.replace(/^www\./, '');
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
href={cardStatusURL || url}
|
||||
target={cardStatusURL ? null : '_blank'}
|
||||
rel="nofollow noopener noreferrer"
|
||||
class={`card link ${size}`}
|
||||
>
|
||||
|
@ -1129,4 +1175,51 @@ export function formatDuration(time) {
|
|||
}
|
||||
}
|
||||
|
||||
function isMastodonLinkMaybe(url) {
|
||||
return /^https:\/\/.*\/\d+$/i.test(url);
|
||||
}
|
||||
|
||||
const denylistDomains = /(twitter|github)\.com/i;
|
||||
|
||||
function _unfurlMastodonLink(instance, url) {
|
||||
if (denylistDomains.test(url)) {
|
||||
return;
|
||||
}
|
||||
const instanceRegex = new RegExp(instance + '/');
|
||||
if (instanceRegex.test(states.unfurledLinks[url]?.url)) {
|
||||
return Promise.resolve(states.unfurledLinks[url]);
|
||||
}
|
||||
console.debug('🦦 Unfurling URL', url);
|
||||
const { masto } = api({ instance });
|
||||
return masto.v2
|
||||
.search({
|
||||
q: url,
|
||||
type: 'statuses',
|
||||
resolve: true,
|
||||
limit: 1,
|
||||
})
|
||||
.then((results) => {
|
||||
if (results.statuses.length > 0) {
|
||||
const status = results.statuses[0];
|
||||
const { id } = status;
|
||||
const statusURL = `/${instance}/s/${id}`;
|
||||
const result = {
|
||||
id,
|
||||
url: statusURL,
|
||||
};
|
||||
console.debug('🦦 Unfurled URL', url, id, statusURL);
|
||||
states.unfurledLinks[url] = result;
|
||||
return result;
|
||||
} else {
|
||||
throw new Error('No results');
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn(e);
|
||||
// Silently fail
|
||||
});
|
||||
}
|
||||
|
||||
const unfurlMastodonLink = throttle(_unfurlMastodonLink);
|
||||
|
||||
export default memo(Status);
|
||||
|
|
|
@ -4,13 +4,9 @@ function handleContentLinks(opts) {
|
|||
const { mentions = [], instance } = opts || {};
|
||||
return (e) => {
|
||||
let { target } = e;
|
||||
if (target.parentNode.tagName.toLowerCase() === 'a') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (
|
||||
target.tagName.toLowerCase() === 'a' &&
|
||||
target.classList.contains('u-url')
|
||||
) {
|
||||
target = target.closest('a');
|
||||
if (!target) return;
|
||||
if (target.classList.contains('u-url')) {
|
||||
const targetText = (
|
||||
target.querySelector('span') || target
|
||||
).innerText.trim();
|
||||
|
@ -39,16 +35,17 @@ function handleContentLinks(opts) {
|
|||
instance,
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
target.tagName.toLowerCase() === 'a' &&
|
||||
target.classList.contains('hashtag')
|
||||
) {
|
||||
} else if (target.classList.contains('hashtag')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const tag = target.innerText.replace(/^#/, '').trim();
|
||||
const hashURL = instance ? `#/${instance}/t/${tag}` : `#/t/${tag}`;
|
||||
console.log({ hashURL });
|
||||
location.hash = hashURL;
|
||||
} else if (states.unfurledLinks[target.href]?.url) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
location.hash = `#${states.unfurledLinks[target.href].url}`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ const states = proxy({
|
|||
reloadStatusPage: 0,
|
||||
spoilers: {},
|
||||
scrollPositions: {},
|
||||
unfurledLinks: {},
|
||||
// Modals
|
||||
showCompose: false,
|
||||
showSettings: false,
|
||||
|
|
Loading…
Reference in a new issue