mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-10-18 06:03:22 +00:00
embed system
This commit is contained in:
parent
9bf0c04692
commit
467e37e621
@ -1,17 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { bech32 } from 'bech32';
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { ComponentChild } from 'preact';
|
||||
import { Link, route } from 'preact-router';
|
||||
|
||||
import EventComponent from './components/events/EventComponent';
|
||||
import SafeImg from './components/SafeImg';
|
||||
import Torrent from './components/Torrent';
|
||||
import Name from './components/user/Name';
|
||||
import Key from './nostr/Key';
|
||||
import { language, translate as t } from './translations/Translation.mjs';
|
||||
import localState from './LocalState';
|
||||
@ -22,14 +14,10 @@ const pubKeyRegex =
|
||||
/(?:^|\s|nostr:|(?:https?:\/\/[\w./]+)|iris\.to\/|snort\.social\/p\/|damus\.io\/)+((?:@)?npub[a-zA-Z0-9]{59,60})(?![\w/])/gi;
|
||||
const noteRegex =
|
||||
/(?:^|\s|nostr:|(?:https?:\/\/[\w./]+)|iris\.to\/|snort\.social\/e\/|damus\.io\/)+((?:@)?note[a-zA-Z0-9]{59,60})(?![\w/])/gi;
|
||||
const nip19Regex = /\bnostr:(n(?:event|profile)1\w+)\b/g;
|
||||
|
||||
const hashtagRegex = /(#[^\s!@#$%^&*()=+./,[{\]};:'"?><]+)/g;
|
||||
|
||||
let settings: any = {};
|
||||
localState.get('settings').on((s) => (settings = s));
|
||||
let existingIrisToAddress: any = {};
|
||||
localState.get('settings').put({}); // ?
|
||||
localState.get('existingIrisToAddress').on((a) => (existingIrisToAddress = a));
|
||||
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
@ -124,419 +112,6 @@ export default {
|
||||
window.open(link, '_self');
|
||||
},
|
||||
|
||||
highlightEverything(s: string, event?: any, opts: any = { showMentionedMessages: true }): any[] {
|
||||
let replacedText = reactStringReplace(s, emojiRegex, (match, i) => {
|
||||
return (
|
||||
<span key={match + i} className="emoji">
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
if (opts.showMentionedMessages) {
|
||||
replacedText = reactStringReplace(replacedText, noteRegex, (match, i) => {
|
||||
return (
|
||||
<EventComponent
|
||||
key={match + i}
|
||||
id={Key.toNostrHexAddress(match) || ''}
|
||||
asInlineQuote={true}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableTwitter !== false) {
|
||||
const twitterRegex = /(?:^|\s)(?:@)?(https?:\/\/twitter.com\/\w+\/status\/\d+\S*)(?![\w/])/g;
|
||||
replacedText = reactStringReplace(replacedText, twitterRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
style={{
|
||||
'max-width': '350px',
|
||||
height: '450px',
|
||||
'background-color': 'white',
|
||||
display: 'block',
|
||||
}}
|
||||
key={match + i}
|
||||
scrolling="no"
|
||||
height={250}
|
||||
width={550}
|
||||
src={`https://twitframe.com/show?url=${encodeURIComponent(match)}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableVideos !== false) {
|
||||
const videoRegex =
|
||||
/(https?:\/\/[^?\s]+\/[^?\s]+\.(?:mp4|mkv|avi|flv|wmv|mov|webm)(?:\?\S*)?)\b/gi;
|
||||
replacedText = reactStringReplace(replacedText, videoRegex, (match, i) => {
|
||||
return (
|
||||
<video
|
||||
className="py-2 rounded max-h-[70vh] md:max-h-96 max-w-full"
|
||||
key={match + i}
|
||||
src={match}
|
||||
poster={`https://imgproxy.iris.to/thumbnail/428/${match}`}
|
||||
muted={!this.isMobile && settings.autoplayVideos !== false}
|
||||
autoPlay={!this.isMobile && settings.autoplayVideos !== false}
|
||||
playsInline
|
||||
controls
|
||||
loop
|
||||
onLoadedData={(e) => {
|
||||
if (!this.isMobile && settings.autoplayVideos) {
|
||||
(e.target as HTMLVideoElement).play();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableAudio !== false) {
|
||||
const audioRegex = /(https?:\/\/\S+\.(?:mp3|wav|ogg|flac))\b/gi;
|
||||
replacedText = reactStringReplace(replacedText, audioRegex, (match, i) => {
|
||||
return <audio key={match + i} src={match} controls={true} loop={true} />;
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableYoutube !== false) {
|
||||
const youtubeRegex =
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=|shorts\/|live\/))([\w-]{11})(?:\S+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, youtubeRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="400"
|
||||
src={`https://www.youtube.com/embed/${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableInstagram !== false) {
|
||||
const igRegex =
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:instagram\.com\/)((?:p|reel)\/[\w-]{11})(?:\S+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, igRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="instagram"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://instagram.com/${match}/embed`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Soundcloud
|
||||
if (settings.enableSoundCloud !== false) {
|
||||
const soundCloudRegex =
|
||||
/(?:https?:\/\/)?(?:www\.)?(soundcloud\.com\/(?!live)[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+)(?:\?.*)?/g;
|
||||
replacedText = reactStringReplace(replacedText, soundCloudRegex, (match, i) => {
|
||||
return (
|
||||
console.log('match: ' + match),
|
||||
console.log('match 0: ' + match[0]),
|
||||
console.log('match 1: ' + match[1]),
|
||||
console.log('match 2: ' + match[2]),
|
||||
(
|
||||
<iframe
|
||||
class="audio"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="380"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://w.soundcloud.com/player/?url=${match}`}
|
||||
frameBorder="0"
|
||||
allow="encrypted-media"
|
||||
/>
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableSpotify !== false) {
|
||||
const spotifyRegex =
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:open\.spotify\.com\/track\/)([\w-]+)(?:\S+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, spotifyRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="audio"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="200"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://open.spotify.com/embed/track/${match}?utm_source=oembed`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//spotify podcast episode
|
||||
if (settings.enableSpotify !== false) {
|
||||
const spotifyRegex =
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:open\.spotify\.com\/episode\/)([\w-]+)(?:\S+)?(?:t=(\d+))?/g;
|
||||
replacedText = reactStringReplace(replacedText, spotifyRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="audio"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="200"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://open.spotify.com/embed/episode/${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// SpotifyTrack album
|
||||
if (settings.enableSpotify !== false) {
|
||||
const spotifyRegex =
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:open\.spotify\.com\/album\/)([\w-]+)(?:\S+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, spotifyRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="audio"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://open.spotify.com/embed/album/${match}`}
|
||||
frameBorder="0"
|
||||
allow="encrypted-media"
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// SpotifyTrack playlist
|
||||
if (settings.enableSpotify !== false) {
|
||||
const spotifyPlaylistRegex =
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:open\.spotify\.com\/playlist\/)([\w-]+)(?:\S+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, spotifyPlaylistRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="audio"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="380"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://open.spotify.com/embed/playlist/${match}`}
|
||||
frameBorder="0"
|
||||
allow="encrypted-media"
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
// Apple Music
|
||||
|
||||
if (settings.enableAppleMusic !== false) {
|
||||
const appleMusicRegex = /(?:https?:\/\/)(?:.*?)(music\.apple\.com\/.*)/gi;
|
||||
replacedText = reactStringReplace(replacedText, appleMusicRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="applemusic"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="150"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.music.apple.com/${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Apple Podcast
|
||||
|
||||
if (settings.enableAppleMusic !== false) {
|
||||
const applePodcastRegex = /(?:https?:\/\/)?(?:www\.)?(podcasts\.apple\.com\/.*)/g;
|
||||
replacedText = reactStringReplace(replacedText, applePodcastRegex, (match, i) => {
|
||||
console.log('embed url: ' + match);
|
||||
const cssClass = match.includes('?i=') ? 'applepodcast-small' : 'applepodcast-large';
|
||||
return (
|
||||
<iframe
|
||||
// class="applepodcast"
|
||||
class={cssClass}
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="175"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableTidal !== false) {
|
||||
const tidalRegex = /(?:https?:\/\/)?(?:www\.)?(?:tidal\.com(?:\/browse)?\/track\/)([\d]+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, tidalRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="audio"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="200"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.tidal.com/tracks/${match}?layout=gridify`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Tiktok embed
|
||||
if (settings.enableTiktok !== false) {
|
||||
const tiktokRegex = /(?:https?:\/\/)?(?:www\.)?tiktok\.com\/.*?video\/(\d{1,19})/g;
|
||||
replacedText = reactStringReplace(replacedText, tiktokRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="tiktok"
|
||||
width="605"
|
||||
height="400"
|
||||
key={match + i}
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://www.tiktok.com/embed/v2/${match}`}
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// twitch.com/videos
|
||||
if (settings.enableTwitch !== false) {
|
||||
const twitchRegex = /(?:https?:\/\/)?(?:www\.)?(?:twitch\.tv\/videos\/)([\d]+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, twitchRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="video"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://player.twitch.tv/?video=${match}&parent=${window.location.hostname}&autoplay=false`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// twitch channels
|
||||
if (settings.enableTwitch !== false) {
|
||||
const twitchRegex = /(?:https?:\/\/)?(?:www\.)?(?:twitch\.tv\/)([\w-]+)?/g;
|
||||
replacedText = reactStringReplace(replacedText, twitchRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
class="video"
|
||||
scrolling="no"
|
||||
key={match + i}
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://player.twitch.tv/?channel=${match}&parent=${window.location.hostname}&autoplay=false`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// wavlake track/album/artist
|
||||
if (settings.enableWavlake !== false) {
|
||||
const wavlakeRegex =
|
||||
/https:\/\/(?:player\.)?wavlake\.com\/(?!feed\/|artists)(track\/[.a-zA-Z0-9-]+|album\/[.a-zA-Z0-9-]+|[.a-zA-Z0-9-]+)/i;
|
||||
replacedText = reactStringReplace(replacedText, wavlakeRegex, (match, i) => {
|
||||
return (
|
||||
<iframe
|
||||
key={match + i}
|
||||
height="380"
|
||||
width="100%"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.wavlake.com/${match}`}
|
||||
frameBorder="0"
|
||||
loading="lazy"
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.enableTorrent !== false) {
|
||||
const magnetRegex = /(magnet:\?xt=urn:btih:.*)/gi;
|
||||
replacedText = reactStringReplace(replacedText, magnetRegex, (match, i) => {
|
||||
// Torrent component
|
||||
console.log('magnet link', match);
|
||||
return <Torrent key={match + i} preview={true} torrentId={match} />;
|
||||
});
|
||||
}
|
||||
|
||||
// find .jpg .jpeg .gif .png .webp urls in msg.text and create img tag
|
||||
if (settings.enableImages !== false) {
|
||||
const imgRegex = /(https?:\/\/[^\s]*\.(?:jpg|jpeg|gif|png|webp)(\?[^\s]*)?( |$|\n))/gi;
|
||||
replacedText = reactStringReplace(replacedText, imgRegex, (match, i) => {
|
||||
return (
|
||||
<SafeImg
|
||||
className="py-2 md:rounded max-h-[70vh] md:max-h-96 max-w-full cursor-pointer"
|
||||
onClick={opts.onImageClick}
|
||||
src={match}
|
||||
key={match + i}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
replacedText = this.highlightText(replacedText, event, opts);
|
||||
|
||||
const lnRegex =
|
||||
/(lightning:[\w.-]+@[\w.-]+|lightning:\w+\?amount=\d+|(?:lightning:)?(?:lnurl|lnbc)[\da-z0-9]+)/gi;
|
||||
replacedText = reactStringReplace(replacedText, lnRegex, (match) => {
|
||||
if (!match.startsWith('lightning:')) {
|
||||
match = `lightning:${match}`;
|
||||
}
|
||||
return (
|
||||
<a href={match} onClick={(e) => this.handleLightningLinkClick(e)}>
|
||||
⚡ Pay with lightning
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
return replacedText;
|
||||
},
|
||||
|
||||
isMobile: (function () {
|
||||
let check = false;
|
||||
(function (a) {
|
||||
@ -553,125 +128,6 @@ export default {
|
||||
return check;
|
||||
})(),
|
||||
|
||||
// hashtags, usernames, links
|
||||
highlightText(s: ComponentChild[], event?: any, opts: any = {}) {
|
||||
s = reactStringReplace(s, pubKeyRegex, (match, i) => {
|
||||
match = match.replace(/@/g, '');
|
||||
const link = `/${match}`;
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
<a href={link} className="link">
|
||||
@<Name key={match + i} pub={match} hideBadge={true} />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
// nip19 decode
|
||||
s = reactStringReplace(s, nip19Regex, (match, i) => {
|
||||
try {
|
||||
const { type, data } = nip19.decode(match);
|
||||
if (type === 'nprofile') {
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
<Link href={`/${data.pubkey}`} className="link">
|
||||
@<Name key={match + i} pub={data.pubkey} hideBadge={true} />
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
} else if (type === 'nevent') {
|
||||
// same as note
|
||||
return <EventComponent key={match + i} id={data.id} asInlineQuote={true} />;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return match;
|
||||
}
|
||||
});
|
||||
|
||||
s = reactStringReplace(s, noteRegex, (match) => {
|
||||
match = match.replace(/@/g, '');
|
||||
const link = `/${match}`;
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
<a href={link} className="link">
|
||||
{match}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
s = reactStringReplace(
|
||||
s,
|
||||
/((?:https?:\/\/\S*[^.?,)\s])|(?:iris\.to\/\S*[^.?,)\s]))/gi,
|
||||
(match, i) => {
|
||||
const url = match.replace(/^(https:\/\/)?iris.to/, '');
|
||||
const isIris = url !== match;
|
||||
return (
|
||||
<a
|
||||
key={match + i}
|
||||
className="link"
|
||||
target="_blank"
|
||||
onClick={(e) => {
|
||||
if (isIris) {
|
||||
e.preventDefault();
|
||||
route(url);
|
||||
}
|
||||
}}
|
||||
href={url}
|
||||
>
|
||||
{match.replace(/^https?:\/\//, '').replace(/\/$/, '')}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (event?.tags) {
|
||||
// replace "#[n]" tags with links to the user: event.tags[n][1]
|
||||
s = reactStringReplace(s, /#\[(\d+)\]/g, (match, i) => {
|
||||
const tag = event.tags[parseInt(match, 10)];
|
||||
if (tag) {
|
||||
const tagTarget = tag[1].replace('@', '');
|
||||
if (tag[0] === 'p') {
|
||||
// profile
|
||||
const link = `/${Key.toNostrBech32Address(tagTarget, 'npub')}`;
|
||||
return (
|
||||
<a href={link}>
|
||||
@<Name key={tagTarget + i} pub={tagTarget} hideBadge={true} />
|
||||
</a>
|
||||
);
|
||||
} else if (tag[0] === 'e') {
|
||||
return opts.showMentionedMessages ? (
|
||||
<EventComponent key={tagTarget + i} id={tagTarget} asInlineQuote={true} />
|
||||
) : (
|
||||
<Link className="link" href={`/${Key.toNostrBech32Address(tagTarget, 'note')}`}>
|
||||
{tag[1]}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
// highlight hashtags, link to /search/${encodeUriComponent(hashtag)}
|
||||
s = reactStringReplace(s, hashtagRegex, (match) => {
|
||||
return (
|
||||
<Link className="link" href={`/search/${encodeURIComponent(match)}`}>
|
||||
{match}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
// remove leading and trailing newlines
|
||||
s = s.map((x) => (typeof x === 'string' ? x.replace(/^\n+|\n+$/g, '') : x));
|
||||
|
||||
return s;
|
||||
},
|
||||
|
||||
copyToClipboard(text: string): boolean {
|
||||
if (window.clipboardData && window.clipboardData.setData) {
|
||||
// Internet Explorer-specific code path to prevent textarea being shown while dialog is visible.
|
||||
|
@ -2,8 +2,13 @@ import { memo } from 'react';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { Event } from 'nostr-tools';
|
||||
|
||||
import localState from '../LocalState';
|
||||
|
||||
import { allEmbeds, textEmbeds } from './embed';
|
||||
|
||||
let settings: any = {};
|
||||
localState.get('settings').on((s) => (settings = s));
|
||||
|
||||
const HyperText = memo(
|
||||
({ children, event, textOnly }: { children: string; event?: Event; textOnly?: boolean }) => {
|
||||
let processedChildren = [children.trim()];
|
||||
@ -11,6 +16,7 @@ const HyperText = memo(
|
||||
const embeds = textOnly ? textEmbeds : allEmbeds;
|
||||
|
||||
embeds.forEach((embed) => {
|
||||
if (settings[embed.settingsKey || ''] === false) return;
|
||||
processedChildren = reactStringReplace(processedChildren, embed.regex, (match, i) => {
|
||||
return embed.component({
|
||||
match,
|
||||
|
@ -9,8 +9,8 @@ import Key from '../nostr/Key';
|
||||
import { DecryptedEvent } from '../views/chat/ChatMessages';
|
||||
|
||||
import Name from './user/Name';
|
||||
import HyperText from './HyperText';
|
||||
import Torrent from './Torrent';
|
||||
import HyperText from "./HyperText";
|
||||
|
||||
type Props = {
|
||||
event: DecryptedEvent;
|
||||
|
@ -0,0 +1,11 @@
|
||||
import Embed from './index';
|
||||
|
||||
const Audio: Embed = {
|
||||
regex: /(https?:\/\/\S+\.(?:mp3|wav|ogg|flac))\b/gi,
|
||||
settingsKey: 'enableAudio',
|
||||
component: ({ match }) => {
|
||||
return <audio src={match} controls={true} loop={true} />;
|
||||
},
|
||||
};
|
||||
|
||||
export default Audio;
|
@ -4,9 +4,9 @@ import Embed from './index';
|
||||
|
||||
const Hashtag: Embed = {
|
||||
regex: /(?<=\s|^)(#\w+)/g,
|
||||
component: ({ match, key }) => {
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<Link key={key} href={`/search/${encodeURIComponent(match)}`} className="link">
|
||||
<Link href={`/search/${encodeURIComponent(match)}`} className="link">
|
||||
{' '}
|
||||
{match}{' '}
|
||||
</Link>
|
||||
|
@ -1,21 +1,22 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import Show from '../helpers/Show';
|
||||
import Modal from '../modal/Modal';
|
||||
import SafeImg from '../SafeImg';
|
||||
|
||||
import Embed from './index';
|
||||
import Show from "../helpers/Show";
|
||||
|
||||
const Image: Embed = {
|
||||
regex: /(https?:\/\/.*\.(?:png|jpg|jpeg|gif|svg|webp)(?:\?\S*)?)/gi,
|
||||
component: ({ match, key }) => {
|
||||
settingsKey: 'enableImages',
|
||||
component: ({ match }) => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const onClick = (e) => {
|
||||
e.stopPropagation();
|
||||
setShowModal(true);
|
||||
};
|
||||
return (
|
||||
<div key={key}>
|
||||
<div>
|
||||
<div className="relative w-full overflow-hidden object-contain my-2">
|
||||
<SafeImg
|
||||
onClick={onClick}
|
||||
|
@ -2,11 +2,11 @@ import Embed from './index';
|
||||
|
||||
const Instagram: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:instagram\.com\/)((?:p|reel)\/[\w-]{11})(?:\S+)?/g,
|
||||
component: ({ match, key }) => {
|
||||
settingsKey: 'enableInstagram',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="instagram"
|
||||
key={key}
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
|
@ -0,0 +1,21 @@
|
||||
import Helpers from '../../Helpers';
|
||||
|
||||
import Embed from './index';
|
||||
|
||||
const TorrentEmbed: Embed = {
|
||||
regex:
|
||||
/(lightning:[\w.-]+@[\w.-]+|lightning:\w+\?amount=\d+|(?:lightning:)?(?:lnurl|lnbc)[\da-z0-9]+)/gi,
|
||||
component: ({ match }) => {
|
||||
if (!match.startsWith('lightning:')) {
|
||||
match = `lightning:${match}`;
|
||||
}
|
||||
// TODO parse invoice and show amount
|
||||
return (
|
||||
<a href={match} onClick={(e) => Helpers.handleLightningLinkClick(e)}>
|
||||
⚡ Pay with lightning
|
||||
</a>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default TorrentEmbed;
|
@ -3,10 +3,10 @@ import Embed from './index';
|
||||
const SoundCloud: Embed = {
|
||||
regex:
|
||||
/(?:https?:\/\/)?(?:www\.)?(soundcloud\.com\/(?!live)[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+)(?:\?.*)?/g,
|
||||
component: ({ match, key }) => {
|
||||
settingsKey: 'enableSoundCloud',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
key={key}
|
||||
className="audio"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
|
@ -0,0 +1,23 @@
|
||||
import Embed from './index';
|
||||
|
||||
const Tidal: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:tidal\.com(?:\/browse)?\/track\/)([\d]+)?/g,
|
||||
settingsKey: 'enableTidal',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="audio"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="200"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.tidal.com/tracks/${match}?layout=gridify`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default Tidal;
|
@ -0,0 +1,22 @@
|
||||
import Embed from './index';
|
||||
|
||||
const TikTok: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?tiktok\.com\/.*?video\/(\d{1,19})/g,
|
||||
settingsKey: 'enableTikTok',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="tiktok"
|
||||
width="605"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://www.tiktok.com/embed/v2/${match}`}
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default TikTok;
|
@ -0,0 +1,13 @@
|
||||
import Torrent from '../Torrent';
|
||||
|
||||
import Embed from './index';
|
||||
|
||||
const TorrentEmbed: Embed = {
|
||||
regex: /(magnet:\?xt=urn:btih:.*)/gi,
|
||||
settingsKey: 'enableTorrent',
|
||||
component: ({ match }) => {
|
||||
return <Torrent preview={true} torrentId={match} />;
|
||||
},
|
||||
};
|
||||
|
||||
export default TorrentEmbed;
|
@ -2,7 +2,8 @@ import Embed from './index';
|
||||
|
||||
const Twitter: Embed = {
|
||||
regex: /(?:^|\s)(?:@)?(https?:\/\/twitter.com\/\w+\/status\/\d+\S*)(?![\w/])/g,
|
||||
component: ({ match, key }) => {
|
||||
settingsKey: 'enableTwitter',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
style={{
|
||||
@ -11,7 +12,6 @@ const Twitter: Embed = {
|
||||
backgroundColor: 'white',
|
||||
display: 'block',
|
||||
}}
|
||||
key={key}
|
||||
scrolling="no"
|
||||
height={250}
|
||||
width={550}
|
||||
|
@ -4,10 +4,10 @@ import Embed from './index';
|
||||
|
||||
const Url: Embed = {
|
||||
regex: /(https?:\/\/[^\s]+)/g,
|
||||
component: ({ match, key }) => {
|
||||
component: ({ match }) => {
|
||||
const url = match.replace(/^(https:\/\/)?iris.to/, '');
|
||||
return (
|
||||
<Link key={key} className="link" target="_blank" href={url}>
|
||||
<Link className="link" target="_blank" href={url}>
|
||||
{match.replace(/^https?:\/\//, '').replace(/\/$/, '')}
|
||||
</Link>
|
||||
);
|
||||
|
@ -2,8 +2,9 @@ import Embed from './index';
|
||||
|
||||
const Video: Embed = {
|
||||
regex: /(https?:\/\/.*\.(?:mp4|webm|ogg|mov)(?:\?\S*)?)/gi,
|
||||
component: ({ match, key }) => (
|
||||
<div key={key} className="relative w-full overflow-hidden object-contain my-2">
|
||||
settingsKey: 'enableVideo',
|
||||
component: ({ match }) => (
|
||||
<div className="relative w-full overflow-hidden object-contain my-2">
|
||||
<video
|
||||
className="rounded max-h-[70vh] md:max-h-96 max-w-full"
|
||||
src={match}
|
||||
|
@ -0,0 +1,21 @@
|
||||
import Embed from './index';
|
||||
|
||||
const WavLake: Embed = {
|
||||
regex:
|
||||
/https:\/\/(?:player\.)?wavlake\.com\/(?!feed\/|artists)(track\/[.a-zA-Z0-9-]+|album\/[.a-zA-Z0-9-]+|[.a-zA-Z0-9-]+)/i,
|
||||
settingsKey: 'enableWavLake',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
height="380"
|
||||
width="100%"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.wavlake.com/${match}`}
|
||||
frameBorder="0"
|
||||
loading="lazy"
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default WavLake;
|
@ -3,10 +3,10 @@ import Embed from './index';
|
||||
const YouTube: Embed = {
|
||||
regex:
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=|shorts\/|live\/))([\w-]{11})(?:\S+)?/g,
|
||||
component: ({ match, key }) => {
|
||||
settingsKey: 'enableYoutube',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
key={key}
|
||||
width="650"
|
||||
height="400"
|
||||
src={`https://www.youtube.com/embed/${match}`}
|
||||
|
23
src/js/components/embed/apple/AppleMusic.tsx
Normal file
23
src/js/components/embed/apple/AppleMusic.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import Embed from '../index';
|
||||
|
||||
const AppleMusic: Embed = {
|
||||
regex: /(?:https?:\/\/)(?:.*?)(music\.apple\.com\/.*)/gi,
|
||||
settingsKey: 'enableAppleMusic',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="applemusic"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="150"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.music.apple.com/${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default AppleMusic;
|
25
src/js/components/embed/apple/ApplePodcast.tsx
Normal file
25
src/js/components/embed/apple/ApplePodcast.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import Embed from '../index';
|
||||
|
||||
const ApplePodcast: Embed = {
|
||||
regex: /(?:https?:\/\/)(?:.*?)(music\.apple\.com\/.*)/gi,
|
||||
settingsKey: 'enableAppleMusic',
|
||||
component: ({ match }) => {
|
||||
const cssClass = match.includes('?i=') ? 'applepodcast-small' : 'applepodcast-large';
|
||||
return (
|
||||
<iframe
|
||||
// class="applepodcast"
|
||||
className={cssClass}
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="175"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default ApplePodcast;
|
@ -1,18 +1,31 @@
|
||||
import { Event } from 'nostr-tools';
|
||||
import { JSX } from 'preact';
|
||||
|
||||
import AppleMusic from './apple/AppleMusic';
|
||||
import ApplePodcast from './apple/ApplePodcast';
|
||||
import InlineMention from './nostr/InlineMention';
|
||||
import Nip19 from './nostr/Nip19';
|
||||
import NostrEvent from './nostr/NostrNote';
|
||||
import NostrNpub from './nostr/NostrNpub';
|
||||
import SpotifyAlbum from './spotify/SpotifyAlbum';
|
||||
import SpotifyPlaylist from './spotify/SpotifyPlaylist';
|
||||
import SpotifyPodcast from './spotify/SpotifyPodcast';
|
||||
import SpotifyTrack from './spotify/SpotifyTrack';
|
||||
import Twitch from './twitch/Twitch';
|
||||
import TwitchChannel from './twitch/TwitchChannel';
|
||||
import Audio from './Audio';
|
||||
import Hashtag from './Hashtag';
|
||||
import Image from './Image';
|
||||
import Instagram from './Instagram';
|
||||
import LightningUri from './LightningUri';
|
||||
import SoundCloud from './SoundCloud';
|
||||
import Tidal from './Tidal';
|
||||
import TikTok from './TikTok';
|
||||
import Torrent from './Torrent';
|
||||
import Twitter from './Twitter';
|
||||
import Url from './Url';
|
||||
import Video from './Video';
|
||||
import WavLake from './WavLake';
|
||||
import Youtube from './YouTube';
|
||||
|
||||
export type EmbedProps = {
|
||||
@ -25,9 +38,11 @@ export type EmbedProps = {
|
||||
type Embed = {
|
||||
regex: RegExp;
|
||||
component: (props: EmbedProps) => JSX.Element;
|
||||
settingsKey?: string;
|
||||
};
|
||||
|
||||
export const allEmbeds = [
|
||||
Audio,
|
||||
Image,
|
||||
Video,
|
||||
Youtube,
|
||||
@ -35,6 +50,18 @@ export const allEmbeds = [
|
||||
Twitter,
|
||||
SoundCloud,
|
||||
SpotifyTrack,
|
||||
SpotifyAlbum,
|
||||
SpotifyPodcast,
|
||||
SpotifyPlaylist,
|
||||
AppleMusic,
|
||||
ApplePodcast,
|
||||
Tidal,
|
||||
TikTok,
|
||||
Twitch,
|
||||
TwitchChannel,
|
||||
WavLake,
|
||||
Torrent,
|
||||
LightningUri,
|
||||
NostrNpub,
|
||||
NostrEvent,
|
||||
Nip19,
|
||||
|
@ -5,14 +5,13 @@ import { Link } from 'preact-router';
|
||||
|
||||
import EventComponent from '../../events/EventComponent';
|
||||
import Name from '../../user/Name';
|
||||
|
||||
import Embed from '../index';
|
||||
|
||||
const fail = (s: string) => `#[${s}]`;
|
||||
|
||||
const InlineMention: Embed = {
|
||||
regex: /#\[([0-9]+)]/g,
|
||||
component: ({ match, index, event, key }) => {
|
||||
component: ({ match, index, event }) => {
|
||||
if (!event?.tags) {
|
||||
console.log('no tags', event);
|
||||
return <>{fail(match)}</>;
|
||||
@ -25,7 +24,7 @@ const InlineMention: Embed = {
|
||||
const [type, id] = tag;
|
||||
if (type === 'p') {
|
||||
return (
|
||||
<Link key={key} href={`/${nip19.npubEncode(id)}`} className="link">
|
||||
<Link href={`/${nip19.npubEncode(id)}`} className="link">
|
||||
<Name pub={id} hideBadge={true} />
|
||||
</Link>
|
||||
);
|
||||
|
@ -3,21 +3,20 @@ import { Link } from 'preact-router';
|
||||
|
||||
import EventComponent from '../../events/EventComponent';
|
||||
import Name from '../../user/Name';
|
||||
|
||||
import Embed from '../index';
|
||||
|
||||
const nip19Regex = /\bnostr:(n(?:event|profile)1\w+)\b/g;
|
||||
|
||||
const NostrUser: Embed = {
|
||||
regex: nip19Regex,
|
||||
component: ({ match, key }) => {
|
||||
component: ({ match }) => {
|
||||
try {
|
||||
const { type, data } = nip19.decode(match);
|
||||
if (type === 'nprofile') {
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
<Link key={key} className="text-iris-blue hover:underline" href={`/${data.pubkey}`}>
|
||||
<Link className="text-iris-blue hover:underline" href={`/${data.pubkey}`}>
|
||||
<Name pub={data.pubkey} />
|
||||
</Link>
|
||||
</>
|
||||
@ -25,7 +24,7 @@ const NostrUser: Embed = {
|
||||
} else if (type === 'nevent') {
|
||||
// same as note
|
||||
return (
|
||||
<div key={key} className="rounded-lg border border-gray-500 my-2">
|
||||
<div className="rounded-lg border border-gray-500 my-2">
|
||||
<EventComponent id={data.id} asInlineQuote={true} />
|
||||
</div>
|
||||
);
|
||||
@ -33,7 +32,7 @@ const NostrUser: Embed = {
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
return <span key={key}>{match}</span>;
|
||||
return <span>{match}</span>;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import Key from '../../../nostr/Key';
|
||||
import EventComponent from '../../events/EventComponent';
|
||||
|
||||
import Embed from '../index';
|
||||
|
||||
const eventRegex =
|
||||
@ -8,9 +7,9 @@ const eventRegex =
|
||||
|
||||
const NostrUser: Embed = {
|
||||
regex: eventRegex,
|
||||
component: ({ match, key }) => {
|
||||
component: ({ match }) => {
|
||||
const hex = Key.toNostrHexAddress(match.replace('@', ''))!;
|
||||
return <EventComponent key={key} id={hex} asInlineQuote={true} />;
|
||||
return <EventComponent id={hex} asInlineQuote={true} />;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Link } from 'preact-router';
|
||||
|
||||
import Name from '../../user/Name';
|
||||
|
||||
import Embed from '../index';
|
||||
|
||||
const pubKeyRegex =
|
||||
@ -9,10 +8,10 @@ const pubKeyRegex =
|
||||
|
||||
const NostrNpub: Embed = {
|
||||
regex: pubKeyRegex,
|
||||
component: ({ match, key }) => {
|
||||
component: ({ match }) => {
|
||||
const pub = match.replace('@', '');
|
||||
return (
|
||||
<Link key={key} href={`/${pub}`} className="link mr-1">
|
||||
<Link href={`/${pub}`} className="link mr-1">
|
||||
<Name pub={pub} hideBadge={true} />
|
||||
</Link>
|
||||
);
|
||||
|
@ -0,0 +1,22 @@
|
||||
import Embed from '../index';
|
||||
|
||||
const SpotifyAlbum: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:open\.spotify\.com\/album\/)([\w-]+)(?:\S+)?/g,
|
||||
settingsKey: 'enableSpotify',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="audio"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://open.spotify.com/embed/album/${match}`}
|
||||
frameBorder="0"
|
||||
allow="encrypted-media"
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default SpotifyAlbum;
|
@ -0,0 +1,23 @@
|
||||
import Embed from '../index';
|
||||
|
||||
const SpotifyPlaylist: Embed = {
|
||||
regex: /(?:https?:\/\/)(?:.*?)(music\.apple\.com\/.*)/gi,
|
||||
settingsKey: 'enableSpotify',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="applemusic"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="150"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://embed.music.apple.com/${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default SpotifyPlaylist;
|
@ -0,0 +1,23 @@
|
||||
import Embed from '../index';
|
||||
|
||||
const SpotifyPodcast: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:open\.spotify\.com\/episode\/)([\w-]+)(?:\S+)?(?:t=(\d+))?/g,
|
||||
settingsKey: 'enableSpotify',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
class="audio"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="200"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://open.spotify.com/embed/episode/${match}`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default SpotifyPodcast;
|
@ -2,12 +2,12 @@ import Embed from '../index';
|
||||
|
||||
const SpotifyTrack: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:open\.spotify\.com\/track\/)([\w-]+)(?:\S+)?/g,
|
||||
component: ({ match, key }) => {
|
||||
settingsKey: 'enableSpotify',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="audio"
|
||||
scrolling="no"
|
||||
key={key}
|
||||
width="650"
|
||||
height="200"
|
||||
style={{ maxWidth: '100%' }}
|
||||
|
23
src/js/components/embed/twitch/Twitch.tsx
Normal file
23
src/js/components/embed/twitch/Twitch.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import Embed from '../index';
|
||||
|
||||
const Twitch: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:twitch\.tv\/videos\/)([\d]+)?/g,
|
||||
settingsKey: 'enableTwitch',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="video"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://player.twitch.tv/?video=${match}&parent=${window.location.hostname}&autoplay=false`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default Twitch;
|
23
src/js/components/embed/twitch/TwitchChannel.tsx
Normal file
23
src/js/components/embed/twitch/TwitchChannel.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import Embed from '../index';
|
||||
|
||||
const Twitch: Embed = {
|
||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:twitch\.tv\/)([\w-]+)?/g,
|
||||
settingsKey: 'enableTwitch',
|
||||
component: ({ match }) => {
|
||||
return (
|
||||
<iframe
|
||||
className="video"
|
||||
scrolling="no"
|
||||
width="650"
|
||||
height="400"
|
||||
style={{ maxWidth: '100%' }}
|
||||
src={`https://player.twitch.tv/?channel=${match}&parent=${window.location.hostname}&autoplay=false`}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default Twitch;
|
@ -11,13 +11,13 @@ import { ID } from '../../nostr/UserIds';
|
||||
import { translate as t } from '../../translations/Translation.mjs';
|
||||
import Follow from '../buttons/Follow';
|
||||
import Show from '../helpers/Show';
|
||||
import HyperText from '../HyperText';
|
||||
|
||||
import Avatar from './Avatar';
|
||||
import ProfileDropdown from './Dropdown';
|
||||
import Name from './Name';
|
||||
import ProfilePicture from './ProfilePicture';
|
||||
import Stats from './Stats';
|
||||
import HyperText from "../HyperText";
|
||||
|
||||
const ProfileCard = (props: { hexPub: string; npub: string }) => {
|
||||
const { hexPub, npub } = props;
|
||||
|
Loading…
Reference in New Issue
Block a user