mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-07 17:35:28 +00:00
Port attachments and embeds.
This commit is contained in:
97
src/components/common/messaging/embed/Embed.module.scss
Normal file
97
src/components/common/messaging/embed/Embed.module.scss
Normal file
@@ -0,0 +1,97 @@
|
||||
.embed {
|
||||
margin: .2em 0;
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.image {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.website {
|
||||
gap: 6px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> div:nth-child(1) {
|
||||
gap: 6px;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
border-inline-start-width: 4px;
|
||||
border-inline-start-style: solid;
|
||||
|
||||
padding: 12px;
|
||||
width: fit-content;
|
||||
border-radius: 4px;
|
||||
background: var(--primary-header);
|
||||
|
||||
.siteinfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
user-select: none;
|
||||
|
||||
.favicon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.site {
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--secondary-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.author {
|
||||
font-size: 1em;
|
||||
color: var(--primary-text);
|
||||
display: inline-block;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
font-size: 1.1em;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
white-space: pre-wrap;
|
||||
// -webkit-line-clamp: 6;
|
||||
// -webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img.image {
|
||||
cursor: pointer;
|
||||
object-fit: contain;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/components/common/messaging/embed/Embed.tsx
Normal file
145
src/components/common/messaging/embed/Embed.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import classNames from 'classnames';
|
||||
import EmbedMedia from './EmbedMedia';
|
||||
import styles from "./Embed.module.scss";
|
||||
import { useContext } from 'preact/hooks';
|
||||
import { Embed as EmbedRJS } from "revolt.js/dist/api/objects";
|
||||
import { useIntermediate } from '../../../../context/intermediate/Intermediate';
|
||||
import { MessageAreaWidthContext } from '../../../../pages/channels/messaging/MessageArea';
|
||||
|
||||
interface Props {
|
||||
embed: EmbedRJS;
|
||||
}
|
||||
|
||||
const MAX_EMBED_WIDTH = 480;
|
||||
const MAX_EMBED_HEIGHT = 640;
|
||||
const CONTAINER_PADDING = 24;
|
||||
const MAX_PREVIEW_SIZE = 150;
|
||||
|
||||
export default function Embed({ embed }: Props) {
|
||||
// ! FIXME: temp code
|
||||
// ! add proxy function to client
|
||||
function proxyImage(url: string) {
|
||||
return 'https://jan.revolt.chat/proxy?url=' + encodeURIComponent(url);
|
||||
}
|
||||
|
||||
const { openScreen } = useIntermediate();
|
||||
const maxWidth = Math.min(useContext(MessageAreaWidthContext) - CONTAINER_PADDING, MAX_EMBED_WIDTH);
|
||||
|
||||
function calculateSize(w: number, h: number): { width: number, height: number } {
|
||||
let limitingWidth = Math.min(
|
||||
maxWidth,
|
||||
w
|
||||
);
|
||||
|
||||
let limitingHeight = Math.min(
|
||||
MAX_EMBED_HEIGHT,
|
||||
h
|
||||
);
|
||||
|
||||
// Calculate smallest possible WxH.
|
||||
let width = Math.min(
|
||||
limitingWidth,
|
||||
limitingHeight * (w / h)
|
||||
);
|
||||
|
||||
let height = Math.min(
|
||||
limitingHeight,
|
||||
limitingWidth * (h / w)
|
||||
);
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
switch (embed.type) {
|
||||
case 'Website': {
|
||||
// ! FIXME: move this to january
|
||||
/*if (embed.url && YOUTUBE_RE.test(embed.url)) {
|
||||
embed.color = '#FF424F';
|
||||
}
|
||||
|
||||
if (embed.url && TWITCH_RE.test(embed.url)) {
|
||||
embed.color = '#7B68EE';
|
||||
}
|
||||
|
||||
if (embed.url && SPOTIFY_RE.test(embed.url)) {
|
||||
embed.color = '#1ABC9C';
|
||||
}
|
||||
|
||||
if (embed.url && SOUNDCLOUD_RE.test(embed.url)) {
|
||||
embed.color = '#FF7F50';
|
||||
}*/
|
||||
|
||||
// Determine special embed size.
|
||||
let mw, mh;
|
||||
let largeMedia = (embed.special && embed.special.type !== 'None') || embed.image?.size === 'Large';
|
||||
switch (embed.special?.type) {
|
||||
case 'YouTube':
|
||||
case 'Bandcamp': {
|
||||
mw = embed.video?.width ?? 1280;
|
||||
mh = embed.video?.height ?? 720;
|
||||
break;
|
||||
}
|
||||
case 'Twitch': {
|
||||
mw = 1280;
|
||||
mh = 720;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (embed.image?.size === 'Preview') {
|
||||
mw = MAX_EMBED_WIDTH;
|
||||
mh = Math.min(embed.image.height ?? 0, MAX_PREVIEW_SIZE);
|
||||
} else {
|
||||
mw = embed.image?.width ?? MAX_EMBED_WIDTH;
|
||||
mh = embed.image?.height ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let { width, height } = calculateSize(mw, mh);
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.embed, styles.website)}
|
||||
style={{
|
||||
borderInlineStartColor: embed.color ?? 'var(--tertiary-background)',
|
||||
width: width + CONTAINER_PADDING
|
||||
}}>
|
||||
<div>
|
||||
{ embed.site_name && <div className={styles.siteinfo}>
|
||||
{ embed.icon_url && <img className={styles.favicon} src={proxyImage(embed.icon_url)} draggable={false} onError={e => e.currentTarget.style.display = 'none'} /> }
|
||||
<div className={styles.site}>{ embed.site_name } </div>
|
||||
</div> }
|
||||
|
||||
{/*<span><a href={embed.url} target={"_blank"} className={styles.author}>Author</a></span>*/}
|
||||
{ embed.title && <span><a href={embed.url} target={"_blank"} className={styles.title}>{ embed.title }</a></span> }
|
||||
{ embed.description && <div className={styles.description}>{ embed.description }</div> }
|
||||
|
||||
{ largeMedia && <EmbedMedia embed={embed} height={height} /> }
|
||||
</div>
|
||||
{
|
||||
!largeMedia && <div>
|
||||
<EmbedMedia embed={embed} width={height * ((embed.image?.width ?? 0) / (embed.image?.height ?? 0))} height={height} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
case 'Image': {
|
||||
return (
|
||||
<img className={classNames(styles.embed, styles.image)}
|
||||
style={calculateSize(embed.width, embed.height)}
|
||||
src={proxyImage(embed.url)}
|
||||
type="text/html"
|
||||
frameBorder="0"
|
||||
onClick={() =>
|
||||
openScreen({ id: "image_viewer", embed })
|
||||
}
|
||||
onMouseDown={ev =>
|
||||
ev.button === 1 &&
|
||||
window.open(embed.url, "_blank")
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
78
src/components/common/messaging/embed/EmbedMedia.tsx
Normal file
78
src/components/common/messaging/embed/EmbedMedia.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import styles from './Embed.module.scss';
|
||||
import { Embed } from "revolt.js/dist/api/objects";
|
||||
import { useIntermediate } from '../../../../context/intermediate/Intermediate';
|
||||
|
||||
interface Props {
|
||||
embed: Embed;
|
||||
width?: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export default function EmbedMedia({ embed, width, height }: Props) {
|
||||
// ! FIXME: temp code
|
||||
// ! add proxy function to client
|
||||
function proxyImage(url: string) {
|
||||
return 'https://jan.revolt.chat/proxy?url=' + encodeURIComponent(url);
|
||||
}
|
||||
|
||||
if (embed.type !== 'Website') return null;
|
||||
const { openScreen } = useIntermediate();
|
||||
|
||||
switch (embed.special?.type) {
|
||||
case 'YouTube': return (
|
||||
<iframe
|
||||
src={`https://www.youtube-nocookie.com/embed/${embed.special.id}?modestbranding=1`}
|
||||
allowFullScreen
|
||||
style={{ height }} />
|
||||
)
|
||||
case 'Twitch': return (
|
||||
<iframe
|
||||
src={`https://player.twitch.tv/?${embed.special.content_type.toLowerCase()}=${embed.special.id}&parent=${window.location.hostname}&autoplay=false`}
|
||||
frameBorder="0"
|
||||
allowFullScreen
|
||||
scrolling="no"
|
||||
style={{ height, }} />
|
||||
)
|
||||
case 'Spotify': return (
|
||||
<iframe
|
||||
src={`https://open.spotify.com/embed/${embed.special.content_type}/${embed.special.id}`}
|
||||
frameBorder="0"
|
||||
allowFullScreen
|
||||
allowTransparency
|
||||
style={{ height }} />
|
||||
)
|
||||
case 'Soundcloud': return (
|
||||
<iframe
|
||||
src={`https://w.soundcloud.com/player/?url=${encodeURIComponent(embed.url as string)}&color=%23FF7F50&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true`}
|
||||
frameBorder="0"
|
||||
scrolling="no"
|
||||
style={{ height }} />
|
||||
)
|
||||
case 'Bandcamp': {
|
||||
return <iframe
|
||||
src={`https://bandcamp.com/EmbeddedPlayer/${embed.special.content_type.toLowerCase()}=${embed.special.id}/size=large/bgcol=181a1b/linkcol=056cc4/tracklist=false/transparent=true/`}
|
||||
seamless
|
||||
style={{ height }} />;
|
||||
}
|
||||
default: {
|
||||
if (embed.image) {
|
||||
let url = embed.image.url;
|
||||
return (
|
||||
<img
|
||||
className={styles.image}
|
||||
src={proxyImage(url)}
|
||||
style={{ width, height }}
|
||||
onClick={() =>
|
||||
openScreen({ id: "image_viewer", embed: embed.image })
|
||||
}
|
||||
onMouseDown={ev =>
|
||||
ev.button === 1 &&
|
||||
window.open(url, "_blank")
|
||||
} />
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
26
src/components/common/messaging/embed/EmbedMediaActions.tsx
Normal file
26
src/components/common/messaging/embed/EmbedMediaActions.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import styles from './Embed.module.scss';
|
||||
import IconButton from '../../../ui/IconButton';
|
||||
import { ExternalLink } from '@styled-icons/feather';
|
||||
import { EmbedImage } from "revolt.js/dist/api/objects";
|
||||
|
||||
interface Props {
|
||||
embed: EmbedImage;
|
||||
}
|
||||
|
||||
export default function EmbedMediaActions({ embed }: Props) {
|
||||
const filename = embed.url.split('/').pop();
|
||||
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
<div className={styles.info}>
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{embed.width + 'x' + embed.height}</span>
|
||||
</div>
|
||||
<a href={embed.url} target="_blank">
|
||||
<IconButton>
|
||||
<ExternalLink size={24} />
|
||||
</IconButton>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user