Last active
October 2, 2024 23:14
-
-
Save Anoesj/71a445cb95bfffbbb71850d6b6d0f3c4 to your computer and use it in GitHub Desktop.
Nuxt v-html but an <a> navigates using the router, simulating <NuxtLink>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This registers a v-nuxt-html directive, that can be used as a drop-in replacement for v-html. | |
It makes sure all rendered <a> tags navigate using the router, simulating the behavior of <NuxtLink>. | |
Inspired by https://www.trpkovski.com/2024/03/24/is-it-possible-to-use-nuxt-link-in-content-rendered-with-v-html | |
Improved so it renders the HTML on the server side as well and doesn't | |
`event.preventDefault` the click in some situations. | |
*/ | |
export default defineNuxtPlugin((nuxtApp) => { | |
function handleLinkClick (event: MouseEvent) { | |
const a = event.target as HTMLAnchorElement; | |
const href = a.getAttribute('href'); | |
const target = a.getAttribute('target'); | |
/* | |
Alt key actually triggers a download of the link, but not in | |
every browser and it's a useless feature anyway, so we can | |
event.preventDefault() it without feeling guilty :-) | |
*/ | |
const isShortcutKeyPressed = | |
// In new tab (Windows/Linux) | |
event.ctrlKey | |
// In new window (not sure if in all browsers and OS's or just some though) | |
|| event.shiftKey | |
// In new tab (Mac) | |
|| event.metaKey; | |
if (href && target !== '_blank' && !isShortcutKeyPressed) { | |
if (href.startsWith('/')) { | |
event.preventDefault(); | |
useNuxtApp().$router.push(href); | |
} | |
else if (href.startsWith(window.location.origin)) { | |
event.preventDefault(); | |
useNuxtApp().$router.push(href.replace(window.location.origin, '')); | |
} | |
} | |
} | |
function addListeners (el: HTMLElement) { | |
if (import.meta.client) { | |
for (const a of el.querySelectorAll('a')) { | |
a.addEventListener('click', handleLinkClick, { passive: false }); | |
} | |
} | |
} | |
function removeListeners (el: HTMLElement) { | |
if (import.meta.client) { | |
for (const a of el.querySelectorAll('a')) { | |
a.removeEventListener('click', handleLinkClick); | |
} | |
} | |
} | |
nuxtApp.vueApp.directive<HTMLElement, string>('nuxtHtml', { | |
mounted (el, binding) { | |
el.innerHTML = binding.value; | |
addListeners(el); | |
}, | |
beforeUpdate (el) { | |
removeListeners(el); | |
}, | |
updated (el, binding) { | |
el.innerHTML = binding.value; | |
addListeners(el); | |
}, | |
beforeUnmount (el) { | |
removeListeners(el); | |
}, | |
getSSRProps (binding) { | |
// This makes sure during SSR the `innerHTML` property of the element is set to the bindng (the html string). | |
// During SSR, all hooks above don't run at all: https://vuejs.org/guide/scaling-up/ssr.html#custom-directives | |
return { | |
innerHTML: binding.value, | |
}; | |
}, | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment