Skip to content

Commit

Permalink
Use voyagers marked
Browse files Browse the repository at this point in the history
  • Loading branch information
emptynick committed Nov 22, 2021
1 parent 4a26040 commit 864dc69
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 206 deletions.
2 changes: 1 addition & 1 deletion dist/voyager-docs.umd.js

Large diffs are not rendered by default.

101 changes: 18 additions & 83 deletions src/VoyagerDocs.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Emptynick\VoyagerDocs;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
Expand Down Expand Up @@ -32,56 +31,36 @@ public function provideProtectedRoutes(): void
{
Inertia::setRootView('voyager::app');

Route::get('docs', function (Request $request) {
Event::dispatch('voyager.page');
$path = $request->get('path', 'introduction.md');
$currentPath = $path;
$path = str_replace('/', DIRECTORY_SEPARATOR, Str::start(urldecode($path), '/'));
Route::get('docs/{path?}', function ($path = 'introduction.md') {
$path = base_path('vendor/voyager-admin/voyager/docs/').$path;
if (File::exists($path)) {
$content = file_get_contents($path);

if (Str::contains($path, '..')) {
abort(404);
}
if (Str::contains($path, '.gitbook')) {
$extension = Str::afterLast($path, '.');
$mime = $this->mime_extensions[$extension] ?? File::mimeType($path);

$path = base_path('vendor/voyager-admin/voyager/docs').$path;
$response = response(File::get($path), 200, ['Content-Type' => $mime]);
$response->setSharedMaxAge(31536000);
$response->setMaxAge(31536000);
$response->setExpires(new \DateTime('+1 year'));

return $response;
}

if (File::exists($path)) {
$content = file_get_contents($path);
$title = Str::after(Str::before($content, "\n"), '# ');
$content = $this->parseHTML(Str::markdown(Str::after($content, "\n")), true);
$toc = $this->parseSummary(Str::markdown(Str::after(file_get_contents(base_path('vendor/voyager-admin/voyager/docs/summary.md')), "\n")));

return Inertia::render('voyager-docs', [
'title' => $title,
'content' => $content,
'toc' => $toc,
'path' => $request->get('path', 'introduction.md'),
'current' => $currentPath,
'toc' => Str::after(file_get_contents(base_path('vendor/voyager-admin/voyager/docs/summary.md')), "\n"),
'path' => Str::beforeLast(\Request::url(), '/').'/',
'base' => route('voyager.voyager-docs').'/',
])->withViewData('title', $title);
}

abort(404);
})->name('voyager-docs');

Route::get('docs-asset', function (Request $request) {
$path = $request->get('path', '');
$path = str_replace('/', DIRECTORY_SEPARATOR, Str::start(urldecode($path), '/'));
$start = Str::beforeLast(urldecode($path), DIRECTORY_SEPARATOR);
$path = base_path('vendor/voyager-admin/voyager/docs/.gitbook/assets').$path;

if (File::exists($path)) {
$extension = Str::afterLast($path, '.');
$mime = $this->mime_extensions[$extension] ?? File::mimeType($path);

$response = response(File::get($path), 200, ['Content-Type' => $mime]);
$response->setSharedMaxAge(31536000);
$response->setMaxAge(31536000);
$response->setExpires(new \DateTime('+1 year'));

return $response;
}

abort(404);
})->name('voyager-docs-asset');
})->where('path', '.*')->name('voyager-docs');
}

public function provideJS(): string
Expand All @@ -98,48 +77,4 @@ public function provideMenuItems(MenuManager $menumanager): void
$item
);
}

private function parseHTML($content, $relative = true)
{
// Replace tables
$content = str_replace(['<table>', '</table>', '<p>'], ['<div class="voyager-table"><table>', '</table></div>', '<p class="mb-4">'], $content);

return $content;
}

private function parseSummary($content)
{
$xml = simplexml_load_string('<div>'.$content.'</div>');
$array = [];
$lastMain = '';
foreach ($xml->children() as $node) {
if (in_array($node->getName(), ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])) {
$array[(string) $node] = [];
$lastMain = (string) $node;
} else {
$array[$lastMain] = $this->ulToArray($node);
}
}

return $array;
}

private function ulToArray($ul)
{
$array = [];
if ($ul->getName() == 'ul') {
foreach ($ul->children() as $node) {
if (count($node->children()) > 1) {
$array[(string) $node->children()[0]] = $this->ulToArray($node->children()[1]);
} else {
$array[] = [
'title' => (string) $node->children()[0],
'href' => (string) $node->children()[0]->attributes()['href']
];
}
}
}

return $array;
}
}
153 changes: 33 additions & 120 deletions src/components/Docs.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
<template>
<Card :title="title">
<div class="w-full my-4 flex flex-wrap space-x-1 space-y-1">
<template v-for="(main, title) in toc">
<template v-if="title !== ''">
<Dropdown class="self-center" placement="bottom-start">
<template v-for="(sub, subtitle) in main">
<a v-if="sub.title" :href="getLink(sub.href)" class="link rounded">{{ sub.title }}</a>
<div v-else class="block mx-6 my-3 leading-tight">
<span class="block px-6">{{ subtitle }}</span>
<a v-for="subsub in sub" :href="getLink(subsub.href)" class="link rounded mx-2">
{{ subsub.title }}
</a>
</div>
</template>
<template #opener>
<button class="button accent">
<span>{{ title }}</span>
<Icon icon="chevron-down" />
</button>
</template>
</Dropdown>
<template #actions>
<SlideIn>
<MarkdownView :options="{ baseUrl: base }" :renderer="renderer()">{{ toc }}</MarkdownView>
<template #opener>
<button class="button blue">Menu</button>
</template>
<template v-else>
<a class="button accent" :href="getLink(main[0].href)">{{ main[0].title }}</a>
</template>
</template>
</SlideIn>
</template>
<div id="doc-content">
<MarkdownView :options="{ baseUrl: path }" :renderer="renderer()" ref="mdcontent">{{ content }}</MarkdownView>
</div>
<div id="doc-content" v-html="parseContent(content, true)"></div>
</Card>
<Modal ref="imageModal">
<img :src="selectedImageSource" class="w-full">
<Modal ref="imageModal" :title="selectedImageTitle" size="full">
<div class="w-full inline-flex self-center">
<img :src="selectedImageSource" class="max-w-full">
</div>
</Modal>
</template>

