diff --git a/new-web/bun.lockb b/new-web/bun.lockb index 07d55d5..f3b54dc 100755 Binary files a/new-web/bun.lockb and b/new-web/bun.lockb differ diff --git a/new-web/package.json b/new-web/package.json index 91a547f..9952319 100644 --- a/new-web/package.json +++ b/new-web/package.json @@ -54,6 +54,7 @@ "shiki-transformer-copy-button": "^0.0.3", "svelte-bricks": "^0.2.1", "svelte-lazy": "1.2.9", - "svelte-legos": "^0.2.5" + "svelte-legos": "^0.2.5", + "unist-util-visit": "^5.0.0" } } diff --git a/new-web/src/lib/elements/ArticlePage.svelte b/new-web/src/lib/elements/ArticlePage.svelte index 03dab0f..c7c832f 100644 --- a/new-web/src/lib/elements/ArticlePage.svelte +++ b/new-web/src/lib/elements/ArticlePage.svelte @@ -241,7 +241,7 @@ } :global(.prose a::after) { - content: ""; + content: ''; @apply icon-[heroicons-outline--external-link] inline-block ml-1 w-3.5 h-3.5 align-text-bottom; } @@ -323,4 +323,45 @@ :global(.prose kbd) { @apply px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500; } + + /* Mixtape embeds */ + :global(.prose .mixtape-embed) { + @apply items-center p-2 overflow-hidden border border-gray-300 mt-7; + } + + :global(.prose .mixtape-content) { + @apply flex flex-row justify-between p-2 overflow-hidden; + } + + :global(.prose .mixtape-text) { + @apply flex flex-col justify-center p-2; + } + + :global(.prose .mixtape-text h2) { + @apply text-base font-bold text-black dark:text-gray-100; + } + + :global(.prose .mixtape-description) { + @apply block mt-2; + } + + :global(.prose .mixtape-description h3) { + @apply text-sm text-gray-700; + } + + :global(.prose .mixtape-site) { + @apply mt-5; + } + + :global(.prose .mixtape-site p) { + @apply text-xs text-gray-700; + } + + :global(.prose .mixtape-image) { + @apply relative flex h-40 flex-row w-60; + } + + :global(.prose .mixtape-image-inner) { + @apply absolute inset-0 bg-center bg-cover; + } diff --git a/new-web/src/lib/test/blog01.md b/new-web/src/lib/test/blog01.md index 87bf3c1..064119d 100644 --- a/new-web/src/lib/test/blog01.md +++ b/new-web/src/lib/test/blog01.md @@ -129,3 +129,7 @@ _For more information, visit our [documentation](https://docs.uploadthing.com)._ ```js console.log('Hello, world!'); ``` + +[](https://miro.medium.com/v2/resize:fit:320/1*abc123.jpg) +> **Why React Hooks are the Future of React Development** +> Learn how React Hooks are revolutionizing the way we write components and manage state in React applications. Discover the benefits and best practices. diff --git a/new-web/src/lib/utils/remark/mixtape-embed.js b/new-web/src/lib/utils/remark/mixtape-embed.js new file mode 100644 index 0000000..52c08ce --- /dev/null +++ b/new-web/src/lib/utils/remark/mixtape-embed.js @@ -0,0 +1,41 @@ +import { visit } from 'unist-util-visit'; + +function remarkMixtapeEmbed() { + return (tree) => { + visit(tree, 'paragraph', (node) => { + const text = node.children[0].value; + const mixtapeRegex = /\[!\[(.*?)\]\((.*?)\)\]\((.*?)\)\n>(.*?)\n>(.*?)\n/; + + const match = mixtapeRegex.exec(text); + if (match) { + const [, altText, linkUrl, imageUrl, title, description] = match; + const siteName = new URL(linkUrl).hostname; + + node.type = 'html'; + node.value = ` +
+ `; + node.children = undefined; + } + }); + }; +} + +export default remarkMixtapeEmbed; diff --git a/new-web/svelte.config.js b/new-web/svelte.config.js index 92e6aa1..df87e43 100644 --- a/new-web/svelte.config.js +++ b/new-web/svelte.config.js @@ -5,6 +5,7 @@ import adapter from '@sveltejs/adapter-auto'; import { toHtml } from 'hast-util-to-html'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { h } from 'hastscript'; +import remarkMixtapeEmbed from './src/lib/utils/remark/mixtape-embed.js'; const readySvg = "data:image/svg+xml;utf8,"; @@ -91,6 +92,7 @@ export function renderCodeCopyButton(code, options = {}) { /** @type {import('mdsvex').MdsvexOptions} */ const mdsvexOptions = { extensions: ['.md'], + remarkPlugins: [remarkMixtapeEmbed], rehypePlugins: [[rehypeExternalLinks, { target: '_blank', rel: ['nofollow'] }]], highlight: { highlighter: async (code, lang = 'text') => {