mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
* Use stricter Vue eslint setting * Implement v-md directive Replaces old uses of v-html + md util * Fix typo
606 lines
12 KiB
Vue
606 lines
12 KiB
Vue
<template>
|
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
<div class="md" :class="pageClass" @click="onClick" v-html="html" />
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, ref, onMounted, onUpdated, inject } from 'vue';
|
|
|
|
import MarkdownIt from 'markdown-it';
|
|
import markdownItTableOfContents from 'markdown-it-table-of-contents';
|
|
import markdownItAnchor from 'markdown-it-anchor';
|
|
import markdownItContainer from 'markdown-it-container';
|
|
import fm from 'front-matter';
|
|
|
|
import hljs from 'highlight.js';
|
|
import hljsGraphQL from '@/utils/hljs-graphql';
|
|
|
|
import { getRootPath } from '@/utils/get-root-path';
|
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
|
hljs.registerLanguage('graphql', hljsGraphQL);
|
|
|
|
const md = new MarkdownIt({
|
|
html: true,
|
|
highlight(str, lang) {
|
|
if (lang && hljs.getLanguage(lang)) {
|
|
try {
|
|
return hljs.highlight(str, { language: lang }).value;
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn('There was an error highlighting in Markdown');
|
|
// eslint-disable-next-line no-console
|
|
console.error(err);
|
|
}
|
|
}
|
|
|
|
return '';
|
|
},
|
|
});
|
|
|
|
md.use(markdownItTableOfContents, { includeLevel: [2] });
|
|
md.use(markdownItAnchor, { permalink: true, permalinkSymbol: '#' });
|
|
|
|
function hintRenderer(type: string) {
|
|
return (tokens: any[], idx: number) => {
|
|
const token = tokens[idx];
|
|
let title = token.info.trim().slice(type.length).trim() || '';
|
|
|
|
if (title) title = `<div class="hint-title">${title}</div>`;
|
|
|
|
if (token.nesting === 1) {
|
|
return `<div class="${type} hint">${title}\n`;
|
|
} else {
|
|
return '</div>\n';
|
|
}
|
|
};
|
|
}
|
|
|
|
md.use(markdownItContainer, 'tip', { render: hintRenderer('tip') });
|
|
md.use(markdownItContainer, 'warning', { render: hintRenderer('warning') });
|
|
md.use(markdownItContainer, 'danger', { render: hintRenderer('danger') });
|
|
|
|
export default defineComponent({
|
|
setup(props, { slots }) {
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
|
|
const html = ref('');
|
|
const pageClass = ref<string>();
|
|
|
|
onMounted(generateHTML);
|
|
onUpdated(generateHTML);
|
|
|
|
return { html, onClick, pageClass };
|
|
|
|
function generateHTML() {
|
|
const source = slots.default?.()[0].children;
|
|
|
|
if (!source || typeof source !== 'string') {
|
|
html.value = '';
|
|
return;
|
|
}
|
|
|
|
const { attributes, body } = fm<{ pageClass?: string }>(source);
|
|
|
|
let markdown = body;
|
|
|
|
const rawImages = body.matchAll(/!\[[^\]]*\]\((?<filename>.*?)(?="|\))(?<optionalpart>".*")?\)/g) ?? [];
|
|
const rootPath = getRootPath();
|
|
|
|
for (const rawImage of rawImages) {
|
|
const filenameParts = rawImage.groups!.filename.split('/');
|
|
|
|
while (filenameParts.includes('assets')) {
|
|
filenameParts.shift();
|
|
}
|
|
|
|
const newFilename = `${rootPath}admin/img/docs/${filenameParts.join('/')}`;
|
|
const newImage = rawImage[0].replace(rawImage.groups!.filename, newFilename);
|
|
markdown = markdown.replace(rawImage[0], newImage);
|
|
}
|
|
|
|
pageClass.value = attributes?.pageClass;
|
|
|
|
const htmlString = md.render(markdown);
|
|
|
|
html.value = htmlString;
|
|
|
|
// The Markdown is fetched async on page transition, which means the # link already exists before the markdown does
|
|
// This will force the main el to scroll down to the targetted element on updates of the content
|
|
const mainElement = inject('main-element', ref<Element | null>(null));
|
|
|
|
if (route.hash) {
|
|
const linkedEl = document.querySelector(route.hash) as HTMLElement;
|
|
|
|
if (linkedEl) {
|
|
mainElement.value?.scrollTo({ top: linkedEl.offsetTop - 100 });
|
|
}
|
|
}
|
|
}
|
|
|
|
function onClick(event: MouseEvent) {
|
|
if (
|
|
event.target &&
|
|
(event.target as HTMLElement).tagName.toLowerCase() === 'a' &&
|
|
(event.target as HTMLAnchorElement).href
|
|
) {
|
|
const link = (event.target as HTMLAnchorElement).getAttribute('href')!;
|
|
|
|
if (link.startsWith('http') || link.startsWith('#')) return;
|
|
|
|
event.preventDefault();
|
|
const parts = link.split('#');
|
|
parts[0] = parts[0].endsWith('/') ? parts[0].slice(0, -1) : parts[0];
|
|
router.push({
|
|
path: `/docs${parts[0]}`,
|
|
hash: parts[1],
|
|
});
|
|
}
|
|
}
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.error {
|
|
padding: 20vh 0;
|
|
}
|
|
|
|
.md {
|
|
max-width: 740px;
|
|
color: var(--foreground-normal-alt);
|
|
font-weight: 400;
|
|
font-size: 16px;
|
|
line-height: 27px;
|
|
}
|
|
|
|
.md > :deep(*:first-child) {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.md > :deep(*:last-child) {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.md :deep(a) {
|
|
color: var(--primary-110);
|
|
font-weight: 500;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.md :deep(h1),
|
|
.md :deep(h2),
|
|
.md :deep(h3),
|
|
.md :deep(h4),
|
|
.md :deep(h5),
|
|
.md :deep(h6) {
|
|
position: relative;
|
|
margin: 40px 0 8px;
|
|
padding: 0;
|
|
color: var(--foreground-normal-alt);
|
|
font-weight: 700;
|
|
cursor: text;
|
|
}
|
|
|
|
.md :deep(h1 a),
|
|
.md :deep(h2 a),
|
|
.md :deep(h3 a),
|
|
.md :deep(h4 a),
|
|
.md :deep(h5 a),
|
|
.md :deep(h6 a) {
|
|
position: absolute;
|
|
right: 100%;
|
|
padding-right: 4px;
|
|
opacity: 0;
|
|
}
|
|
|
|
.md :deep(h1) {
|
|
margin-bottom: 40px;
|
|
font-size: 35px;
|
|
line-height: 44px;
|
|
}
|
|
|
|
.md :deep(h2) {
|
|
margin-top: 60px;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 4px;
|
|
font-size: 24px;
|
|
line-height: 34px;
|
|
border-bottom: 2px solid var(--border-subdued);
|
|
}
|
|
|
|
.md :deep(h3) {
|
|
margin-bottom: 0px;
|
|
font-size: 19px;
|
|
line-height: 24px;
|
|
}
|
|
|
|
.md :deep(h4) {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.md :deep(h5) {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.md :deep(h6) {
|
|
color: var(--foreground-normal);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.md :deep(pre) {
|
|
padding: 16px 20px;
|
|
overflow: auto;
|
|
font-size: 13px;
|
|
line-height: 24px;
|
|
background-color: var(--background-normal);
|
|
border-radius: var(--border-radius);
|
|
}
|
|
|
|
.md :deep(code),
|
|
.md :deep(tt) {
|
|
margin: 0 1px;
|
|
padding: 0 4px;
|
|
font-size: 15px;
|
|
font-family: var(--family-monospace);
|
|
white-space: nowrap;
|
|
background-color: var(--background-page);
|
|
border: 1px solid var(--background-normal);
|
|
border-radius: var(--border-radius);
|
|
}
|
|
|
|
.md :deep(pre code) {
|
|
margin: 0;
|
|
padding: 0;
|
|
white-space: pre;
|
|
background: transparent;
|
|
border: none;
|
|
}
|
|
|
|
.md :deep(p) {
|
|
margin-block-start: 1em;
|
|
margin-block-end: 1em;
|
|
margin-inline-start: 0px;
|
|
margin-inline-end: 0px;
|
|
}
|
|
|
|
.md :deep(h3 + p) {
|
|
margin-block-start: 0.5em;
|
|
}
|
|
|
|
.md > :deep(h2:first-child) {
|
|
margin-top: 0;
|
|
padding-top: 0;
|
|
}
|
|
|
|
.md > :deep(h1:first-child) {
|
|
margin-top: 0;
|
|
padding-top: 0;
|
|
}
|
|
|
|
.md > :deep(h3:first-child),
|
|
.md > :deep(h4:first-child),
|
|
.md > :deep(h5:first-child),
|
|
.md > :deep(h6:first-child) {
|
|
margin-top: 0;
|
|
padding-top: 0;
|
|
}
|
|
|
|
.md :deep(blockquote) {
|
|
max-width: 740px;
|
|
margin-bottom: 4rem;
|
|
padding: 0.25rem 0 0.25rem 1rem;
|
|
color: var(--foreground-subdued);
|
|
font-size: 18px;
|
|
border-left: 2px solid var(--background-normal);
|
|
}
|
|
|
|
.md :deep(blockquote > :first-child) {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.md :deep(blockquote > :last-child) {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.md :deep(table) {
|
|
min-width: 100%;
|
|
margin: 40px 0;
|
|
padding: 0;
|
|
border-collapse: collapse;
|
|
border-spacing: 0;
|
|
}
|
|
|
|
.md :deep(img) {
|
|
max-width: 100%;
|
|
margin: 20px 0;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.md :deep(table img) {
|
|
margin: 0;
|
|
}
|
|
|
|
.md :deep(table tr) {
|
|
margin: 0;
|
|
padding: 0;
|
|
border-top: 1px solid var(--border-normal);
|
|
}
|
|
|
|
.md :deep(table tr:nth-child(2n)) {
|
|
background-color: var(--background-page);
|
|
}
|
|
|
|
.md :deep(table tr th) {
|
|
margin: 0;
|
|
padding: 8px 20px;
|
|
font-weight: bold;
|
|
text-align: left;
|
|
border: 1px solid var(--border-normal);
|
|
}
|
|
|
|
.md :deep(table tr td) {
|
|
margin: 0;
|
|
padding: 8px 20px;
|
|
text-align: left;
|
|
border: 1px solid var(--border-normal);
|
|
}
|
|
|
|
.md :deep(a:first-child h1),
|
|
.md :deep(a:first-child h2),
|
|
.md :deep(a:first-child h3),
|
|
.md :deep(a:first-child h4),
|
|
.md :deep(a:first-child h5),
|
|
.md :deep(a:first-child h6) {
|
|
margin-top: 0;
|
|
padding-top: 0;
|
|
}
|
|
|
|
.md :deep(table tr th :first-child),
|
|
.md :deep(table tr td :first-child) {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.md :deep(table tr th :last-child),
|
|
.md :deep(table tr td :last-child) {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.md :deep(h1 a:hover),
|
|
.md :deep(h2 a:hover),
|
|
.md :deep(h3 a:hover),
|
|
.md :deep(h4 a:hover),
|
|
.md :deep(h5 a:hover),
|
|
.md :deep(h6 a:hover) {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.md :deep(h1:hover a),
|
|
.md :deep(h2:hover a),
|
|
.md :deep(h3:hover a),
|
|
.md :deep(h4:hover a),
|
|
.md :deep(h5:hover a),
|
|
.md :deep(h6:hover a) {
|
|
opacity: 1;
|
|
}
|
|
|
|
.md :deep(pre code),
|
|
.md :deep(pre tt) {
|
|
background-color: transparent;
|
|
border: none;
|
|
}
|
|
|
|
.md :deep(h1 tt),
|
|
.md :deep(h1 code),
|
|
.md :deep(h2 tt),
|
|
.md :deep(h2 code),
|
|
.md :deep(h3 tt),
|
|
.md :deep(h3 code),
|
|
.md :deep(h4 tt),
|
|
.md :deep(h4 code),
|
|
.md :deep(h5 tt),
|
|
.md :deep(h5 code),
|
|
.md :deep(h6 tt),
|
|
.md :deep(h6 code) {
|
|
font-size: inherit;
|
|
}
|
|
|
|
.md :deep(h1 p),
|
|
.md :deep(h2 p),
|
|
.md :deep(h3 p),
|
|
.md :deep(h4 p),
|
|
.md :deep(h5 p),
|
|
.md :deep(h6 p) {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.md :deep(ul),
|
|
.md :deep(ol) {
|
|
margin: 20px 0;
|
|
padding-left: 20px;
|
|
}
|
|
|
|
.md :deep(ul li),
|
|
.md :deep(ol li) {
|
|
margin: 8px 0;
|
|
line-height: 24px;
|
|
}
|
|
|
|
.md :deep(ul ul),
|
|
.md :deep(ul ol),
|
|
.md :deep(ol ul),
|
|
.md :deep(ol ol) {
|
|
margin: 4px 0;
|
|
}
|
|
|
|
.md :deep(ul ul li),
|
|
.md :deep(ul ol li),
|
|
.md :deep(ol ul li),
|
|
.md :deep(ol ol li) {
|
|
margin: 4px 0;
|
|
line-height: 24px;
|
|
}
|
|
|
|
.md :deep(img.no-margin) {
|
|
margin: 0;
|
|
}
|
|
|
|
.md :deep(img.full) {
|
|
width: 100%;
|
|
}
|
|
|
|
.md :deep(img.shadow) {
|
|
box-shadow: 0px 5px 10px 0px rgba(23, 41, 64, 0.1), 0px 2px 40px 0px rgba(23, 41, 64, 0.05);
|
|
}
|
|
|
|
.md.page-reference {
|
|
max-width: 1200px;
|
|
}
|
|
|
|
.md.page-reference :deep(hr) {
|
|
position: relative;
|
|
left: -2.5rem;
|
|
width: calc(100% + 5rem);
|
|
margin: 3rem 0;
|
|
}
|
|
|
|
.md.page-reference :deep(h2) {
|
|
margin-top: 3rem;
|
|
font-size: 2rem;
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.md.page-reference :deep(h3) {
|
|
margin-top: 3rem;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.md.page-reference :deep(h4) {
|
|
margin-top: 2rem;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.md :deep(.heading-link) {
|
|
color: var(--foreground-subdued);
|
|
font-size: 16px;
|
|
}
|
|
|
|
.md :deep(.heading-link:hover) {
|
|
color: var(--primary-110);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.md :deep(li p.first) {
|
|
display: inline-block;
|
|
}
|
|
|
|
.md :deep(.table-of-contents ul),
|
|
.md :deep(.table-of-contents ol) {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.md :deep(.table-of-contents ul li),
|
|
.md :deep(.table-of-contents ol li) {
|
|
margin: 4px 0;
|
|
}
|
|
|
|
.md :deep(.hint) {
|
|
display: inline-block;
|
|
width: 100%;
|
|
margin: 20px 0;
|
|
padding: 0 20px;
|
|
background-color: var(--background-subdued);
|
|
border-left: 2px solid var(--primary);
|
|
}
|
|
|
|
.md :deep(.two-up) {
|
|
margin-top: 3rem;
|
|
}
|
|
|
|
.md :deep(.table-of-contents) {
|
|
margin-top: -20px;
|
|
}
|
|
|
|
.md :deep(.hint-title) {
|
|
margin-block-start: 1em;
|
|
margin-block-end: 1em;
|
|
margin-inline-start: 0px;
|
|
margin-inline-end: 0px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.md :deep(.hint.tip) {
|
|
border-left: 2px solid var(--primary);
|
|
}
|
|
|
|
.md :deep(.hint.warning) {
|
|
background-color: var(--warning-10);
|
|
border-left: 2px solid var(--warning);
|
|
}
|
|
|
|
.md :deep(.hint.danger) {
|
|
background-color: var(--danger-10);
|
|
border-left: 2px solid var(--danger);
|
|
}
|
|
|
|
.md :deep(.two-up .right) {
|
|
margin-top: 50px;
|
|
}
|
|
|
|
.md :deep(.two-up .right h5) {
|
|
margin-top: 20px;
|
|
color: var(--foreground-subdued);
|
|
}
|
|
|
|
.md.page-reference :deep(.definitions) {
|
|
font-size: 0.9rem;
|
|
line-height: 1.5rem;
|
|
}
|
|
|
|
.md.page-reference :deep(.definitions > p) {
|
|
margin: 0;
|
|
padding: 0.8rem 0;
|
|
border-bottom: 2px solid var(--border-subdued);
|
|
}
|
|
|
|
.md.page-reference :deep(.definitions > p:first-child) {
|
|
border-top: 2px solid var(--border-subdued);
|
|
}
|
|
|
|
.md.page-reference :deep(.definitions > p > code:first-child) {
|
|
margin-right: 0.2rem;
|
|
padding: 0;
|
|
font-weight: 700;
|
|
font-size: 0.9rem;
|
|
background: transparent;
|
|
border: 0;
|
|
}
|
|
|
|
.md.page-reference :deep(.definitions > p > strong) {
|
|
color: var(--foreground-subdued);
|
|
}
|
|
|
|
@media (min-width: 1000px) {
|
|
.md :deep(.two-up) {
|
|
display: grid;
|
|
grid-gap: 40px;
|
|
grid-template-columns: minmax(0, 4fr) minmax(0, 3fr);
|
|
align-items: start;
|
|
}
|
|
|
|
.md :deep(.two-up .right) {
|
|
position: sticky;
|
|
top: 100px;
|
|
margin-top: 0;
|
|
}
|
|
|
|
.md :deep(.two-up .left > *:first-child),
|
|
.md :deep(.two-up .right > *:first-child) {
|
|
margin-top: 0 !important;
|
|
}
|
|
}
|
|
</style>
|