Expand All @@ -50,120 +36,47 @@ export default {
props: {
title: String,
content: String,
toc: Object,
toc: String,
base: String,
path: String,
current: String,
},
methods: {
parseContent(input, relative) {
let content = this.parseLinks(
this.parseImages(
this.parseLists(
this.parseInfoBlocks(
this.parseCode(
this.parseHeadings(input)
)
)
)
)
, relative);
return content;
},
parseLinks(input, relative) {
return input.replace(/href=\"(.+)\"/g, (block, url) => {
if (url.startsWith('#')) {
return `href="${url}"`;
} else if (!url.startsWith('https://') && !url.startsWith('http://')) {
if (!relative) {
url = url.replaceAll('./', '');
return `href="${route('voyager.voyager-docs')}?path=${url}"`;
}
let current = this.current.substring(0, this.current.lastIndexOf('/'));
url = this.resolveRelativePath(current+'/'+url);
url = url.replaceAll('./', '');
return `href="${route('voyager.voyager-docs')}?path=${url}"`;
}
return `href="${url}" target="_blank"`;
});
},
parseInfoBlocks(input) {
return input.replace(/{% hint style=&quot;([^&]+)&quot; %}([^{]+){% endhint %}/gmi, (block, type, content) => {
let color = 'red';
if (type == 'info') {
color = 'blue';
} else if (type == 'warning') {
color = 'yellow';
}
return `<div class="rounded-md p-2 border border-${color}-500 text-sm leading-5 text-${color}-500 my-2">${content}</div>`;
});
},
parseImages(input) {
return input.replace(/src=\"(.+)\"/g, (block, url) => {
if (!url.startsWith('https://') && !url.startsWith('http://')) {
url = url.replaceAll('../', '').replace('.gitbook/assets/', '');
return `src="${route('voyager.voyager-docs-asset')}?path=${url}" class="mx-auto py-2 w-full lg:w-4/6 cursor-pointer"`;
renderer() {
let base = this.base;
return {
heading(text, level) {
return `<h${level} class="my-2" id="${slugify(text, { lower: true })}">${text}</h${level}>`;
},
image(href, title, text) {
return `<img src="${base}${href.replace('../', '')}" alt="${title || text}" class="mx-auto py-2 max-w-full lg:max-w-4/6 cursor-pointer">`;
}
return `src="${url}"`;
});
},
parseLists(input) {
return input.replaceAll('<ul>', '<ul class="pl-4">');
},
parseHeadings(input) {
return input.replace(/<h([1-6])>([^<]+)/g, (block, heading, content) => {
let slug = slugify(content, { lower: true });
return `<h${parseInt(heading)+2} class="mt-6 mb-2"><a href="#${slug}" id="${slug}">${content}</a>`;
});
},
parseCode(input) {
return input.replace(/<code class=\"language-(.*?)\"\>(.*?)<\/code>/gmis, (block, language, content) => {
return `<code style="padding: 0 !important">${hljs.highlight(this.decodeHtml(content), {language: language}).value}</code>`;
});
},
decodeHtml(html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
},
resolveRelativePath(path) {
var parts = path.split('/');
var i = 1;
while (i < parts.length) {
if (parts[i] === '..' && i > 0 && parts[i-1] !== '..') {
parts.splice(i-1, 2);
i -= 2;
}
i++;
}
return parts.join('/');
},
getLink(link) {
return `${this.route('voyager.voyager-docs')}?path=${link}`;
}
},
data() {
return {
selectedImageSource: null,
selectedImageTitle: null,
}
},
mounted() {
document.getElementById('doc-content').getElementsByTagName('img').forEach((image) => {
if (image.src.startsWith(`${route('voyager.voyager-docs-asset')}?path=`)) {
if (image.src.startsWith(this.base)) {
image.addEventListener('click', () => {
this.selectedImageSource = image.src;
this.selectedImageTitle = image.alt;
this.$refs.imageModal.open();
});
}
});
},
created() {
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
});
});
}
}
</script>
5 changes: 3 additions & 2 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ export default defineConfig({
fileName: 'voyager-docs'
},
rollupOptions: {
external: ['vue'],
external: ['vue', 'marked'],
output: {
globals: {
vue: 'Vue'
vue: 'Vue',
marked: 'marked',
}
}
}
Expand Down

0 comments on commit 864dc69

Please sign in to comment.