Open Steinblock opened 5 years ago
I couldn't find an easy or efficient way to do this. But I did find a way.
What I did is defined an alternate markdown syntax for internal links as [text]{my-internal-link}
. Notice the curly braces instead of parentheses. Then I defined a vue-markdown prerender
method to intercept the markdown, extract the link information and ultimately substitute the content with a router-link
.
I had to jump through a few hoops to make this happen, but I was able to get it to work in the end.
I couldn't find an easy or efficient way to do this. But I did find a way.
What I did is defined an alternate markdown syntax for internal links as
[text]{my-internal-link}
. Notice the curly braces instead of parentheses. Then I defined a vue-markdownprerender
method to intercept the markdown, extract the link information and ultimately substitute the content with arouter-link
.I had to jump through a few hoops to make this happen, but I was able to get it to work in the end.
Could you detail your method with some code sample ?
Sure. Like I said, I had to jump through some hoops, so open to simplifying things. Ideally these features could be incorporated into vue-markdown.
This is my md file.
<div id="image">
</div>
[normal link](http://www.google.com)
[vue link]{/my/app/route}
<markdown :source="source">
<template v-slot:image>
<img src="/my/img"/>
</template>
</markdown>
<template>
<div>
<vue-markdown
v-bind="$attrs"
:toc="true"
:breaks="true"
:linkify="false"
:toc-anchor-link-space="true"
toc-anchor-link-symbol="¶"
:prerender="prerender"
>
<slot></slot>
</vue-markdown>
<div v-for="(i,s) in $slots" :key="s" :ref="s">
<slot :name="s" v-if="slotsInserted">
</slot>
</div>
<router-link v-for="(link) in internalLinks"
:to="link.to"
:key="link.id"
:ref="link.id">{{ link.text }}</router-link>
</div>
</template>
<script lang="ts">
import { CreateElement } from 'vue';
import { Vue, Component } from "vue-property-decorator";
import VueMarkdown from "vue-markdown";
import router from '@/router';
interface InternalLink {
id: string;
to: string;
text: string;
}
/**
* Component that wraps the <vue-markdown> component.
*
* This currently offers two main features:
*
* 1) Allows for using named slots to inject vue code
* into the markdown. To do this, in the .md file create
* a div/span with a certain id. Then create a slot with a name
* that matches the id. This will be injected into that placeholder
* div/span.
*
* 2) Adds support for internal links using router-link.
* Adds preprocessor for syntax [text]{internal-url}. Notice
* the curly braces instead of the parentheses.
*/
@Component({
components: {
VueMarkdown
}
})
export default class Markdown extends Vue {
public numSlots = this.$slots.length;
public slotsInserted = false;
public internalLinks: InternalLink[] = [];
public prerendered = false;
public mounted() {
this.insertSlotContentIntoMarkdown();
}
public updated() {
this.insertInternalLinksIntoMarkdown();
}
/**
* Insert all slots content
* into dom elements with id of slot name.
*/
public insertSlotContentIntoMarkdown() {
for(var slotName in this.$slots) {
if(slotName == null) {
continue;
}
var slot = (this.$refs[slotName] as Element[])[0];
var parent = this.$el.querySelector(`#${slotName}`);
if(slot !== null && parent !== null) {
parent.appendChild(slot);
} else {
console.error(`Markdown mapping error for id/slot "${slotName}"`);
}
}
this.slotsInserted = true;
}
/**
* Insert all internal router-links
* into dom elements with id.
*/
public insertInternalLinksIntoMarkdown() {
for(var link of this.internalLinks) {
var routerLink = (this.$refs[link.id] as any[])[0];
if (routerLink == null) {
continue;
}
var parent = this.$el.querySelector(`#${link.id}`);
if(routerLink !== null && parent !== null) {
parent.appendChild(routerLink.$el);
} else {
console.error(`Markdown mapping error for id/slot "${link.id}"`);
}
}
this.slotsInserted = true;
}
public prerender(content: string): string {
// substitute "internal" links ({text}(url) with div placeholder
// for slots.
const regex = /\[(.*?)\]\{(.*?)\}/g;
var match;
let internalLinks: InternalLink[] = [];
while ((match = regex.exec(content)) !== null) {
let to = match[2];
let text = match[1]
let id = `internal-link-${internalLinks.length}`;
internalLinks.push({ id, to, text });
let replacement = `<span id="${id}"></span>`;
content = content.replace(match[0], replacement);
}
if (!this.prerendered) {
this.internalLinks = internalLinks;
this.prerendered = true;
}
return content;
}
}
</script>
This feature should be prioritized. vue-markdown was so awesome, until I hit this issue. All my pages are FULL reloading. I use vue-markdown for localizing many of my pages.
Another (hacky) way I'm not proud of, but hey... it works!
document.querySelectorAll('#yourElement a').forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault()
this.$router.push({ path: a.attributes.href.value })
})
})
not having support for this is outrageous
For anyone else who happens to stumble upon this facing the same issue, here is the hacky approach I took which combines a bit of methodologies from other commentors.
<template>
<vue-markdown v-bind="$attrs" :linkify="false" ref="VMD">
<slot></slot>
</vue-markdown>
</template>
<script>
import Vue from "vue";
import VueMarkdown from "vue-markdown";
export default {
name: "WrapVueMarkdown",
components: {
VueMarkdown
},
mounted() {
this.handleReplaceWithRouterLinks();
},
methods: {
handleReplaceWithRouterLinks() {
const links = this.$refs.VMD.$el.querySelectorAll("a");
Array.from(links).forEach(aTag => {
const path = aTag.attributes.href.value;
const propsData = { to: path };
const parent = this;
const RouterLink = Vue.component("RouterLink");
const routerLink = new RouterLink({ propsData, parent });
routerLink.$mount(aTag);
routerLink.$el.innerText = aTag.innerText;
});
}
}
};
</script>
A simple wrapper that will replace a tags within the instance of that component with router-links after it's mounted. For my case, we're using markdown for the content of a Notifications list which also loads more Notifications as you scroll down, so I needed it to continually replace a tags as they were rendered.
Obviously the following code
gets translated into
Without
vue-markdown
I could writewhich will use client side routing instead of reloading the page. Is this possible to use this concept with
vue-markdown
?