mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-07-30 17:59:13 -04:00
Make referral links editable: add input field for customization, update share button and QR code to reflect changes dynamically.
This commit is contained in:
@@ -575,6 +575,7 @@
|
||||
"languages.en": "Englisch",
|
||||
"languages.fr": "Französisch",
|
||||
"media.next": "Weiter",
|
||||
"media.play_video": "Video abspielen",
|
||||
"media.previous": "Zurück",
|
||||
"messages.action.add_reaction": "Reaktion hinzufügen",
|
||||
"messages.action.delete": "Löschen",
|
||||
@@ -1360,6 +1361,7 @@
|
||||
"profiles.loading_slow": "Noch am Starten — gleich geht's los…",
|
||||
"profiles.title": "Personen",
|
||||
"profiles.try_keyword_search": "Schlüsselwortsuche versuchen",
|
||||
"referrals.link_label": "Dein Empfehlungslink",
|
||||
"referrals.title": "Lade jemanden ein, Compass beizutreten!",
|
||||
"register.agreement.and": " und ",
|
||||
"register.agreement.prefix": "Mit der Registrierung akzeptiere ich die ",
|
||||
|
||||
@@ -575,6 +575,7 @@
|
||||
"languages.en": "Anglais",
|
||||
"languages.fr": "Français",
|
||||
"media.next": "Suivant",
|
||||
"media.play_video": "Lire la vidéo",
|
||||
"media.previous": "Précédent",
|
||||
"messages.action.add_reaction": "Ajouter une réaction",
|
||||
"messages.action.delete": "Supprimer",
|
||||
@@ -1359,6 +1360,7 @@
|
||||
"profiles.loading_slow": "Démarrage en cours — encore quelques secondes…",
|
||||
"profiles.title": "Personnes",
|
||||
"profiles.try_keyword_search": "Essayer une recherche par mot-clé",
|
||||
"referrals.link_label": "Votre lien de parrainage",
|
||||
"referrals.title": "Invitez quelqu'un à rejoindre Compass !",
|
||||
"register.agreement.and": " et ",
|
||||
"register.agreement.prefix": "En vous inscrivant, j'accepte les ",
|
||||
|
||||
@@ -2,9 +2,12 @@ import clsx from 'clsx'
|
||||
import Video from 'common/util/tiptap-video'
|
||||
import {useState} from 'react'
|
||||
import {MediaModal} from 'web/components/media-modal'
|
||||
import {firstFrameSrc, VideoThumbnail} from 'web/components/widgets/video-thumbnail'
|
||||
|
||||
export const BasicVideo = Video.extend({
|
||||
renderReact: (attrs: any) => <video src={attrs.src} controls preload="metadata" />,
|
||||
// Controls are their own play affordance here; the fragment is what keeps the frame behind them
|
||||
// from being blank.
|
||||
renderReact: (attrs: any) => <video src={firstFrameSrc(attrs.src)} controls preload="metadata" />,
|
||||
})
|
||||
|
||||
export const DisplayVideo = Video.extend({
|
||||
@@ -21,16 +24,10 @@ function ExpandingVideo(props: {src: string; size?: 'md'}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<video
|
||||
{...rest}
|
||||
muted
|
||||
playsInline
|
||||
preload="metadata"
|
||||
<VideoThumbnail
|
||||
src={rest.src}
|
||||
onClick={() => setExpanded(true)}
|
||||
className={clsx(
|
||||
'cursor-pointer object-contain',
|
||||
size === 'md' ? 'max-h-[400px]' : 'h-[128px]',
|
||||
)}
|
||||
className={clsx(size === 'md' ? 'max-h-[400px]' : 'h-[128px]')}
|
||||
/>
|
||||
<MediaModal url={rest.src} open={expanded} setOpen={setExpanded} />
|
||||
</>
|
||||
|
||||
@@ -31,6 +31,7 @@ import {DisplayMention} from '../editor/user-mention/mention-extension'
|
||||
import {BasicVideo, DisplayVideo} from '../editor/video'
|
||||
import {Linkify} from './linkify'
|
||||
import {linkClass} from './site-link'
|
||||
import {VideoThumbnail} from './video-thumbnail'
|
||||
|
||||
const DisplayLink = Link.extend({
|
||||
renderHTML({HTMLAttributes}) {
|
||||
@@ -496,20 +497,12 @@ function recurse(
|
||||
)
|
||||
case 'video':
|
||||
return (
|
||||
<button
|
||||
<VideoThumbnail
|
||||
key={key}
|
||||
type="button"
|
||||
src={node.attrs?.src ?? ''}
|
||||
onClick={() => onMediaClick?.(node.attrs?.src ?? '')}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<video
|
||||
src={node.attrs?.src}
|
||||
muted
|
||||
playsInline
|
||||
preload="metadata"
|
||||
className={size === 'sm' ? 'max-h-32' : size === 'md' ? 'max-h-64' : undefined}
|
||||
/>
|
||||
</button>
|
||||
className={size === 'sm' ? 'max-h-32' : size === 'md' ? 'max-h-64' : undefined}
|
||||
/>
|
||||
)
|
||||
case 'table':
|
||||
return (
|
||||
|
||||
52
web/components/widgets/video-thumbnail.tsx
Normal file
52
web/components/widgets/video-thumbnail.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import {PlayIcon} from '@heroicons/react/24/solid'
|
||||
import clsx from 'clsx'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
/**
|
||||
* A video shown as a still cover frame with a play affordance.
|
||||
*
|
||||
* Nothing plays in place here, so the element paints only what the browser has decoded:
|
||||
* `preload="metadata"` on its own leaves Chrome and the Android WebView with an empty grey box.
|
||||
* The `#t=` media fragment asks for the frame at that timestamp, and the seek on loadedmetadata
|
||||
* covers the browsers that load the metadata but ignore the fragment.
|
||||
*/
|
||||
export function VideoThumbnail(props: {src: string; className?: string; onClick?: () => void}) {
|
||||
const {src, className, onClick} = props
|
||||
const t = useT()
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-label={t('media.play_video', 'Play video')}
|
||||
className="relative inline-block cursor-pointer align-middle leading-none"
|
||||
>
|
||||
<video
|
||||
src={firstFrameSrc(src)}
|
||||
muted
|
||||
playsInline
|
||||
preload="metadata"
|
||||
onLoadedMetadata={(e) => {
|
||||
const el = e.currentTarget
|
||||
// Only nudge a video parked at the very start: a fragment the browser did honour has
|
||||
// already moved it, and re-seeking would throw that frame away.
|
||||
if (el.currentTime === 0) el.currentTime = FIRST_FRAME_TIME
|
||||
}}
|
||||
className={clsx('object-contain', className)}
|
||||
/>
|
||||
<span className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<span className="flex h-10 w-10 items-center justify-center rounded-full bg-black/50 ring-1 ring-white/50">
|
||||
<PlayIcon className="ml-0.5 h-5 w-5 text-white" />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
/** Not 0: a request for exactly the start is what a browser showing a blank poster already did. */
|
||||
const FIRST_FRAME_TIME = 0.001
|
||||
|
||||
/** Media fragment asking the browser to show the opening frame rather than an empty box. */
|
||||
export function firstFrameSrc(src: string) {
|
||||
return src.includes('#') ? src : `${src}#t=${FIRST_FRAME_TIME}`
|
||||
}
|
||||
Reference in New Issue
Block a user