Skip to content

Commit c362fda

Browse files
authored
Add VitePress documentation site with GitHub Pages deployment (#93)
- Migrate docs to VitePress with organized structure (guide/, reference/, cookbook/, extensions/) - Add interactive playground calling live sandbox API - Add GitHub Actions workflow for automatic deployment - Update README with links to new documentation site - Add custom theme with purple branding - Include CLI reference and Safe Mode documentation
1 parent f8f1a4c commit c362fda

36 files changed

Lines changed: 3388 additions & 1278 deletions

.github/workflows/deploy-docs.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Deploy Documentation
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- 'docs/**'
9+
- '.github/workflows/deploy-docs.yml'
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
pages: write
15+
id-token: write
16+
17+
concurrency:
18+
group: pages
19+
cancel-in-progress: false
20+
21+
jobs:
22+
build:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- name: Checkout
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 0
29+
30+
- name: Setup Node
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: 20
34+
cache: npm
35+
cache-dependency-path: docs/package-lock.json
36+
37+
- name: Setup Pages
38+
uses: actions/configure-pages@v5
39+
40+
- name: Install dependencies
41+
run: npm ci
42+
working-directory: docs
43+
44+
- name: Build with VitePress
45+
run: npm run docs:build
46+
working-directory: docs
47+
48+
- name: Upload artifact
49+
uses: actions/upload-pages-artifact@v3
50+
with:
51+
path: docs/.vitepress/dist
52+
53+
deploy:
54+
environment:
55+
name: github-pages
56+
url: ${{ steps.deployment.outputs.page_url }}
57+
needs: build
58+
runs-on: ubuntu-latest
59+
steps:
60+
- name: Deploy to GitHub Pages
61+
id: deployment
62+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@
1616
# Fuzz testing artifacts
1717
/fuzz/crash-*
1818
/fuzz/minimized-*
19+
20+
# Documentation
21+
/docs/node_modules/
22+
/docs/.vitepress/cache/
23+
/docs/.vitepress/dist/

README.md

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,21 @@ Output:
9393
</code></pre>
9494
```
9595

96-
## Demo: Sandbox with live preview
97-
https://sandbox.dereuromark.de/sandbox/djot
98-
9996
## Documentation
10097

101-
- [Why Djot?](docs/why-djot.md) - Comparison with Markdown, migration guide
102-
- [Examples](docs/README.md) - Comprehensive usage examples
103-
- [Syntax Reference](docs/syntax.md) - Complete Djot syntax guide
104-
- [API Reference](docs/api.md) - Classes and methods
105-
- [Extensions](docs/extensions.md) - Built-in extensions for common features
106-
- [Profiles](docs/profiles.md) - Feature restriction for different contexts
107-
- [Converters](docs/converters.md) - Markdown/BBCode to Djot conversion
108-
- [Cookbook](docs/cookbook.md) - Common customizations and recipes
109-
- [Architecture](docs/architecture.md) - Internal design
110-
- [Enhancements](docs/enhancements.md) - Fixes beyond the current spec
111-
- [Performance](docs/performance.md) - Benchmarks and performance data
98+
Full documentation is available at **https://php-collective.github.io/djot-php/**
99+
100+
- [Getting Started](https://php-collective.github.io/djot-php/guide/) - Installation and quick start
101+
- [Why Djot?](https://php-collective.github.io/djot-php/guide/why-djot) - Comparison with Markdown
102+
- [Syntax Reference](https://php-collective.github.io/djot-php/guide/syntax) - Complete Djot syntax guide
103+
- [Extensions](https://php-collective.github.io/djot-php/extensions/) - Built-in extensions
104+
- [API Reference](https://php-collective.github.io/djot-php/reference/api) - Classes and methods
105+
- [Cookbook](https://php-collective.github.io/djot-php/cookbook/) - Customization recipes
106+
107+
## Demo
108+
109+
- [Interactive Playground](https://php-collective.github.io/djot-php/playground) - Try djot-php in your browser
110+
- [Sandbox](https://sandbox.dereuromark.de/sandbox/djot) - Full-featured sandbox with all options
112111

113112
## Security
114113

@@ -121,7 +120,7 @@ $html = $converter->convert($untrustedInput);
121120

122121
Safe mode automatically blocks dangerous URL schemes (`javascript:`, etc.), strips event handler attributes (`onclick`, etc.), and escapes raw HTML.
123122

124-
See [Security Considerations](docs/README.md#security-considerations) for details and advanced configuration.
123+
See [Safe Mode](https://php-collective.github.io/djot-php/guide/safe-mode) for details and advanced configuration.
125124

126125

127126
## Implementations
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<script setup lang="ts">
2+
import { ref, watch, onMounted } from 'vue'
3+
4+
const SANDBOX_URL = 'https://sandbox.dereuromark.de/sandbox/djot'
5+
6+
const djotInput = ref(`# Welcome to Djot
7+
8+
This is a _live playground_ for *djot-php*.
9+
10+
Try editing this text to see the output change!
11+
12+
## Features
13+
14+
- Emphasis with _underscores_
15+
- Strong with *asterisks*
16+
- Links like [djot.net](https://djot.net)
17+
18+
## Code Example
19+
20+
\`\`\` php
21+
$converter = new DjotConverter();
22+
echo $converter->convert($djot);
23+
\`\`\`
24+
25+
> Djot is a lightweight markup language
26+
> with clean, unambiguous syntax.
27+
28+
| Feature | Djot | Markdown |
29+
|---------|------|----------|
30+
| Emphasis | _text_ | *text* |
31+
| Strong | *text* | **text** |
32+
`)
33+
34+
const htmlOutput = ref('')
35+
const activeTab = ref<'preview' | 'source'>('preview')
36+
const profile = ref('')
37+
const isLoading = ref(false)
38+
const error = ref('')
39+
const extensions = ref<string[]>([])
40+
41+
const availableExtensions = [
42+
{ id: 'autolink', label: 'Autolink' },
43+
{ id: 'external_links', label: 'External Links' },
44+
{ id: 'heading_permalinks', label: 'Heading Permalinks' },
45+
{ id: 'mentions', label: 'Mentions' },
46+
{ id: 'smart_quotes', label: 'Smart Quotes' },
47+
{ id: 'semantic_span', label: 'Semantic Span' },
48+
{ id: 'toc', label: 'Table of Contents' },
49+
{ id: 'default_attributes', label: 'Default Attributes' },
50+
]
51+
52+
const profiles = [
53+
{ id: '', label: 'No filter' },
54+
{ id: 'full', label: 'Full' },
55+
{ id: 'article', label: 'Article' },
56+
{ id: 'comment', label: 'Comment' },
57+
{ id: 'minimal', label: 'Minimal' },
58+
]
59+
60+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
61+
let abortController: AbortController | null = null
62+
63+
async function convert() {
64+
if (abortController) {
65+
abortController.abort()
66+
}
67+
abortController = new AbortController()
68+
69+
isLoading.value = true
70+
error.value = ''
71+
72+
try {
73+
const endpoint = extensions.value.length > 0
74+
? `${SANDBOX_URL}/convert-with-extensions`
75+
: `${SANDBOX_URL}/convert`
76+
77+
const body = new URLSearchParams()
78+
body.append('djot', djotInput.value)
79+
80+
if (extensions.value.length > 0) {
81+
extensions.value.forEach(ext => body.append('extensions[]', ext))
82+
} else {
83+
if (profile.value) {
84+
body.append('profile', profile.value)
85+
}
86+
}
87+
88+
const response = await fetch(endpoint, {
89+
method: 'POST',
90+
headers: {
91+
'Content-Type': 'application/x-www-form-urlencoded',
92+
'X-Requested-With': 'XMLHttpRequest',
93+
},
94+
body: body.toString(),
95+
signal: abortController.signal,
96+
})
97+
98+
if (!response.ok) {
99+
throw new Error(`HTTP error: ${response.status}`)
100+
}
101+
102+
const data = await response.json()
103+
104+
if (data.error) {
105+
error.value = data.error
106+
htmlOutput.value = ''
107+
} else {
108+
htmlOutput.value = data.html || ''
109+
error.value = ''
110+
}
111+
} catch (err) {
112+
if (err instanceof Error && err.name === 'AbortError') {
113+
return
114+
}
115+
error.value = err instanceof Error ? err.message : 'Unknown error'
116+
htmlOutput.value = ''
117+
} finally {
118+
isLoading.value = false
119+
}
120+
}
121+
122+
function debouncedConvert() {
123+
if (debounceTimer) {
124+
clearTimeout(debounceTimer)
125+
}
126+
debounceTimer = setTimeout(convert, 300)
127+
}
128+
129+
watch(djotInput, debouncedConvert)
130+
watch(profile, convert)
131+
watch(extensions, convert, { deep: true })
132+
133+
onMounted(() => {
134+
convert()
135+
})
136+
137+
function toggleExtension(extId: string) {
138+
const index = extensions.value.indexOf(extId)
139+
if (index === -1) {
140+
extensions.value.push(extId)
141+
} else {
142+
extensions.value.splice(index, 1)
143+
}
144+
}
145+
</script>
146+
147+
<template>
148+
<div class="djot-playground">
149+
<div class="options">
150+
<label>
151+
Profile:
152+
<select v-model="profile" :disabled="extensions.length > 0">
153+
<option v-for="p in profiles" :key="p.id" :value="p.id">{{ p.label }}</option>
154+
</select>
155+
</label>
156+
<div class="extension-toggles">
157+
<label v-for="ext in availableExtensions" :key="ext.id">
158+
<input
159+
type="checkbox"
160+
:checked="extensions.includes(ext.id)"
161+
@change="toggleExtension(ext.id)"
162+
/>
163+
{{ ext.label }}
164+
</label>
165+
</div>
166+
</div>
167+
168+
<div class="input-section">
169+
<h3>Djot Input</h3>
170+
<textarea
171+
v-model="djotInput"
172+
placeholder="Enter Djot markup here..."
173+
spellcheck="false"
174+
></textarea>
175+
</div>
176+
177+
<div class="output-section">
178+
<h3>Output</h3>
179+
<div class="output-tabs">
180+
<button
181+
:class="{ active: activeTab === 'preview' }"
182+
@click="activeTab = 'preview'"
183+
>
184+
Preview
185+
</button>
186+
<button
187+
:class="{ active: activeTab === 'source' }"
188+
@click="activeTab = 'source'"
189+
>
190+
HTML Source
191+
</button>
192+
</div>
193+
194+
<div v-if="isLoading" class="output-content loading">
195+
Converting...
196+
</div>
197+
<div v-else-if="error" class="output-content error">
198+
{{ error }}
199+
</div>
200+
<div
201+
v-else-if="activeTab === 'preview'"
202+
class="output-content preview"
203+
v-html="htmlOutput"
204+
></div>
205+
<div
206+
v-else
207+
class="output-content source"
208+
>{{ htmlOutput }}</div>
209+
</div>
210+
</div>
211+
</template>

0 commit comments

Comments
 (0)