Remove empty space at the end of a note

This commit is contained in:
Bojan Mojsilovic 2024-02-07 18:09:06 +01:00
parent 08027cd8ff
commit 6031edb758
6 changed files with 95 additions and 30 deletions

View File

@ -15,7 +15,7 @@ import VerificationCheck from '../VerificationCheck/VerificationCheck';
import styles from './EmbeddedNote.module.scss'; import styles from './EmbeddedNote.module.scss';
const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record<string, PrimalUser>, includeEmbeds?: boolean}> = (props) => { const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record<string, PrimalUser>, includeEmbeds?: boolean, isLast?: boolean}> = (props) => {
const threadContext = useThreadContext(); const threadContext = useThreadContext();
const intl = useIntl(); const intl = useIntl();
@ -36,7 +36,7 @@ const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record<string
if (props.includeEmbeds) { if (props.includeEmbeds) {
return ( return (
<div <div
class={`${styles.mentionedNote} embeddedContent`} class={`${styles.mentionedNote} embeddedNote ${props.isLast ? 'noBottomMargin' : ''}`}
data-event={props.note.post.id} data-event={props.note.post.id}
data-event-bech32={noteId()} data-event-bech32={noteId()}
> >
@ -48,7 +48,7 @@ const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record<string
return ( return (
<A <A
href={`/e/${noteId()}`} href={`/e/${noteId()}`}
class={`${styles.mentionedNote} embeddedContent`} class={`${styles.mentionedNote} embeddedNote ${props.isLast ? 'noBottomMargin' : ''}`}
onClick={() => navToThread()} onClick={() => navToThread()}
data-event={props.note.post.id} data-event={props.note.post.id}
data-event-bech32={noteId()} data-event-bech32={noteId()}

View File

@ -4,7 +4,7 @@ import { hookForDev } from '../../lib/devTools';
import styles from './LinkPreview.module.scss'; import styles from './LinkPreview.module.scss';
const LinkPreview: Component<{ preview: any, id?: string, bordered?: boolean }> = (props) => { const LinkPreview: Component<{ preview: any, id?: string, bordered?: boolean, isLast?: boolean }> = (props) => {
const media = useMediaContext(); const media = useMediaContext();
@ -44,6 +44,8 @@ const LinkPreview: Component<{ preview: any, id?: string, bordered?: boolean }>
k += " embeddedContent"; k += " embeddedContent";
k += props.isLast ? ' noBottomMargin' : '';
return k; return k;
}; };

View File

@ -399,6 +399,17 @@ const ParsedNote: Component<{
parseToken(token); parseToken(token);
} }
// Check if the last media is the last meaningfull content in the note
// And if so, make it the actual last content
// @ts-ignore
const lastMediaIndex = content.findLastIndex(c => c.type !== 'text');
const lastContent = content[content.length - 1];
if (lastMediaIndex === content.length - 2 && lastContent.type === 'text' && lastContent.tokens.every(t => [' ', ''].includes(t))) {
setContent((cont) => cont.slice(0, cont.length - 1));
}
}; };
const renderLinebreak = (item: NoteContent) => { const renderLinebreak = (item: NoteContent) => {
@ -430,11 +441,15 @@ const ParsedNote: Component<{
return <>{text}</>; return <>{text}</>;
}; };
const renderImage = (item: NoteContent) => { const renderImage = (item: NoteContent, index?: number) => {
const groupCount = item.tokens.length; const groupCount = item.tokens.length;
const imageGroup = generatePrivateKey(); const imageGroup = generatePrivateKey();
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
if (groupCount === 1) { if (groupCount === 1) {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -446,7 +461,7 @@ const ParsedNote: Component<{
setWordsDisplayed(w => w + 100); setWordsDisplayed(w => w + 100);
return <NoteImage return <NoteImage
class={`noteimage image_${props.note.post.noteId}`} class={`noteimage image_${props.note.post.noteId} ${lastClass}`}
src={url} src={url}
isDev={dev} isDev={dev}
media={image} media={image}
@ -488,7 +503,11 @@ const ParsedNote: Component<{
</div> </div>
} }
const renderVideo = (item: NoteContent) => { const renderVideo = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}>{ return <For each={item.tokens}>{
(token) => { (token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -512,6 +531,7 @@ const ParsedNote: Component<{
} }
klass += ' embeddedContent'; klass += ' embeddedContent';
klass += ` ${lastClass}`;
setWordsDisplayed(w => w + shortMentionInWords); setWordsDisplayed(w => w + shortMentionInWords);
@ -533,7 +553,10 @@ const ParsedNote: Component<{
}</For>; }</For>;
} }
const renderYouTube = (item: NoteContent) => { const renderYouTube = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
@ -544,7 +567,7 @@ const ParsedNote: Component<{
const youtubeId = isYouTube(token) && RegExp.$1; const youtubeId = isYouTube(token) && RegExp.$1;
return <iframe return <iframe
class="w-max embeddedContent" class={`w-max embeddedContent ${lastClass}`}
src={`https://www.youtube.com/embed/${youtubeId}`} src={`https://www.youtube.com/embed/${youtubeId}`}
title="YouTube video player" title="YouTube video player"
// @ts-ignore no property // @ts-ignore no property
@ -557,7 +580,11 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderSpotify = (item: NoteContent) => { const renderSpotify = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -567,7 +594,7 @@ const ParsedNote: Component<{
const convertedUrl = token.replace(/\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/, "/embed/$1/$2"); const convertedUrl = token.replace(/\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/, "/embed/$1/$2");
return <iframe return <iframe
class="embeddedContent" class={`embeddedContent ${lastClass}`}
style="borderRadius: 12" style="borderRadius: 12"
src={convertedUrl} src={convertedUrl}
width="100%" width="100%"
@ -581,7 +608,11 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderTwitch = (item: NoteContent) => { const renderTwitch = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -593,7 +624,7 @@ const ParsedNote: Component<{
const args = `?channel=${channel}&parent=${window.location.hostname}&muted=true`; const args = `?channel=${channel}&parent=${window.location.hostname}&muted=true`;
return <iframe return <iframe
class="embeddedContent" class={`embeddedContent ${lastClass}`}
src={`https://player.twitch.tv/${args}`} src={`https://player.twitch.tv/${args}`}
// @ts-ignore no property // @ts-ignore no property
className="w-max" className="w-max"
@ -603,7 +634,11 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderMixCloud = (item: NoteContent) => { const renderMixCloud = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -612,7 +647,7 @@ const ParsedNote: Component<{
const feedPath = (isMixCloud(token) && RegExp.$1) + "%2F" + (isMixCloud(token) && RegExp.$2); const feedPath = (isMixCloud(token) && RegExp.$1) + "%2F" + (isMixCloud(token) && RegExp.$2);
return <div class="embeddedContent"> return <div class={`embeddedContent ${lastClass}`}>
<iframe <iframe
title="SoundCloud player" title="SoundCloud player"
width="100%" width="100%"
@ -626,7 +661,11 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderSoundCloud = (item: NoteContent) => { const renderSoundCloud = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -634,7 +673,7 @@ const ParsedNote: Component<{
setWordsDisplayed(w => w + shortMentionInWords); setWordsDisplayed(w => w + shortMentionInWords);
return <iframe return <iframe
class="embeddedContent" class={`embeddedContent ${lastClass}`}
width="100%" width="100%"
height="166" height="166"
// @ts-ignore no property // @ts-ignore no property
@ -646,7 +685,11 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderAppleMusic = (item: NoteContent) => { const renderAppleMusic = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -657,7 +700,7 @@ const ParsedNote: Component<{
const isSongLink = /\?i=\d+$/.test(convertedUrl); const isSongLink = /\?i=\d+$/.test(convertedUrl);
return <iframe return <iframe
class="embeddedContent" class={`embeddedContent ${lastClass}`}
allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write" allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write"
// @ts-ignore no property // @ts-ignore no property
frameBorder="0" frameBorder="0"
@ -670,7 +713,11 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderWavelake = (item: NoteContent) => { const renderWavelake = (item: NoteContent, index?: number) => {
// Remove bottom margin if media is the last thing in the note
const lastClass = index === content.length-1 ?
'noBottomMargin' : '';
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -680,7 +727,7 @@ const ParsedNote: Component<{
const convertedUrl = token.replace(/(?:player\.|www\.)?wavlake\.com/, "embed.wavlake.com"); const convertedUrl = token.replace(/(?:player\.|www\.)?wavlake\.com/, "embed.wavlake.com");
return <iframe return <iframe
class="embeddedContent" class={`embeddedContent ${lastClass}`}
style="borderRadius: 12" style="borderRadius: 12"
src={convertedUrl} src={convertedUrl}
width="100%" width="100%"
@ -693,7 +740,7 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderLinks = (item: NoteContent) => { const renderLinks = (item: NoteContent, index?: number) => {
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -710,7 +757,11 @@ const ParsedNote: Component<{
if (hasMinimalPreviewData) { if (hasMinimalPreviewData) {
setWordsDisplayed(w => w + shortMentionInWords); setWordsDisplayed(w => w + shortMentionInWords);
return <LinkPreview preview={preview} bordered={props.isEmbeded} />; return <LinkPreview
preview={preview}
bordered={props.isEmbeded}
isLast={index === content.length-1}
/>;
} }
setWordsDisplayed(w => w + 1); setWordsDisplayed(w => w + 1);
@ -719,7 +770,7 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderNoteMention = (item: NoteContent) => { const renderNoteMention = (item: NoteContent, index?: number) => {
return <For each={item.tokens}> return <For each={item.tokens}>
{(token) => { {(token) => {
if (isNoteTooLong()) return; if (isNoteTooLong()) return;
@ -765,6 +816,7 @@ const ParsedNote: Component<{
<EmbeddedNote <EmbeddedNote
note={ment} note={ment}
mentionedUsers={props.note.mentionedUsers || {}} mentionedUsers={props.note.mentionedUsers || {}}
isLast={index === content.length-1}
/> />
</div>; </div>;
} }
@ -965,9 +1017,9 @@ const ParsedNote: Component<{
</For> </For>
}; };
const renderContent = (item: NoteContent) => { const renderContent = (item: NoteContent, index: number) => {
const renderers: Record<string, (item: NoteContent) => JSXElement> = { const renderers: Record<string, (item: NoteContent, index?: number) => JSXElement> = {
linebreak: renderLinebreak, linebreak: renderLinebreak,
text: renderText, text: renderText,
image: renderImage, image: renderImage,
@ -988,7 +1040,7 @@ const ParsedNote: Component<{
} }
return renderers[item.type] ? return renderers[item.type] ?
renderers[item.type](item) : renderers[item.type](item, index) :
<></>; <></>;
}; };
@ -999,7 +1051,7 @@ const ParsedNote: Component<{
return ( return (
<div ref={thisNote} id={id()} class={styles.parsedNote} > <div ref={thisNote} id={id()} class={styles.parsedNote} >
<For each={content}> <For each={content}>
{(item) => renderContent(item)} {(item, index) => renderContent(item, index())}
</For> </For>
<Show when={isNoteTooLong()}> <Show when={isNoteTooLong()}>
<span class={styles.more}> <span class={styles.more}>

View File

@ -254,6 +254,7 @@ export const hashtagRegex = /(?:\s|^)#[^\s!@#$%^&*(),.?":{}|<>]+/i;
export const linebreakRegex = /(\r\n|\r|\n)/ig; export const linebreakRegex = /(\r\n|\r|\n)/ig;
export const tagMentionRegex = /\#\[([0-9]*)\]/; export const tagMentionRegex = /\#\[([0-9]*)\]/;
export const noteRegex = /nostr:((note|nevent)1\w+)\b/g; export const noteRegex = /nostr:((note|nevent)1\w+)\b/g;
export const noteRegexLocal = /nostr:((note|nevent)1\w+)\b/;
export const profileRegex = /nostr:((npub|nprofile)1\w+)\b/; export const profileRegex = /nostr:((npub|nprofile)1\w+)\b/;
export const editMentionRegex = /(?:\s|^)@\`(.*?)\`/ig; export const editMentionRegex = /(?:\s|^)@\`(.*?)\`/ig;

View File

@ -191,11 +191,21 @@ a {
overflow: hidden !important; overflow: hidden !important;
} }
.embeddedNote {
margin-block: 12px !important;
border-radius: var(--border-radius-big) !important;
overflow: hidden !important;
}
.noteimage { .noteimage {
margin-block: 12px !important; margin-block: 12px !important;
display: block !important; display: block !important;
} }
.noBottomMargin {
margin-bottom: 0px !important;
}
.imageGrid { .imageGrid {
margin-block: 12px !important; margin-block: 12px !important;
display: grid; display: grid;

View File

@ -2,7 +2,7 @@
import { Relay } from "nostr-tools"; import { Relay } from "nostr-tools";
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
import LinkPreview from "../components/LinkPreview/LinkPreview"; import LinkPreview from "../components/LinkPreview/LinkPreview";
import { appleMusicRegex, emojiRegex, hashtagRegex, interpunctionRegex, Kind, linebreakRegex, mixCloudRegex, nostrNestsRegex, noteRegex, profileRegex, soundCloudRegex, spotifyRegex, tagMentionRegex, twitchRegex, urlRegex, urlRegexG, wavlakeRegex, youtubeRegex } from "../constants"; import { appleMusicRegex, emojiRegex, hashtagRegex, interpunctionRegex, Kind, linebreakRegex, mixCloudRegex, nostrNestsRegex, noteRegex, noteRegexLocal, profileRegex, soundCloudRegex, spotifyRegex, tagMentionRegex, twitchRegex, urlRegex, urlRegexG, wavlakeRegex, youtubeRegex } from "../constants";
import { sendMessage, subscribeTo } from "../sockets"; import { sendMessage, subscribeTo } from "../sockets";
import { MediaSize, NostrRelays, NostrRelaySignedEvent, PrimalNote, SendNoteResult } from "../types/primal"; import { MediaSize, NostrRelays, NostrRelaySignedEvent, PrimalNote, SendNoteResult } from "../types/primal";
import { getMediaUrl as getMediaUrlDefault } from "./media"; import { getMediaUrl as getMediaUrlDefault } from "./media";
@ -54,7 +54,7 @@ export const isUrl = (url: string) => urlRegex.test(url);
export const isHashtag = (url: string) => hashtagRegex.test(url); export const isHashtag = (url: string) => hashtagRegex.test(url);
export const isLinebreak = (url: string) => linebreakRegex.test(url); export const isLinebreak = (url: string) => linebreakRegex.test(url);
export const isTagMention = (url: string) => tagMentionRegex.test(url); export const isTagMention = (url: string) => tagMentionRegex.test(url);
export const isNoteMention = (url: string) => noteRegex.test(url); export const isNoteMention = (url: string) => noteRegexLocal.test(url);
export const isUserMention = (url: string) => profileRegex.test(url); export const isUserMention = (url: string) => profileRegex.test(url);
export const isInterpunction = (url: string) => interpunctionRegex.test(url); export const isInterpunction = (url: string) => interpunctionRegex.test(url);
export const isCustomEmoji = (url: string) => emojiRegex.test(url); export const isCustomEmoji = (url: string) => emojiRegex.test(url);