mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-10-03 02:10:55 +00:00
Fix Exception when grouping images in gallery
This commit is contained in:
parent
a5a7359423
commit
9cf6bf5ffe
@ -44,7 +44,6 @@ const App: Component = () => {
|
||||
<HomeProvider>
|
||||
<ExploreProvider>
|
||||
<ThreadProvider>
|
||||
<input id="defocus" class={styles.invisible}/>
|
||||
<Router />
|
||||
</ThreadProvider>
|
||||
</ExploreProvider>
|
||||
|
@ -1,29 +1,18 @@
|
||||
import { useIntl } from '@cookbook/solid-intl';
|
||||
import { Component, createEffect, createSignal, For, Match, Show, Switch } from 'solid-js';
|
||||
import { useAccountContext } from '../../contexts/AccountContext';
|
||||
import { useSettingsContext } from '../../contexts/SettingsContext';
|
||||
import { zapNote } from '../../lib/zap';
|
||||
import { userName } from '../../stores/profile';
|
||||
import { toastZapFail, zapCustomOption } from '../../translations';
|
||||
import { PrimalNote } from '../../types/primal';
|
||||
import { debounce } from '../../utils';
|
||||
import { Component, createEffect, createSignal } from 'solid-js';
|
||||
import Modal from '../Modal/Modal';
|
||||
import { useToastContext } from '../Toaster/Toaster';
|
||||
import { base64 } from '@scure/base';
|
||||
|
||||
import { nip19, utils } from 'nostr-tools';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
|
||||
import { login as tLogin, pin as tPin, actions as tActions } from '../../translations';
|
||||
import { pin as tPin, actions as tActions } from '../../translations';
|
||||
|
||||
import styles from './EnterPinModal.module.scss';
|
||||
import { hookForDev } from '../../lib/devTools';
|
||||
import ButtonPrimary from '../Buttons/ButtonPrimary';
|
||||
import ButtonLink from '../Buttons/ButtonLink';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import TextInput from '../TextInput/TextInput';
|
||||
import ButtonSecondary from '../Buttons/ButtonSecondary';
|
||||
import { decryptWithPin, encryptWithPin, setCurrentPin } from '../../lib/PrimalNostr';
|
||||
import { decryptWithPin, setCurrentPin } from '../../lib/PrimalNostr';
|
||||
|
||||
const EnterPinModal: Component<{
|
||||
id?: string,
|
||||
@ -43,9 +32,6 @@ const EnterPinModal: Component<{
|
||||
const decWithPin = async () => {
|
||||
const val = props.valueToDecrypt || '';
|
||||
const dec = await decryptWithPin(pin(), val);
|
||||
// console.log('ENCODED: ', dec);
|
||||
// console.log('PIN: ', pin());
|
||||
// console.log('DECODE: ', decryptWithPin);
|
||||
return dec;
|
||||
};
|
||||
|
||||
|
@ -27,10 +27,10 @@ const FeedSelect: Component<{ isPhone?: boolean, id?: string}> = (props) => {
|
||||
const selectFeed = (option: FeedOption) => {
|
||||
|
||||
const [hex, includeReplies] = option.value?.split('_') || [];
|
||||
const selector = document.getElementById('defocus');
|
||||
// const selector = document.getElementById('defocus');
|
||||
|
||||
selector?.focus();
|
||||
selector?.blur();
|
||||
// selector?.focus();
|
||||
// selector?.blur();
|
||||
|
||||
if (hex && !isSelected(option)) {
|
||||
const feed = findFeed(decodeURI(hex), includeReplies);
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
import { truncateNpub, userName } from '../../stores/profile';
|
||||
import EmbeddedNote from '../EmbeddedNote/EmbeddedNote';
|
||||
import {
|
||||
Component, createEffect, createSignal, For, JSXElement, onMount, Show,
|
||||
Component, createSignal, For, JSXElement, onMount, Show,
|
||||
} from 'solid-js';
|
||||
import {
|
||||
PrimalNote,
|
||||
@ -47,16 +47,6 @@ import PhotoSwipeLightbox from 'photoswipe/lightbox';
|
||||
|
||||
const groupGridLimit = 7;
|
||||
|
||||
const convertHTMLEntity = (text: string) => {
|
||||
const span = document.createElement('span');
|
||||
|
||||
return text
|
||||
.replace(/&[#A-Za-z0-9]+;/gi, (entity)=> {
|
||||
span.innerHTML = entity;
|
||||
return span.innerText;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const groupGalleryImages = (noteHolder: HTMLDivElement | undefined) => {
|
||||
|
||||
@ -147,6 +137,8 @@ const ParsedNote: Component<{
|
||||
const intl = useIntl();
|
||||
const media = useMediaContext();
|
||||
|
||||
const dev = localStorage.getItem('devMode') === 'true';
|
||||
|
||||
const id = () => {
|
||||
// if (props.id) return props.id;
|
||||
|
||||
@ -155,10 +147,6 @@ const ParsedNote: Component<{
|
||||
|
||||
let thisNote: HTMLDivElement | undefined;
|
||||
|
||||
let imageGroup: string = generatePrivateKey()
|
||||
let consecutiveImages: number = 0;
|
||||
let imgCount = 0;
|
||||
|
||||
const lightbox = new PhotoSwipeLightbox({
|
||||
gallery: `#${id()}`,
|
||||
children: `a.image_${props.note.post.noteId}`,
|
||||
@ -173,29 +161,12 @@ const ParsedNote: Component<{
|
||||
lightbox.init();
|
||||
});
|
||||
|
||||
let allImagesLoaded = false;
|
||||
|
||||
createEffect(() => {
|
||||
if (imagesLoaded() > 0 && imagesLoaded() === imgCount && !allImagesLoaded) {
|
||||
allImagesLoaded = true;
|
||||
groupGalleryImages(thisNote);
|
||||
}
|
||||
});
|
||||
|
||||
const [tokens, setTokens] = createStore<string[]>([]);
|
||||
const [imagesLoaded, setImagesLoaded] = createSignal(0);
|
||||
|
||||
let wordsDisplayed = 0;
|
||||
const [wordsDisplayed, setWordsDisplayed] = createSignal(0);
|
||||
|
||||
const shouldShowToken = () => {
|
||||
if (!props.shorten) return true;
|
||||
|
||||
|
||||
if (wordsDisplayed < shortNoteWords) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
const isNoteTooLong = () => {
|
||||
return props.shorten && wordsDisplayed() > shortNoteWords;
|
||||
};
|
||||
|
||||
const parseContent = () => {
|
||||
@ -208,20 +179,41 @@ const ParsedNote: Component<{
|
||||
setTokens(() => [...tokens]);
|
||||
}
|
||||
|
||||
const parseToken: (token: string) => JSXElement = (token: string) => {
|
||||
type NoteContent = {
|
||||
type: string,
|
||||
tokens: string[],
|
||||
meta?: Record<string, any>,
|
||||
};
|
||||
|
||||
const [content, setContent] = createStore<NoteContent[]>([]);
|
||||
|
||||
const updateContent = (contentArray: NoteContent[], type: string, token: string, meta?: Record<string, any>) => {
|
||||
if (contentArray.length > 0 && contentArray[contentArray.length -1].type === type) {
|
||||
setContent(content.length -1, 'tokens' , (els) => [...els, token]);
|
||||
meta && setContent(content.length -1, 'meta' , () => ({ ...meta }));
|
||||
return;
|
||||
}
|
||||
|
||||
setContent(content.length, () => ({ type, tokens: [token], meta }));
|
||||
}
|
||||
|
||||
let lastSignificantContent = 'text';
|
||||
|
||||
const parseToken = (token: string) => {
|
||||
if (token === '__LB__') {
|
||||
return <br />;
|
||||
lastSignificantContent !== 'image' && updateContent(content, 'linebreak', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token === '__SP__') {
|
||||
return <> </>;
|
||||
lastSignificantContent !== 'image' && updateContent(content, 'text', ' ');
|
||||
return;
|
||||
}
|
||||
|
||||
wordsDisplayed++;
|
||||
|
||||
if (isInterpunction(token)) {
|
||||
return <span>{token}</span>;
|
||||
lastSignificantContent = 'text';
|
||||
updateContent(content, 'text', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isUrl(token)) {
|
||||
@ -234,26 +226,170 @@ const ParsedNote: Component<{
|
||||
|
||||
if (matched) {
|
||||
const suffix = token.substring(matched.length + index, token.length);
|
||||
return <>{parseToken(prefix)}{parseToken(matched)}{parseToken(suffix)}</>;
|
||||
|
||||
parseToken(prefix);
|
||||
parseToken(matched);
|
||||
parseToken(suffix);
|
||||
return;
|
||||
} else {
|
||||
return <>{parseToken(prefix)}{parseToken(token.slice(index))}</>;
|
||||
parseToken(prefix);
|
||||
parseToken(token.slice(index));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!props.ignoreMedia) {
|
||||
if (isImage(token)) {
|
||||
imgCount++;
|
||||
consecutiveImages++;
|
||||
const dev = localStorage.getItem('devMode') === 'true';
|
||||
lastSignificantContent = 'image';
|
||||
updateContent(content, 'image', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMp4Video(token)) {
|
||||
lastSignificantContent = 'video';
|
||||
updateContent(content, 'video', token, { videoType: 'video/mp4'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOggVideo(token)) {
|
||||
lastSignificantContent = 'video';
|
||||
updateContent(content, 'video', token, { videoType: 'video/ogg'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWebmVideo(token)) {
|
||||
lastSignificantContent = 'video';
|
||||
updateContent(content, 'video', token, { videoType: 'video/webm'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isYouTube(token)) {
|
||||
lastSignificantContent = 'youtube';
|
||||
updateContent(content, 'youtube', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSpotify(token)) {
|
||||
lastSignificantContent = 'spotify';
|
||||
updateContent(content, 'spotify', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTwitch(token)) {
|
||||
lastSignificantContent = 'twitch';
|
||||
updateContent(content, 'twitch', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMixCloud(token)) {
|
||||
lastSignificantContent = 'mixcloud';
|
||||
updateContent(content, 'mixcloud', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSoundCloud(token)) {
|
||||
lastSignificantContent = 'soundcloud';
|
||||
updateContent(content, 'soundcloud', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAppleMusic(token)) {
|
||||
lastSignificantContent = 'applemusic';
|
||||
updateContent(content, 'applemusic', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWavelake(token)) {
|
||||
lastSignificantContent = 'wavelake';
|
||||
updateContent(content, 'wavelake', token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.noLinks === 'text') {
|
||||
lastSignificantContent = 'text';
|
||||
updateContent(content, 'text', token);
|
||||
return;
|
||||
}
|
||||
|
||||
lastSignificantContent = 'link';
|
||||
updateContent(content, 'link', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNoteMention(token)) {
|
||||
lastSignificantContent = 'notemention';
|
||||
updateContent(content, 'notemention', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isUserMention(token)) {
|
||||
lastSignificantContent = 'usermention';
|
||||
updateContent(content, 'usermention', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTagMention(token)) {
|
||||
lastSignificantContent = 'tagmention';
|
||||
updateContent(content, 'tagmention', token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isHashtag(token)) {
|
||||
lastSignificantContent = 'hashtag';
|
||||
updateContent(content, 'hashtag', token);
|
||||
return;
|
||||
}
|
||||
|
||||
lastSignificantContent = 'text';
|
||||
updateContent(content, 'text', token);
|
||||
return;
|
||||
};
|
||||
|
||||
const generateContent = () => {
|
||||
|
||||
parseContent();
|
||||
|
||||
for (let i=0; i<tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
|
||||
parseToken(token);
|
||||
}
|
||||
};
|
||||
|
||||
const renderLinebreak = (item: NoteContent) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
// Allow only one consecutive linebreak
|
||||
return <br />
|
||||
};
|
||||
|
||||
const renderText = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{token => {
|
||||
if (isNoteTooLong()) return;
|
||||
if (token.trim().length > 0) {
|
||||
setWordsDisplayed(w => w + 1);
|
||||
}
|
||||
return token
|
||||
}}
|
||||
</For>;
|
||||
};
|
||||
|
||||
const renderImage = (item: NoteContent) => {
|
||||
|
||||
const groupCount = item.tokens.length;
|
||||
const imageGroup = generatePrivateKey();
|
||||
|
||||
if (groupCount === 1) {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
const token = item.tokens[0];
|
||||
let image = media?.actions.getMedia(token, 'o');
|
||||
const url = image?.media_url || getMediaUrlDefault(token);
|
||||
|
||||
if (consecutiveImages > 1) {
|
||||
// There are consecutive images, so reduce the impact of each image in order to show them grouped
|
||||
wordsDisplayed += 10;
|
||||
} else {
|
||||
wordsDisplayed += shortMentionInWords
|
||||
}
|
||||
// Images tell a 100 words :)
|
||||
setWordsDisplayed(w => w + 100);
|
||||
|
||||
return <NoteImage
|
||||
class={`noteimage image_${props.note.post.noteId}`}
|
||||
@ -262,16 +398,43 @@ const ParsedNote: Component<{
|
||||
media={image}
|
||||
width={514}
|
||||
imageGroup={imageGroup}
|
||||
onImageLoaded={() => setImagesLoaded(i => i+1)}
|
||||
shortHeight={props.shorten}
|
||||
/>;
|
||||
/>
|
||||
}
|
||||
|
||||
consecutiveImages = 0;
|
||||
imageGroup = generatePrivateKey();
|
||||
const gridClass = groupCount < groupGridLimit ? `grid-${groupCount}` : 'grid-large';
|
||||
|
||||
return <div class={`imageGrid ${gridClass}`}>
|
||||
<For each={item.tokens}>
|
||||
{(token, index) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
let image = media?.actions.getMedia(token, 'o');
|
||||
const url = image?.media_url || getMediaUrlDefault(token);
|
||||
|
||||
// There are consecutive images, so reduce the impact of each image in order to show them grouped
|
||||
setWordsDisplayed(w => w + 10 * groupCount);
|
||||
|
||||
return <NoteImage
|
||||
class={`noteimage_gallery image_${props.note.post.noteId} cell_${index()}`}
|
||||
src={url}
|
||||
isDev={dev}
|
||||
media={image}
|
||||
width={514}
|
||||
imageGroup={imageGroup}
|
||||
shortHeight={props.shorten}
|
||||
plainBorder={true}
|
||||
/>
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
}
|
||||
|
||||
const renderVideo = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>{
|
||||
(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
if (isMp4Video(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
let mVideo = media?.actions.getMedia(token, 'o');
|
||||
|
||||
let h: number | undefined = undefined;
|
||||
@ -284,74 +447,38 @@ const ParsedNote: Component<{
|
||||
h = h > 680 ? 680 : h;
|
||||
}
|
||||
|
||||
// const h = mVideo ? mVideo?.h > 524 ? 524 * mVideo?.h / mVideo?.w : mVideo?.h : undefined;
|
||||
// const w = mVideo ? mVideo?.w > 524 ? 524 : mVideo?.w : undefined;
|
||||
const klass = mVideo ? 'w-cen' : 'w-max';
|
||||
let klass = mVideo ? 'w-cen' : 'w-max';
|
||||
|
||||
const video = <video class={klass} width={w} height={h} controls muted={true} ><source src={token} type="video/mp4" /></video>;
|
||||
media?.actions.addVideo(video as HTMLVideoElement);
|
||||
|
||||
|
||||
return video;
|
||||
if (dev && !mVideo) {
|
||||
klass += ' redBorder';
|
||||
}
|
||||
|
||||
if (isOggVideo(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
let mVideo = media?.actions.getMedia(token, 'o');
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
let h: number | undefined = undefined;
|
||||
let w: number | undefined = undefined;
|
||||
|
||||
if (mVideo) {
|
||||
const ratio = mVideo.w / mVideo.h;
|
||||
h = (524 / ratio);
|
||||
w = h > 680 ? 680 * ratio : 524;
|
||||
h = h > 680 ? 680 : h;
|
||||
}
|
||||
|
||||
// const h = mVideo ? mVideo?.h > 524 ? 524 * mVideo?.h / mVideo?.w : mVideo?.h : undefined;
|
||||
// const w = mVideo ? mVideo?.w > 524 ? 524 : mVideo?.w : undefined;
|
||||
const klass = mVideo ? 'w-cen' : 'w-max';
|
||||
|
||||
const video =
|
||||
<video
|
||||
const video = <video
|
||||
class={klass}
|
||||
width={w}
|
||||
height={h}
|
||||
controls
|
||||
muted={true}
|
||||
>
|
||||
<source src={token} type="video/ogg" />
|
||||
<source src={token} type={item.meta?.videoType} />
|
||||
</video>;
|
||||
|
||||
media?.actions.addVideo(video as HTMLVideoElement);
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
if (isWebmVideo(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
let mVideo = media?.actions.getMedia(token, 'o');
|
||||
|
||||
let h: number | undefined = undefined;
|
||||
let w: number | undefined = undefined;
|
||||
|
||||
if (mVideo) {
|
||||
const ratio = mVideo.w / mVideo.h;
|
||||
h = (524 / ratio);
|
||||
w = h > 680 ? 680 * ratio : 524;
|
||||
h = h > 680 ? 680 : h;
|
||||
}</For>;
|
||||
}
|
||||
|
||||
// const h = mVideo ? mVideo?.h > 524 ? 524 * mVideo?.h / mVideo?.w : mVideo?.h : undefined;
|
||||
// const w = mVideo ? mVideo?.w > 524 ? 524 : mVideo?.w : undefined;
|
||||
const klass = mVideo ? 'w-cen' : 'w-max';
|
||||
const renderYouTube = (item: NoteContent) => {
|
||||
|
||||
const video = <video class={klass} width={w} height={h} controls muted={true} ><source src={token} type="video/webm" /></video>;
|
||||
media?.actions.addVideo(video as HTMLVideoElement);
|
||||
return video;
|
||||
}
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
if (isYouTube(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
const youtubeId = isYouTube(token) && RegExp.$1;
|
||||
|
||||
@ -365,10 +492,16 @@ const ParsedNote: Component<{
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
></iframe>;
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
if (isSpotify(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
const renderSpotify = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
const convertedUrl = token.replace(/\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/, "/embed/$1/$2");
|
||||
|
||||
@ -382,10 +515,16 @@ const ParsedNote: Component<{
|
||||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||||
loading="lazy"
|
||||
></iframe>;
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
if (isTwitch(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
const renderTwitch = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
const channel = token.split("/").slice(-1);
|
||||
|
||||
@ -397,10 +536,16 @@ const ParsedNote: Component<{
|
||||
className="w-max"
|
||||
allowFullScreen
|
||||
></iframe>;
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
if (isMixCloud(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
const renderMixCloud = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
const feedPath = (isMixCloud(token) && RegExp.$1) + "%2F" + (isMixCloud(token) && RegExp.$2);
|
||||
|
||||
@ -414,10 +559,16 @@ const ParsedNote: Component<{
|
||||
src={`https://www.mixcloud.com/widget/iframe/?hide_cover=1&feed=%2F${feedPath}%2F`}
|
||||
></iframe>
|
||||
</div>;
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
if (isSoundCloud(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
const renderSoundCloud = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
return <iframe
|
||||
width="100%"
|
||||
@ -427,10 +578,16 @@ const ParsedNote: Component<{
|
||||
allow="autoplay"
|
||||
src={`https://w.soundcloud.com/player/?url=${token}`}
|
||||
></iframe>;
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
if (isAppleMusic(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
const renderAppleMusic = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
const convertedUrl = token.replace("music.apple.com", "embed.music.apple.com");
|
||||
const isSongLink = /\?i=\d+$/.test(convertedUrl);
|
||||
@ -444,10 +601,16 @@ const ParsedNote: Component<{
|
||||
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation"
|
||||
src={convertedUrl}
|
||||
></iframe>;
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
if (isWavelake(token)) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
const renderWavelake = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
const convertedUrl = token.replace(/(?:player\.|www\.)?wavlake\.com/, "embed.wavlake.com");
|
||||
|
||||
@ -460,12 +623,14 @@ const ParsedNote: Component<{
|
||||
frameBorder="0"
|
||||
loading="lazy"
|
||||
></iframe>;
|
||||
}
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
if (props.noLinks === 'text') {
|
||||
return <span class="whole">{token}</span>;
|
||||
}
|
||||
const renderLinks = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
const preview = getLinkPreview(token);
|
||||
|
||||
@ -478,21 +643,25 @@ const ParsedNote: Component<{
|
||||
);
|
||||
|
||||
if (hasMinimalPreviewData) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
return <LinkPreview preview={preview} bordered={props.isEmbeded} />;
|
||||
}
|
||||
|
||||
setWordsDisplayed(w => w + 1);
|
||||
return <span data-url={token}><a link href={token.toLowerCase()} target="_blank" >{token}</a></span>;
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
consecutiveImages = 0;
|
||||
imageGroup = generatePrivateKey();
|
||||
const renderNoteMention = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
if (isNoteMention(token)) {
|
||||
let [_, id] = token.split(':');
|
||||
|
||||
if (!id) {
|
||||
return token;
|
||||
return <>{token}</>;
|
||||
}
|
||||
|
||||
let end = '';
|
||||
@ -524,7 +693,7 @@ const ParsedNote: Component<{
|
||||
link = <A href={path}>{token}</A>;
|
||||
|
||||
if (ment) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
setWordsDisplayed(w => w + shortMentionInWords);
|
||||
|
||||
link = <div>
|
||||
<EmbeddedNote
|
||||
@ -536,17 +705,25 @@ const ParsedNote: Component<{
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
setWordsDisplayed(w => w + 1);
|
||||
link = <span class={styles.error}>{token}</span>;
|
||||
}
|
||||
|
||||
return <span class="whole"> {link}{end}</span>;
|
||||
}
|
||||
return link;}}
|
||||
</For>
|
||||
};
|
||||
|
||||
const renderUserMention = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + 1);
|
||||
|
||||
if (isUserMention(token)) {
|
||||
let [_, id] = token.split(':');
|
||||
|
||||
if (!id) {
|
||||
return token;
|
||||
return <>{token}</>;
|
||||
}
|
||||
|
||||
let end = '';
|
||||
@ -582,17 +759,23 @@ const ParsedNote: Component<{
|
||||
<><A href={path}>@{label}</A>{end}</> :
|
||||
<>{MentionedUserLink({ user })}{end}</>;
|
||||
}
|
||||
|
||||
return <span class="whole"> {link}</span>;
|
||||
return link;
|
||||
} catch (e) {
|
||||
return <span class={styles.error}> {token}</span>;
|
||||
}
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
const renderTagMention = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + 1);
|
||||
|
||||
if (isTagMention(token)) {
|
||||
let t = `${token}`;
|
||||
|
||||
|
||||
let end = t[t.length - 1];
|
||||
|
||||
if ([',', '?', ';', '!'].some(x => end === x)) {
|
||||
@ -628,7 +811,7 @@ const ParsedNote: Component<{
|
||||
embeded = <><A href={path}>{noteId}</A>{end}</>;
|
||||
|
||||
if (ment) {
|
||||
wordsDisplayed += shortMentionInWords;
|
||||
setWordsDisplayed(w => w + shortMentionInWords - 1);
|
||||
|
||||
embeded = <div>
|
||||
<EmbeddedNote
|
||||
@ -661,12 +844,19 @@ const ParsedNote: Component<{
|
||||
<><A href={path}>@{label}</A>{end}</> :
|
||||
<>{MentionedUserLink({ user })}{end}</>;
|
||||
}
|
||||
|
||||
return <span> {link}</span>;
|
||||
}
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
const renderHashtag = (item: NoteContent) => {
|
||||
return <For each={item.tokens}>
|
||||
{(token) => {
|
||||
if (isNoteTooLong()) return;
|
||||
|
||||
setWordsDisplayed(w => w + 1);
|
||||
|
||||
if (isHashtag(token)) {
|
||||
let [_, term] = token.split('#');
|
||||
let end = '';
|
||||
|
||||
@ -683,25 +873,46 @@ const ParsedNote: Component<{
|
||||
<A href={`/search/%23${term}`}>#{term}</A>;
|
||||
|
||||
return <span class="whole"> {embeded}{end}</span>;
|
||||
}}
|
||||
</For>
|
||||
};
|
||||
|
||||
const renderContent = (item: NoteContent) => {
|
||||
|
||||
const renderers: Record<string, (item: NoteContent) => JSXElement> = {
|
||||
linebreak: renderLinebreak,
|
||||
text: renderText,
|
||||
image: renderImage,
|
||||
video: renderVideo,
|
||||
youtube: renderYouTube,
|
||||
spotify: renderSpotify,
|
||||
twitch: renderTwitch,
|
||||
mixcloud: renderMixCloud,
|
||||
soundcloud: renderSoundCloud,
|
||||
applemusic: renderAppleMusic,
|
||||
wavelake: renderWavelake,
|
||||
link: renderLinks,
|
||||
notemention: renderNoteMention,
|
||||
usermention: renderUserMention,
|
||||
tagmention: renderTagMention,
|
||||
hashtag: renderHashtag,
|
||||
}
|
||||
|
||||
return <span class="whole">{convertHTMLEntity(token)}</span>;
|
||||
return renderers[item.type] ?
|
||||
renderers[item.type](item) :
|
||||
<></>;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
parseContent();
|
||||
generateContent();
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={thisNote} id={id()} class={styles.parsedNote} >
|
||||
<For each={tokens}>
|
||||
{(token) =>
|
||||
<Show when={shouldShowToken()}>
|
||||
<>{parseToken(token)}</>
|
||||
</Show>
|
||||
}
|
||||
<For each={content}>
|
||||
{(item) => renderContent(item)}
|
||||
</For>
|
||||
<Show when={props.shorten && tokens.length > shortNoteWords}>
|
||||
<Show when={isNoteTooLong()}>
|
||||
<span class={styles.more}>
|
||||
... <span class="linkish">{intl.formatMessage(actions.seeMore)}</span>
|
||||
</span>
|
||||
|
@ -2,7 +2,6 @@
|
||||
import { Relay } from "nostr-tools";
|
||||
import { createStore } from "solid-js/store";
|
||||
import LinkPreview from "../components/LinkPreview/LinkPreview";
|
||||
import NoteImage from "../components/NoteImage/NoteImage";
|
||||
import { appleMusicRegex, hashtagRegex, interpunctionRegex, Kind, linebreakRegex, mixCloudRegex, nostrNestsRegex, noteRegex, profileRegex, soundCloudRegex, spotifyRegex, tagMentionRegex, twitchRegex, urlRegex, urlRegexG, wavlakeRegex, youtubeRegex } from "../constants";
|
||||
import { sendMessage, subscribeTo } from "../sockets";
|
||||
import { MediaSize, NostrRelays, NostrRelaySignedEvent, PrimalNote, SendNoteResult } from "../types/primal";
|
||||
|
@ -125,7 +125,7 @@ const Thread: Component = () => {
|
||||
scrollWindowTo(rect.top - header - banner);
|
||||
|
||||
// repliesHolder.setAttribute('style', `height: ${document.documentElement.scrollHeight}px;`)
|
||||
}, 0)
|
||||
}, 1000)
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user