Skip to content

Commit c5b13e9

Browse files
committed
Add syntax highlighting and improve preview
- Add highlight.js for code block syntax highlighting - Sync highlight.js theme with IDE dark/light mode - Update djot.js to v0.3.2 (fixes task list rendering) - Improve task list checkbox styling - Fix definition list syntax in examples - Improve HTML export: try npx djot, show warning on fallback
1 parent a249af1 commit c5b13e9

File tree

4 files changed

+78
-20
lines changed

4 files changed

+78
-20
lines changed

examples/complete-document.djot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,4 @@ reliable parsing and rendering.
141141

142142
---
143143

144-
_Last updated: 2024_
144+
_Last updated: 2025_

examples/lists.djot

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,20 @@ Mixed nesting:
5757

5858
## Definition Lists
5959

60-
Term 1
61-
: Definition for term 1
60+
: Term 1
6261

63-
Term 2
64-
: Definition for term 2
65-
: Another definition for term 2
62+
Definition for term 1
6663

67-
Complex term
68-
: This definition can span
64+
: Term 2
65+
: Term 2b
66+
67+
Definition for term 2
68+
69+
Another paragraph for term 2
70+
71+
: Complex term
72+
73+
This definition can span
6974
multiple lines if needed.
7075

7176
It can even contain multiple paragraphs.

src/main/kotlin/org/phpcollective/djot/actions/ExportHtmlAction.kt

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class ExportHtmlAction : AnAction() {
5858
}
5959

