diff --git a/package-lock.json b/package-lock.json index cce28c1f..cc1d4e5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vite-template-solid", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vite-template-solid", - "version": "0.0.0", + "version": "1.0.0", "license": "MIT", "dependencies": { "@cookbook/solid-intl": "^0.1.2", @@ -18,6 +18,7 @@ "@types/dompurify": "^2.4.0", "dompurify": "^3.0.0", "link-preview-js": "^3.0.4", + "medium-zoom": "^1.0.8", "nostr-tools": "^1.4.1", "sass": "^1.58.0", "solid-js": "^1.6.6" @@ -1806,6 +1807,11 @@ "remove-accents": "0.4.2" } }, + "node_modules/medium-zoom": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.0.8.tgz", + "integrity": "sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==" + }, "node_modules/merge-anything": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.4.tgz", diff --git a/package.json b/package.json index f18d5c05..b74019f6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/dompurify": "^2.4.0", "dompurify": "^3.0.0", "link-preview-js": "^3.0.4", + "medium-zoom": "^1.0.8", "nostr-tools": "^1.4.1", "sass": "^1.58.0", "solid-js": "^1.6.6" diff --git a/src/components/EmbeddedNote/EmbeddedNote.tsx b/src/components/EmbeddedNote/EmbeddedNote.tsx index 1d598fda..6c355543 100644 --- a/src/components/EmbeddedNote/EmbeddedNote.tsx +++ b/src/components/EmbeddedNote/EmbeddedNote.tsx @@ -169,7 +169,7 @@ const EmbeddedNote: Component<{ note: PrimalNote, mentionedUsers?: Record void, idPre const parseForReferece = (value: string) => { - const content = replaceLinkPreviews(parseUserMentions(highlightHashtags(parseNote1(value)))); + const content = replaceLinkPreviews(parseUserMentions(highlightHashtags(parseNote1(value).urlified))); parseNpubLinks(content); parseNoteLinks(content); diff --git a/src/components/Note/Note.module.scss b/src/components/Note/Note.module.scss index 4d801aec..60e8c9f1 100644 --- a/src/components/Note/Note.module.scss +++ b/src/components/Note/Note.module.scss @@ -52,45 +52,37 @@ } } - -.postLink { +.container { text-decoration: none; color: unset; margin: 0px; padding: 16px 20px; - // background: var(--brand-gradient-vertical); background-color: var(--background-card); border-radius: 8px; display: block; transition: 0.2s padding; margin-top: 8px; + >div { border-radius: 4px; transition: 0.2s border-radius ease-out; } - - // &:hover { - // padding-left: 4px; - // transition: 0.2s padding; - // border-radius: 4px; - // >div { - // border-radius: 0px 4px 4px 0px; - // transition: 0.2s border-radius ease-out; - // } - // } } .repostedBy { padding-bottom: 16px; display: flex; + >span { >a { margin-inline: 5px; } + color: var(--text-tertiary); font-size: 16px; line-height: 16px; font-weight: 400; + >span { text-transform: lowercase; } @@ -101,12 +93,14 @@ @media only screen and (max-width: 720px) { .postLink { width: 100vw; + .post { width: 100%; // grid-template-columns: 62px 1fr; margin-left: 0px; margin-right: 0px; padding-right: 0px; + .content { margin-left: 0; } @@ -114,4 +108,4 @@ } -} +} \ No newline at end of file diff --git a/src/components/Note/Note.tsx b/src/components/Note/Note.tsx index 3183eb13..937ba291 100644 --- a/src/components/Note/Note.tsx +++ b/src/components/Note/Note.tsx @@ -1,57 +1,38 @@ -import { A } from '@solidjs/router'; -import { Component, Show } from 'solid-js'; -import { PrimalNote } from '../../types/primal'; -import ParsedNote from '../ParsedNote/ParsedNote'; -import NoteFooter from './NoteFooter/NoteFooter'; -import NoteHeader from './NoteHeader/NoteHeader'; - -import styles from './Note.module.scss'; -import { useThreadContext } from '../../contexts/ThreadContext'; -import { useIntl } from '@cookbook/solid-intl'; -import { truncateNpub } from '../../stores/profile'; -import { note as t } from '../../translations'; +import { A } from "@solidjs/router"; +import { Component, Show } from "solid-js"; +import { PrimalNote } from "../../types/primal"; +import ParsedNote from "../ParsedNote/ParsedNote"; +import NoteFooter from "./NoteFooter/NoteFooter"; +import NoteHeader from "./NoteHeader/NoteHeader"; + +import styles from "./Note.module.scss"; +import { useIntl } from "@cookbook/solid-intl"; +import { truncateNpub } from "../../stores/profile"; +import { note as t } from "../../translations"; const Note: Component<{ note: PrimalNote }> = (props) => { - - const threadContext = useThreadContext(); const intl = useIntl(); const repost = () => props.note.repost; - const navToThread = (note: PrimalNote) => { - threadContext?.actions.setPrimaryNote(note); - }; - const reposterName = () => { const r = repost(); if (!r) { - return ''; + return ""; } - return r.user?.displayName || - r.user?.name || - truncateNpub(r.user.npub); - } + return r.user?.displayName || r.user?.name || truncateNpub(r.user.npub); + }; return ( - navToThread(props.note)} - data-event={props.note.post.id} - data-event-bech32={props.note.post.noteId} - > +
-
- {reposterName()} - - - {intl.formatMessage(t.reposted)} - + {reposterName()} + {intl.formatMessage(t.reposted)}
@@ -64,8 +45,8 @@ const Note: Component<{ note: PrimalNote }> = (props) => {
- - ) -} + + ); +}; export default Note; diff --git a/src/components/ParsedNote/ParsedNote.module.scss b/src/components/ParsedNote/ParsedNote.module.scss index e86e2005..169c972f 100644 --- a/src/components/ParsedNote/ParsedNote.module.scss +++ b/src/components/ParsedNote/ParsedNote.module.scss @@ -88,3 +88,15 @@ .error { color: var(--brand-1); } + +.imagesContainer { + display: flex; + flex-direction: column; + gap: 6px; +} + +.postLink { + text-decoration: none !important; + color: unset; + margin: 0px; +} diff --git a/src/components/ParsedNote/ParsedNote.tsx b/src/components/ParsedNote/ParsedNote.tsx index f5fd193f..e053dcbf 100644 --- a/src/components/ParsedNote/ParsedNote.tsx +++ b/src/components/ParsedNote/ParsedNote.tsx @@ -1,27 +1,27 @@ -import { A } from '@solidjs/router'; -import { hexToNpub } from '../../lib/keys'; -import { linkPreviews, parseNote1 } from '../../lib/notes'; -import { truncateNpub, userName } from '../../stores/profile'; -import EmbeddedNote from '../EmbeddedNote/EmbeddedNote'; -import { - Component, createEffect, createSignal, -} from 'solid-js'; -import { - PrimalNote, -} from '../../types/primal'; - -import styles from './ParsedNote.module.scss'; -import { nip19 } from 'nostr-tools'; -import LinkPreview from '../LinkPreview/LinkPreview'; -import MentionedUserLink from '../Note/MentionedUserLink/MentionedUserLink'; - - -export const parseNoteLinks = (text: string, note: PrimalNote, highlightOnly = false) => { - +import { A } from "@solidjs/router"; +import { hexToNpub } from "../../lib/keys"; +import { linkPreviews, parseNote1 } from "../../lib/notes"; +import { truncateNpub, userName } from "../../stores/profile"; +import EmbeddedNote from "../EmbeddedNote/EmbeddedNote"; +import { Component, For, createEffect, createSignal } from "solid-js"; +import { PrimalNote } from "../../types/primal"; + +import styles from "./ParsedNote.module.scss"; +import { nip19 } from "nostr-tools"; +import LinkPreview from "../LinkPreview/LinkPreview"; +import MentionedUserLink from "../Note/MentionedUserLink/MentionedUserLink"; +import PostImage from "../PostImage/PostImage"; +import { useThreadContext } from "../../contexts/ThreadContext"; + +export const parseNoteLinks = ( + text: string, + note: PrimalNote, + highlightOnly = false +) => { const regex = /\bnostr:((note|nevent)1\w+)\b|#\[(\d+)\]/g; return text.replace(regex, (url) => { - const [_, id] = url.split(':'); + const [_, id] = url.split(":"); if (!id) { return url; @@ -29,40 +29,43 @@ export const parseNoteLinks = (text: string, note: PrimalNote, highlightOnly = f try { const eventId = nip19.decode(id).data as string | nip19.EventPointer; - const hex = typeof eventId === 'string' ? eventId : eventId.id; + const hex = typeof eventId === "string" ? eventId : eventId.id; const noteId = nip19.noteEncode(hex); const path = `/e/${noteId}`; const ment = note.mentionedNotes && note.mentionedNotes[hex]; - const link = highlightOnly ? - {url} : - ment ? -
- -
: - {url}; + const link = highlightOnly ? ( + {url} + ) : ment ? ( +
+ +
+ ) : ( + {url} + ); // @ts-ignore return link.outerHTML || url; } catch (e) { return `${url}`; } - }); - }; -export const parseNpubLinks = (text: string, note: PrimalNote, highlightOnly = false) => { - +export const parseNpubLinks = ( + text: string, + note: PrimalNote, + highlightOnly = false +) => { const regex = /\bnostr:((npub|nprofile)1\w+)\b|#\[(\d+)\]/g; return text.replace(regex, (url) => { - const [_, id] = url.split(':'); + const [_, id] = url.split(":"); if (!id) { return url; @@ -71,21 +74,25 @@ export const parseNpubLinks = (text: string, note: PrimalNote, highlightOnly = f try { const profileId = nip19.decode(id).data as string | nip19.ProfilePointer; - const hex = typeof profileId === 'string' ? profileId : profileId.pubkey; + const hex = typeof profileId === "string" ? profileId : profileId.pubkey; const npub = hexToNpub(hex); const path = `/p/${npub}`; const user = note.mentionedUsers && note.mentionedUsers[hex]; - let link = highlightOnly ? - @{truncateNpub(npub)} : - @{truncateNpub(npub)}; + let link = highlightOnly ? ( + @{truncateNpub(npub)} + ) : ( + @{truncateNpub(npub)} + ); if (user) { - link = highlightOnly ? - @{userName(user)} : - MentionedUserLink({ user }); + link = highlightOnly ? ( + @{userName(user)} + ) : ( + MentionedUserLink({ user }) + ); } // @ts-ignore @@ -94,11 +101,12 @@ export const parseNpubLinks = (text: string, note: PrimalNote, highlightOnly = f return `${url}`; } }); - }; -const ParsedNote: Component<{ note: PrimalNote, ignoreMentionedNotes?: boolean}> = (props) => { - +const ParsedNote: Component<{ + note: PrimalNote; + ignoreMentionedNotes?: boolean; +}> = (props) => { const parsedContent = (text: string) => { const regex = /\#\[([0-9]*)\]/g; let parsed = text; @@ -106,12 +114,12 @@ const ParsedNote: Component<{ note: PrimalNote, ignoreMentionedNotes?: boolean}> let refs = []; let match; - while((match = regex.exec(text)) !== null) { + while ((match = regex.exec(text)) !== null) { refs.push(match[1]); } if (refs.length > 0) { - for(let i =0; i < refs.length; i++) { + for (let i = 0; i < refs.length; i++) { let r = parseInt(refs[i]); const tag = props.note.post.tags[r]; @@ -119,7 +127,7 @@ const ParsedNote: Component<{ note: PrimalNote, ignoreMentionedNotes?: boolean}> if (tag === undefined || tag.length === 0) continue; if ( - tag[0] === 'e' && + tag[0] === "e" && props.note.mentionedNotes && props.note.mentionedNotes[tag[1]] ) { @@ -132,12 +140,15 @@ const ParsedNote: Component<{ note: PrimalNote, ignoreMentionedNotes?: boolean}> ); - // @ts-ignore parsed = parsed.replace(`#[${r}]`, embeded.outerHTML); } - if (tag[0] === 'p' && props.note.mentionedUsers && props.note.mentionedUsers[tag[1]]) { + if ( + tag[0] === "p" && + props.note.mentionedUsers && + props.note.mentionedUsers[tag[1]] + ) { const user = props.note.mentionedUsers[tag[1]]; const link = MentionedUserLink({ user }); @@ -149,35 +160,32 @@ const ParsedNote: Component<{ note: PrimalNote, ignoreMentionedNotes?: boolean}> } return parsed; - }; const highlightHashtags = (text: string) => { - const regex = /(?:\s|^)#[^\s!@#$%^&*(),.?":{}|<>]+/ig; + const regex = /(?:\s|^)#[^\s!@#$%^&*(),.?":{}|<>]+/gi; return text.replace(regex, (token) => { - const [space, term] = token.split('#'); + const [space, term] = token.split("#"); const embeded = ( {space} - #{term} + #{term} ); // @ts-ignore return embeded.outerHTML; }); - } + }; const replaceLinkPreviews = (text: string, previews: Record) => { let parsed = text; - const regex = /__LINK__.*?__LINK__/ig; + const regex = /__LINK__.*?__LINK__/gi; parsed = parsed.replace(regex, (link) => { - const url = link.split('__LINK__')[1]; + const url = link.split("__LINK__")[1]; const preview = previews[url]; @@ -190,41 +198,65 @@ const ParsedNote: Component<{ note: PrimalNote, ignoreMentionedNotes?: boolean}> return `${url}`; } - const linkElement = (
); + const linkElement = ( +
+ +
+ ); // @ts-ignore return linkElement.outerHTML; }); return parsed; - } + }; + + const parsed = parseNote1(props.note.post.content); const content = () => { return parseNoteLinks( parseNpubLinks( - parsedContent( - highlightHashtags( - parseNote1(props.note.post.content) - ), - ), - props.note, + parsedContent(highlightHashtags(parsed.urlified)), + props.note ), - props.note, + props.note ); }; - const [displayedContent, setDisplayedContent] = createSignal(content()); + const [displayedContent, setDisplayedContent] = createSignal( + content() + ); + const threadContext = useThreadContext(); createEffect(() => { - const newContent = replaceLinkPreviews(displayedContent(), { ...linkPreviews }); + const newContent = replaceLinkPreviews(displayedContent(), { + ...linkPreviews, + }); setDisplayedContent(() => newContent); }); + const navToThread = (note: PrimalNote) => { + threadContext?.actions.setPrimaryNote(note); + }; return ( -
-
+ <> + navToThread(props.note)} + data-event={props.note.post.id} + data-event-bech32={props.note.post.noteId} + > +
+ +
+ + {(url: string) => } + +
+ ); }; diff --git a/src/components/PostImage/PostImage.module.scss b/src/components/PostImage/PostImage.module.scss new file mode 100644 index 00000000..9f736957 --- /dev/null +++ b/src/components/PostImage/PostImage.module.scss @@ -0,0 +1,7 @@ +.postImage { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 8px; + z-index: 22; +} \ No newline at end of file diff --git a/src/components/PostImage/PostImage.tsx b/src/components/PostImage/PostImage.tsx new file mode 100644 index 00000000..63539cea --- /dev/null +++ b/src/components/PostImage/PostImage.tsx @@ -0,0 +1,39 @@ +import { onCleanup, onMount } from "solid-js"; +import styles from "./PostImage.module.scss"; +import mediumZoom from "medium-zoom"; +import type { Zoom } from 'medium-zoom'; + +type PostImageProps = { + src: string; +}; + +export default function PostImage(props: PostImageProps) { + let imgRef; + let zoomRef; + + function getZoom(): Zoom { + if (zoomRef) { + return zoomRef; + } + + const zoom = mediumZoom(imgRef, { + background: "#000" + }); + zoomRef = zoom; + + return zoom; + } + + onMount(() => { + if (imgRef) { + const zoom = getZoom(); + zoom.attach(imgRef); + + onCleanup(() => { + zoom.detach(imgRef); + }); + } + }); + + return ; +} diff --git a/src/index.scss b/src/index.scss index 5c22ea23..45eba15f 100644 --- a/src/index.scss +++ b/src/index.scss @@ -306,20 +306,16 @@ color: var(--accent-1); } - .postImage { - display: block; - width: 100%; - max-height: 1200px; - object-fit: contain; - border-radius: 12px; - } - .w-max { width: 100%; height: 300px; border-radius: 4px; } + .medium-zoom--opened .medium-zoom-overlay { + z-index: 22; + } + * { ::-moz-selection { color: var(--background-site); diff --git a/src/lib/notes.ts b/src/lib/notes.ts index 90dd52ed..15805f82 100644 --- a/src/lib/notes.ts +++ b/src/lib/notes.ts @@ -55,13 +55,17 @@ export const youtubeRegex = /(?:https?:\/\/)?(?:www|m\.)?(?:youtu\.be\/|youtube\ export const urlify = (text: string, highlightOnly = false, skipEmbed = false, skipLinkPreview = false) => { const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,8}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; - return text.replace(urlRegex, (url) => { + const imageUrls = []; + + const result = text.replace(urlRegex, (url) => { if (!skipEmbed) { const isImage = url.includes('.jpg')|| url.includes('.jpeg')|| url.includes('.webp') || url.includes('.png') || url.includes('.gif') || url.includes('format=png'); if (isImage) { - return '' + imageUrls.push(getMediaUrl(url)); + return ''; + // return '' } const isMp4Video = url.includes('.mp4') || url.includes('.mov'); @@ -187,6 +191,11 @@ export const urlify = (text: string, highlightOnly = false, skipEmbed = false, s return `__LINK__${url}__LINK__`; }) + + return { + imageUrls, + urlified: result + }; } export const replaceLinkPreviews = (text: string) => { diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index 36c95a3e..ed5bdb9a 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -307,7 +307,7 @@ const Messages: Component = () => { return parseNoteLinks( parseNpubLinks( highlightHashtags( - parseNote3(message) + parseNote3(message).urlified ), messages?.referecedUsers, ), diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 61284027..fbbc913e 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -335,7 +335,7 @@ const Profile: Component = () => {
-
+