From 757a93c2a22e3c8d2f04ccfb664e38e6257bc62b Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 21 Nov 2022 21:21:53 +0800 Subject: [PATCH] feat: support blurhash --- components/common/Blurhash.ts | 37 +++++++++++++++++++++++++ components/status/StatusAttachment.vue | 24 ++++++++++++---- components/status/StatusMedia.vue | 3 -- composables/content.ts | 2 +- composables/utils.ts | 16 +++++++++++ package.json | 1 + pages/@[user]/[post].vue | 38 ++++++++++++++++---------- pnpm-lock.yaml | 6 ++++ 8 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 components/common/Blurhash.ts create mode 100644 composables/utils.ts diff --git a/components/common/Blurhash.ts b/components/common/Blurhash.ts new file mode 100644 index 00000000..6b07bd89 --- /dev/null +++ b/components/common/Blurhash.ts @@ -0,0 +1,37 @@ +import { decode } from 'blurhash' +import { getDataUrlFromArr } from '~~/composables/utils' + +export default defineComponent({ + inheritAttrs: false, + props: { + blurhash: { + type: String, + required: true, + }, + src: { + type: String, + required: true, + }, + }, + setup(props, { attrs }) { + const placeholderSrc = ref() + const isLoaded = ref(false) + + onMounted(() => { + const img = document.createElement('img') + img.onload = () => { + isLoaded.value = true + } + img.src = props.src + + if (props.blurhash) { + const pixels = decode(props.blurhash, 32, 32) + placeholderSrc.value = getDataUrlFromArr(pixels, 32, 32) + } + }) + + return () => isLoaded.value || !placeholderSrc.value + ? h('img', { ...attrs, src: props.src }) + : h('img', { ...attrs, src: placeholderSrc.value }) + }, +}) diff --git a/components/status/StatusAttachment.vue b/components/status/StatusAttachment.vue index 6182225f..668b4ffa 100644 --- a/components/status/StatusAttachment.vue +++ b/components/status/StatusAttachment.vue @@ -4,21 +4,33 @@ import type { Attachment } from 'masto' const { attachment } = defineProps<{ attachment: Attachment }>() + +const aspectRatio = computed(() => { + if (attachment.meta?.original?.aspect) + return attachment.meta.original.aspect + if (attachment.meta?.small?.aspect) + return attachment.meta.small.aspect + return undefined +}) diff --git a/components/status/StatusMedia.vue b/components/status/StatusMedia.vue index 01298e25..1c162369 100644 --- a/components/status/StatusMedia.vue +++ b/components/status/StatusMedia.vue @@ -27,16 +27,13 @@ const { status } = defineProps<{ .status-media-container-2 { display: grid; grid-template-columns: 1fr 1fr; - aspect-ratio: 2/1; } .status-media-container-3 { display: grid; - aspect-ratio: 16/9; grid-template-columns: 1fr 1fr; } .status-media-container-4 { display: grid; - aspect-ratio: 16/9; grid-template-columns: 1fr 1fr; } diff --git a/composables/content.ts b/composables/content.ts index 57e62351..f1d55c14 100644 --- a/composables/content.ts +++ b/composables/content.ts @@ -36,7 +36,7 @@ export function contentToVNode( handle: (node: Element) => Element | undefined | null | void = defaultHandle, customEmojis: Record = {}, ): VNode { - content = content.replace(/:([\w-]+?):/g, (_, name) => { + content = content.trim().replace(/:([\w-]+?):/g, (_, name) => { const emoji = customEmojis[name] if (emoji) return `${name}` diff --git a/composables/utils.ts b/composables/utils.ts new file mode 100644 index 00000000..ba066592 --- /dev/null +++ b/composables/utils.ts @@ -0,0 +1,16 @@ +export function getDataUrlFromArr(arr: Uint8ClampedArray, w: number, h: number) { + if (typeof w === 'undefined' || typeof h === 'undefined') + w = h = Math.sqrt(arr.length / 4) + + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d')! + + canvas.width = w + canvas.height = h + + const imgData = ctx.createImageData(w, h) + imgData.data.set(arr) + ctx.putImageData(imgData, 0, 0) + + return canvas.toDataURL() +} diff --git a/package.json b/package.json index c8dd394d..c01be631 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@types/sanitize-html": "^2.6.2", "@unocss/nuxt": "^0.46.5", "@vueuse/nuxt": "^9.5.0", + "blurhash": "^2.0.4", "eslint": "^8.27.0", "form-data": "^4.0.0", "fs-extra": "^10.1.0", diff --git a/pages/@[user]/[post].vue b/pages/@[user]/[post].vue index debf39e9..f23035c1 100644 --- a/pages/@[user]/[post].vue +++ b/pages/@[user]/[post].vue @@ -8,21 +8,29 @@ const { data: context } = await useAsyncData(`${id}-context`, () => masto.status