Skip to content

Commit 63abd62

Browse files
Feature: Markdown Viewer and Copy-to-Clipboard for Docs (#202)
* Add route to serve raw markdown documentation Introduces a new controller method and route to serve raw markdown files for documentation. This allows direct access to the markdown source for a given platform, version, and page. * Add 'Copy as Markdown' button to docs pages Introduces a new Alpine.js component and Blade view for copying the current documentation page as Markdown. The button is added to the table of contents section and includes visual feedback for successful copying. Also adds a copy icon component and integrates the new Alpine data into the app. * Fix regex to match markdown URLs correctly
1 parent 3ac74a8 commit 63abd62

File tree

8 files changed

+87
-2
lines changed

8 files changed

+87
-2
lines changed

app/Http/Controllers/ShowDocumentationController.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,24 @@ public function __invoke(Request $request, string $platform, string $version, ?s
5252
return view('docs.index')->with($pageProperties);
5353
}
5454

55+
public function serveRawMarkdown(Request $request, string $platform, string $version, string $page)
56+
{
57+
abort_unless(is_dir(resource_path('views/docs/'.$platform.'/'.$version)), 404);
58+
59+
$filePath = resource_path("views/docs/{$platform}/{$version}/{$page}.md");
60+
61+
if (! file_exists($filePath)) {
62+
abort(404);
63+
}
64+
65+
$content = file_get_contents($filePath);
66+
67+
return response($content, 200, [
68+
'Content-Type' => 'text/plain; charset=utf-8',
69+
'Content-Disposition' => 'inline; filename="'.basename($filePath).'"',
70+
]);
71+
}
72+
5573
protected function getPageProperties($platform, $version, $page = null): array
5674
{
5775
$markdownFileName = $platform.'.'.$version.'.'.($page ?? 'index');
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export default () => ({
2+
showMessage: false,
3+
4+
async copyMarkdownToClipboard() {
5+
try {
6+
// Get the current page URL and convert it to .md URL
7+
const currentUrl = window.location.href
8+
const mdUrl = currentUrl.replace(
9+
/\/docs\/([^\/]+\/[^\/]+\/.*)$/,
10+
'/docs/$1.md',
11+
)
12+
13+
// Fetch the raw markdown content
14+
const response = await fetch(mdUrl)
15+
if (!response.ok) {
16+
throw new Error('Failed to fetch markdown content')
17+
}
18+
19+
const markdownContent = await response.text()
20+
21+
// Copy to clipboard
22+
await navigator.clipboard.writeText(markdownContent)
23+
24+
// Show success message
25+
this.showMessage = true
26+
setTimeout(() => {
27+
this.showMessage = false
28+
}, 2000)
29+
} catch (error) {
30+
console.error('Failed to copy markdown:', error)
31+
// Could show an error message here if needed
32+
}
33+
},
34+
})

resources/js/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Alpine,
88
} from '../../vendor/livewire/livewire/dist/livewire.esm'
99
import codeBlock from './alpine/codeBlock.js'
10+
import copyMarkdown from './alpine/copyMarkdown.js'
1011
import docsearch from '@docsearch/js'
1112
import Atropos from 'atropos'
1213
import '@docsearch/css'
@@ -60,6 +61,7 @@ window.gsap = gsap
6061

6162
// Alpine
6263
Alpine.data('codeBlock', codeBlock)
64+
Alpine.data('copyMarkdown', copyMarkdown)
6365
Alpine.magic('refAll', (el) => {
6466
return (refName) => {
6567
return Array.from(el.querySelectorAll(`[x-ref="${refName}"]`))
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{{-- Copy as Markdown Button --}}
2+
<div {{ $attributes->merge(['class' => 'mb-4']) }} x-data="copyMarkdown()" x-init="$watch('$el', () => {})">
3+
<button
4+
@click="copyMarkdownToClipboard()"
5+
class="flex items-center gap-1.5 text-sm opacity-60 hover:opacity-100 transition-opacity duration-200"
6+
title="Copy page as Markdown"
7+
>
8+
{{-- Icon --}}
9+
<x-icons.copy x-show="!showMessage" class="size-[18px]" />
10+
<x-icons.checkmark x-show="showMessage" x-cloak class="size-[18px]" />
11+
12+
{{-- Label --}}
13+
<div x-show="!showMessage">Copy as Markdown</div>
14+
<div x-show="showMessage" x-cloak>Copied!</div>
15+
</button>
16+
</div>

resources/views/components/docs/toc-and-sponsors.blade.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
{{-- Copy as Markdown Button --}}
2+
<x-docs.copy-markdown-button />
3+
14
{{-- On this page --}}
25
<h3 class="flex items-center gap-1.5 text-sm opacity-60">
36
{{-- Icon --}}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<svg {{ $attributes->merge(['fill' => 'none', 'stroke' => 'currentColor', 'viewBox' => '0 0 24 24']) }}>
2+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
3+
</svg>

resources/views/docs/index.blade.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
<x-docs.separator class="mt-4" />
1515

1616
{{-- Table of contents --}}
17-
<div class="xl:hidden">
18-
<h3 class="inline-flex items-center gap-1.5 pt-5 text-sm opacity-50">
17+
<div class="xl:hidden pt-5">
18+
{{-- Copy as Markdown Button --}}
19+
<x-docs.copy-markdown-button class="mt-4" />
20+
21+
<h3 class="inline-flex items-center gap-1.5 text-sm opacity-50">
1922
{{-- Icon --}}
2023
<x-icons.stacked-lines class="size-[18px]" />
2124
{{-- Label --}}

routes/web.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
Route::get('blog', [ShowBlogController::class, 'index'])->name('blog');
3939
Route::get('blog/{article}', [ShowBlogController::class, 'show'])->name('article');
4040

41+
Route::get('/docs/{platform}/{version}/{page}.md', [ShowDocumentationController::class, 'serveRawMarkdown'])
42+
->where('page', '(.*)')
43+
->where('platform', '[a-z]+')
44+
->where('version', '[0-9]+')
45+
->name('docs.raw');
46+
4147
Route::get('/docs/{platform}/{version}/{page?}', ShowDocumentationController::class)
4248
->where('page', '(.*)')
4349
->where('platform', '[a-z]+')

0 commit comments

Comments
 (0)