nitinol/mithril.scm
2026-07-12 16:29:41 +08:00

89 lines
2.5 KiB
Scheme

(define-macro (mithril:init)
"(mithril:init)
Loads Mithril into the document head and sets the
pragma->sxml function."
`(begin (get-resource "https://cdnjs.cloudflare.com/ajax/libs/mithril/2.2.2/mithril.min.js")
(pragma->sxml m)))
(mithril:init)
(define-macro (mithril:comp view . args)
"(mithril:comp view . args)
Creates a Mithril component object, where VIEW is
a function body used to display the component, and
ARGS is a list of keyword-value pairs that specify
additional values on the component object.
For example, the following code creates a stateful
heading component:
```
(define Heading
(component
(sxml
(h1 ~vnode.state.title))
:title \"Title Text\"))
```"
(when (and (not (null? args))
(= (remainder (length args) 2) 1))
(error (format
"mithril:comp-> Component macro expects even length args list.
Call: (mithril:comp ~s ~s)"
view args)))
`(object
:view
(lambda (vnode) (,@view))
,@args))
(define (mithril:repl prompt)
"(mithril:repl prompt preamble)
Returns a REPL Mithril component that allows evaluating
custom LIPS code. The environment used by this REPL is
the same as the application execution environment, meaning
that the DOM and running scripts can be modified.
PROMPT specifies the user prompt."
(let* ((add-line (lambda (state text prompt?)
(map
(lambda (l)
(set! state.lines (cons `&(:text ,l :prompt ,prompt?) state.lines)))
(string-split "\n" text))))
(evaluate (lambda (state cmd)
(add-line state (format "~a~a" prompt cmd) #t)
(let ((res (eval (with-input-from-string cmd read))))
(when res
(add-line state res #f)))))
(handle-key-down (lambda (state e)
(cond
((equal? e.key "Enter")
(e.preventDefault)
(let ((cmd state.input))
(set! state.input "")
(evaluate state cmd)))))))
(mithril:comp
(let ((state vnode.state))
(sxml
(div.repl
(div.repl-output
~(list->array
(map
(lambda (line)
(m ".line" line.text))
(reverse state.lines))))
(div.repl-input
(span.prompt ~prompt)
(input (@ (type "text")
(value state.input)
(oninput (lambda (e) (set! state.input e.target.value)))
(onkeydown (lambda (e) (handle-key-down state e)))
(oncreate (lambda (vnode)
(set! state.inputRef vnode.dome)
(vnode.dom.focus)))
(onfocus (lambda () (object)))
(autofocus #t)))))))
:oninit (lambda (vnode)
(set! vnode.state.lines '())
(set! vnode.state.input "")))))