feat: StackBlitz code block expansion (#1240)
Co-authored-by: Daniel Roe <daniel@roe.dev>zio/stable
parent
486f1baf37
commit
8adb9f403b
|
@ -9,74 +9,13 @@ const props = defineProps<{
|
||||||
root?: boolean
|
root?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// mastodon's default max og image width
|
|
||||||
const ogImageWidth = 400
|
|
||||||
|
|
||||||
const alt = $computed(() => `${props.card.title} - ${props.card.title}`)
|
|
||||||
const isSquare = $computed(() => (
|
|
||||||
props.smallPictureOnly
|
|
||||||
|| props.card.width === props.card.height
|
|
||||||
|| Number(props.card.width || 0) < ogImageWidth
|
|
||||||
|| Number(props.card.height || 0) < ogImageWidth / 2
|
|
||||||
))
|
|
||||||
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
|
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
|
||||||
|
|
||||||
const gitHubCards = $(usePreferences('experimentalGitHubCards'))
|
const gitHubCards = $(usePreferences('experimentalGitHubCards'))
|
||||||
|
|
||||||
// TODO: handle card.type: 'photo' | 'video' | 'rich';
|
|
||||||
const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
|
||||||
link: 'i-ri:profile-line',
|
|
||||||
photo: 'i-ri:image-line',
|
|
||||||
video: 'i-ri:play-line',
|
|
||||||
rich: 'i-ri:profile-line',
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<StatusPreviewGitHub v-if="gitHubCards && providerName === 'GitHub'" :card="card" />
|
<LazyStatusPreviewGitHub v-if="gitHubCards && providerName === 'GitHub'" :card="card" />
|
||||||
<NuxtLink
|
<LazyStatusPreviewStackBlitz v-else-if="gitHubCards && providerName === 'stackblitz.com'" :card="card" :small-picture-only="smallPictureOnly" :root="root" />
|
||||||
v-else
|
<StatusPreviewCardNormal v-else :card="card" :small-picture-only="smallPictureOnly" :root="root" />
|
||||||
block
|
|
||||||
of-hidden
|
|
||||||
:to="card.url"
|
|
||||||
bg-card
|
|
||||||
hover:bg-active
|
|
||||||
:class="{
|
|
||||||
'flex': isSquare,
|
|
||||||
'p-4': root,
|
|
||||||
'rounded-lg': !root,
|
|
||||||
}"
|
|
||||||
target="_blank"
|
|
||||||
external
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="card.image"
|
|
||||||
flex flex-col
|
|
||||||
display-block of-hidden
|
|
||||||
:class="{
|
|
||||||
'sm:(min-w-32 w-32 h-32) min-w-24 w-24 h-24': isSquare,
|
|
||||||
'w-full aspect-[1.91]': !isSquare,
|
|
||||||
'rounded-lg': root,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<CommonBlurhash
|
|
||||||
:blurhash="card.blurhash"
|
|
||||||
:src="card.image"
|
|
||||||
:width="card.width"
|
|
||||||
:height="card.height"
|
|
||||||
:alt="alt"
|
|
||||||
w-full h-full object-cover
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
min-w-24 w-24 h-24 sm="min-w-32 w-32 h-32" bg="slate-500/10" flex justify-center items-center
|
|
||||||
:class="[
|
|
||||||
root ? 'rounded-lg' : '',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<div :class="cardTypeIconMap[card.type]" w="30%" h="30%" text-secondary />
|
|
||||||
</div>
|
|
||||||
<StatusPreviewCardInfo :p="isSquare ? 'x-4' : '4'" :root="root" :card="card" :provider="providerName" />
|
|
||||||
</NuxtLink>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { mastodon } from 'masto'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
card: mastodon.v1.PreviewCard
|
||||||
|
/** For the preview image, only the small image mode is displayed */
|
||||||
|
smallPictureOnly?: boolean
|
||||||
|
/** When it is root card in the list, not appear as a child card */
|
||||||
|
root?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// mastodon's default max og image width
|
||||||
|
const ogImageWidth = 400
|
||||||
|
|
||||||
|
const alt = $computed(() => `${props.card.title} - ${props.card.title}`)
|
||||||
|
const isSquare = $computed(() => (
|
||||||
|
props.smallPictureOnly
|
||||||
|
|| props.card.width === props.card.height
|
||||||
|
|| Number(props.card.width || 0) < ogImageWidth
|
||||||
|
|| Number(props.card.height || 0) < ogImageWidth / 2
|
||||||
|
))
|
||||||
|
const providerName = $computed(() => props.card.providerName ? props.card.providerName : new URL(props.card.url).hostname)
|
||||||
|
|
||||||
|
// TODO: handle card.type: 'photo' | 'video' | 'rich';
|
||||||
|
const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
||||||
|
link: 'i-ri:profile-line',
|
||||||
|
photo: 'i-ri:image-line',
|
||||||
|
video: 'i-ri:play-line',
|
||||||
|
rich: 'i-ri:profile-line',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink
|
||||||
|
block
|
||||||
|
of-hidden
|
||||||
|
:to="card.url"
|
||||||
|
bg-card
|
||||||
|
hover:bg-active
|
||||||
|
:class="{
|
||||||
|
'flex': isSquare,
|
||||||
|
'p-4': root,
|
||||||
|
'rounded-lg': !root,
|
||||||
|
}"
|
||||||
|
target="_blank"
|
||||||
|
external
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="card.image"
|
||||||
|
flex flex-col
|
||||||
|
display-block of-hidden
|
||||||
|
:class="{
|
||||||
|
'sm:(min-w-32 w-32 h-32) min-w-24 w-24 h-24': isSquare,
|
||||||
|
'w-full aspect-[1.91]': !isSquare,
|
||||||
|
'rounded-lg': root,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<CommonBlurhash
|
||||||
|
:blurhash="card.blurhash"
|
||||||
|
:src="card.image"
|
||||||
|
:width="card.width"
|
||||||
|
:height="card.height"
|
||||||
|
:alt="alt"
|
||||||
|
w-full h-full object-cover
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
min-w-24 w-24 h-24 sm="min-w-32 w-32 h-32" bg="slate-500/10" flex justify-center items-center
|
||||||
|
:class="[
|
||||||
|
root ? 'rounded-lg' : '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div :class="cardTypeIconMap[card.type]" w="30%" h="30%" text-secondary />
|
||||||
|
</div>
|
||||||
|
<StatusPreviewCardInfo :p="isSquare ? 'x-4' : '4'" :root="root" :card="card" :provider="providerName" />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
|
@ -0,0 +1,93 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { mastodon } from 'masto'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
card: mastodon.v1.PreviewCard
|
||||||
|
/** For the preview image, only the small image mode is displayed */
|
||||||
|
smallPictureOnly?: boolean
|
||||||
|
/** When it is root card in the list, not appear as a child card */
|
||||||
|
root?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
interface Meta {
|
||||||
|
code?: string
|
||||||
|
file?: string
|
||||||
|
lines?: string
|
||||||
|
project?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = $computed(() => {
|
||||||
|
const { description } = props.card
|
||||||
|
const meta = description.match(/.+\n\nCode Snippet from (.+), lines ([\w-]+)\n\n(.+)/s)
|
||||||
|
const file = meta?.[1]
|
||||||
|
const lines = meta?.[2].replaceAll('N', '')
|
||||||
|
const code = meta?.[3]
|
||||||
|
const project = props.card.title?.replace(' - StackBlitz', '')
|
||||||
|
const info = $ref<Meta>({
|
||||||
|
file,
|
||||||
|
lines,
|
||||||
|
code,
|
||||||
|
project,
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
})
|
||||||
|
|
||||||
|
const vnodeCode = $computed(() => {
|
||||||
|
if (!meta.code)
|
||||||
|
return null
|
||||||
|
const vnode = contentToVNode(`<p>\`\`\`${meta.file?.split('.')?.[1] ?? ''}\n${meta.code}\n\`\`\`\</p>`, {
|
||||||
|
markdown: true,
|
||||||
|
})
|
||||||
|
return vnode
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="meta.code"
|
||||||
|
flex flex-col gap-1
|
||||||
|
display-block of-hidden
|
||||||
|
w-full
|
||||||
|
rounded-lg
|
||||||
|
overflow-hidden
|
||||||
|
pb-2
|
||||||
|
>
|
||||||
|
<div whitespace-pre-wrap break-words>
|
||||||
|
<span v-if="vnodeCode" class="content-rich line-compact" dir="auto">
|
||||||
|
<component :is="vnodeCode" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
flex flex-col
|
||||||
|
display-block of-hidden
|
||||||
|
bg-card
|
||||||
|
w-full
|
||||||
|
justify-center
|
||||||
|
p-3
|
||||||
|
pb-4
|
||||||
|
>
|
||||||
|
<div flex justify-between>
|
||||||
|
<p flex gap-1>
|
||||||
|
<span>Code Snippet from</span><span>{{ meta.file }}</span><span text-secondary>{{ `- Lines ${meta.lines}` }}</span>
|
||||||
|
</p>
|
||||||
|
<NuxtLink external target="_blank" btn-solid py-0 px-2 :to="card.url">
|
||||||
|
Open
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div flex font-bold gap-2>
|
||||||
|
<span text-primary>{{ meta.project }}</span><span flex text-secondary><span flex items-center><svg h-5 width="22.27" height="32" viewBox="0 0 256 368"><path fill="currentColor" d="M109.586 217.013H0L200.34 0l-53.926 150.233H256L55.645 367.246l53.927-150.233z" /></svg></span><span>StackBlitz</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<StatusPreviewCardNormal v-else :card="card" :small-picture-only="smallPictureOnly" :root="root" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.content-rich p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.code-block {
|
||||||
|
margin-top: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue