Lyng Web utilities (:lyngweb)
Reusable JS/Compose for Web utilities and UI pieces for Lyng-powered sites. The module is self-sufficient: adding it as a dependency is enough — no external CSS classes are required for its editor overlay to render correctly.
What’s inside
EditorWithOverlay— a pure code editor Composable with a syntax-highlight overlay. It keeps a native<textarea>for input/caret while rendering highlighted HTML on top, staying in perfect glyph alignment. No built-in buttons or actions.- HTML utilities for Markdown pipelines:
ensureBootstrapCodeBlocks(html: String): String— addsclass="code"to<pre>blocks (for Bootstrap-like styling).highlightLyngHtml(html: String): String— transforms Lyng code blocks inside HTML into highlighted spans usinghl-*classes.htmlEscape(s: String): String— HTML-escape utility used by the highlighter.
- Backward-compatible
net.sergeych.site.SiteHighlight.renderHtml(text)that renders a highlighted string with the samehl-*classes used by the site.
All essential styles for the editor are injected inline; there is no dependency on external CSS class names (e.g., editor-overlay). You can still override visuals with your own CSS if desired.
Quick start
- Add the dependency in your JS
build.gradle.kts:
dependencies {
implementation(project(":lyngweb"))
}
- Use the editor in a Compose HTML page:
@Composable
fun TryLyngSnippet() {
var code by remember { mutableStateOf("""
// Type Lyng code here
import lyng.stdlib
[1,2,3].map { it * 10 }
""".trimIndent()) }
fun runCode() { /* evaluate code in your Scope */ }
Div({ classes("mb-3") }) {
Div({ classes("form-label") }) { Text("Code") }
EditorWithOverlay(
code = code,
setCode = { code = it },
// Optionally handle keyboard shortcuts (e.g., Ctrl/Cmd+Enter to run):
onKeyDown = { ev ->
val ctrlOrMeta = ev.ctrlKey || ev.metaKey
if (ctrlOrMeta && ev.key.lowercase() == "enter") {
ev.preventDefault()
runCode()
}
}
)
}
// Your own action buttons
// ...your own action buttons...
}
The editor provides:
- Tab insertion with configurable
tabSize(default 4) - Smart newline indentation (copies the leading spaces of the current line)
- Scroll sync and 1:1 glyph alignment between overlay and textarea
- Inline styles for overlay/textarea; no external CSS required
Highlight Lyng code inside Markdown HTML
If you render Markdown to HTML first (e.g., with marked), you can post-process it with lyngweb to highlight Lyng code blocks:
fun renderMarkdownLyng(mdHtml: String): String {
// 1) ensure <pre> blocks have class="code"
val withPre = ensureBootstrapCodeBlocks(mdHtml)
// 2) highlight <code class="language-lyng"> blocks
return highlightLyngHtml(withPre)
}
Lyng tokens are wrapped into spans with classes like hl-kw, hl-id, hl-num, hl-cmt, etc. You can style them as you wish, for example:
.hl-kw { color: #6f42c1; font-weight: 600; }
.hl-id { color: #1f2328; }
.hl-num { color: #0a3069; }
.hl-str { color: #015b2f; }
.hl-cmt { color: #6a737d; font-style: italic; }
API Summary
-
@Composable fun EditorWithOverlay(code: String, setCode: (String) -> Unit, tabSize: Int = 4, onKeyDown: ((SyntheticKeyboardEvent) -> Unit)? = null)- Pure editor, no actions. Wire your own buttons and shortcuts.
- Self-contained styling, adjustable with your own CSS if desired.
-
fun ensureBootstrapCodeBlocks(html: String): String- Adds
class="code"to<pre>tags if not present.
- Adds
-
fun highlightLyngHtml(html: String): String- Highlights Lyng
<code class="language-lyng">...</code>blocks inside the provided HTML.
- Highlights Lyng
-
fun htmlEscape(s: String): String- Escapes special HTML characters.
-
object net.sergeych.site.SiteHighlightfun renderHtml(text: String): String— renders highlighted spans withhl-*classes; kept for compatibility with existing site code/tests.
Notes
- The editor does not ship default color styles for
hl-*classes. Provide your own CSS to match your theme. - If you want a minimal look without Bootstrap, the editor still works out of the box due to inline styles.