6060
private fun convertToHtml(project: Project, djot: String): String {
61-
return try {
61+
// Try djot-php first
62+
try {
6263
val phpCode = "require_once 'vendor/autoload.php'; " +
6364
"\$c = new \\Djot\\DjotConverter(); " +
6465
"echo \$c->convert(file_get_contents('php://stdin'));"
@@ -73,23 +74,53 @@ class ExportHtmlAction : AnAction() {
7374
val result = process.inputStream.bufferedReader().readText()
7475
val exitCode = process.waitFor()
7576

76-
if (exitCode == 0) result else fallbackConvert(djot)
77-
} catch (e: Exception) {
78-
fallbackConvert(djot)
79-
}
77+
if (exitCode == 0 && result.isNotBlank()) return result
78+
} catch (_: Exception) {}
79+
80+
// Try npx djot (Node.js)
81+
try {
82+
val process = ProcessBuilder("npx", "djot")
83+
.directory(project.basePath?.let { File(it) })
84+
.redirectErrorStream(false)
85+
.start()
86+
87+
process.outputStream.bufferedWriter().use { it.write(djot) }
88+
process.outputStream.close()
89+
90+
val result = process.inputStream.bufferedReader().readText()
91+
val exitCode = process.waitFor()
92+
93+
if (exitCode == 0 && result.isNotBlank()) return result
94+
} catch (_: Exception) {}
95+
96+
// Fallback: show warning comment in output
97+
return "<!-- WARNING: Export requires djot-php or Node.js djot package for proper rendering.\n" +
98+
" Install: composer require php-collective/djot-php\n" +
99+
" Or: npm install -g @djot/djot -->\n\n" +
100+
fallbackConvert(djot)
80101
}
81102

82103
private fun fallbackConvert(djot: String): String {
83-
// Very basic fallback
104+
// Basic fallback - headings, emphasis, code only
84105
return djot
85-
.replace(Regex("^# (.+)$", RegexOption.MULTILINE), "<h1>$1</h1>")
86-
.replace(Regex("^## (.+)$", RegexOption.MULTILINE), "<h2>$1</h2>")
106+
.replace(Regex("^###### (.+)$", RegexOption.MULTILINE), "<h6>$1</h6>")
107+
.replace(Regex("^##### (.+)$", RegexOption.MULTILINE), "<h5>$1</h5>")
108+
.replace(Regex("^#### (.+)$", RegexOption.MULTILINE), "<h4>$1</h4>")
87109
.replace(Regex("^### (.+)$", RegexOption.MULTILINE), "<h3>$1</h3>")
110+
.replace(Regex("^## (.+)$", RegexOption.MULTILINE), "<h2>$1</h2>")
111+
.replace(Regex("^# (.+)$", RegexOption.MULTILINE), "<h1>$1</h1>")
88112
.replace(Regex("\\*([^*]+)\\*"), "<strong>$1</strong>")
89113
.replace(Regex("_([^_]+)_"), "<em>$1</em>")
90114
.replace(Regex("`([^`]+)`"), "<code>$1</code>")
115+
.replace(Regex("\\{=([^=]+)=\\}"), "<mark>$1</mark>")
116+
.replace(Regex("^---+$", RegexOption.MULTILINE), "<hr>")
117+
.replace(Regex("^\\*\\*\\*+$", RegexOption.MULTILINE), "<hr>")
91118
.replace("\n\n", "</p>\n<p>")
92119
.let { "<p>$it</p>" }
120+
.replace(Regex("<p>(<h[1-6]>)"), "$1")
121+
.replace(Regex("(</h[1-6]>)</p>"), "$1")
122+
.replace(Regex("<p>(<hr>)</p>"), "$1")
123+
.replace("<p></p>", "")
93124
}
94125

95126
private fun wrapFullHtml(title: String, content: String): String {

src/main/kotlin/org/phpcollective/djot/preview/DjotPreviewPanel.kt

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ class DjotPreviewPanel(
8181
val isDark = isDarkTheme()
8282
ApplicationManager.getApplication().invokeLater {
8383
browser.cefBrowser.executeJavaScript(
84-
"document.body.classList.toggle('dark', $isDark); document.body.classList.toggle('light', ${!isDark});",
84+
"""
85+
document.body.classList.toggle('dark', $isDark);
86+
document.body.classList.toggle('light', ${!isDark});
87+
document.getElementById('hljs-light').disabled = $isDark;
88+
document.getElementById('hljs-dark').disabled = ${!isDark};
89+
""".trimIndent(),
8590
browser.cefBrowser.url,
8691
0
8792
)
@@ -205,8 +210,9 @@ class DjotPreviewPanel(
205210
hr { border: none; border-top: 1px solid #ddd; margin: 2em 0; }
206211
ul, ol { padding-left: 2em; }
207212
li { margin: 0.25em 0; }
208-
.task-list-item { list-style: none; margin-left: -1.5em; }
209-
.task-list-item input { margin-right: 0.5em; }
213+
li:has(> input[type="checkbox"]) { list-style: none; margin-left: -1.5em; }
214+
li > input[type="checkbox"] { margin-right: 0.5em; width: 1em; height: 1em; vertical-align: middle; }
215+
li > input[type="checkbox"]:checked { accent-color: #3498db; }
210216
sup, sub { font-size: 0.75em; }
211217
dt { font-weight: bold; margin-top: 1em; }
212218
dd { margin-left: 2em; }
@@ -219,7 +225,14 @@ class DjotPreviewPanel(
219225
body.dark #error { background: #5a2d2d; color: #f8d7da; }
220226
body.dark #loading { color: #aaa; }
221227
</style>
222-
<script src="https://cdn.jsdelivr.net/npm/@djot/[email protected]/dist/djot.min.js"></script>
228+
<script src="https://cdn.jsdelivr.net/npm/@djot/[email protected]/dist/djot.min.js"></script>
229+
<link id="hljs-light" rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/github.min.css"${if (isDark) " disabled" else ""}>
230+
<link id="hljs-dark" rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/github-dark.min.css"${if (!isDark) " disabled" else ""}>
231+
<style>
232+
body.light pre code.hljs { background: #f6f8fa; }
233+
body.dark pre code.hljs { background: #161b22; }
234+
</style>
235+
<script src="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
223236
</head>
224237
<body class="$themeClass">
225238
<div id="error"></div>
@@ -241,6 +254,7 @@ class DjotPreviewPanel(
241254
const doc = djot.parse(djotSource);
242255
const html = djot.renderHTML(doc);
243256
contentEl.innerHTML = html;
257+
highlightCode();
244258
} catch (e) {
245259
errorEl.textContent = 'Render error: ' + e.message;
246260
errorEl.style.display = 'block';
@@ -249,6 +263,14 @@ class DjotPreviewPanel(
249263
}
250264
}
251265
266+
function highlightCode() {
267+
if (typeof hljs !== 'undefined') {
268+
document.querySelectorAll('pre code').forEach((block) => {
269+
hljs.highlightElement(block);
270+
});
271+
}
272+
}
273+
252274
function fallbackRender(source) {
253275
// Very basic fallback renderer
254276
return source

0 commit comments

Comments
 (0)