2024-05-25 12:48:35 +00:00
|
|
|
(defpackage #:hsx/element
|
2024-05-25 03:00:39 +00:00
|
|
|
(:use #:cl)
|
2024-05-31 09:39:25 +00:00
|
|
|
(:import-from #:hsx/escaper
|
|
|
|
#:escape-html-attribute
|
|
|
|
#:escape-html-text-content)
|
2024-06-01 11:14:17 +00:00
|
|
|
(:import-from #:hsx/group
|
2024-06-01 12:40:45 +00:00
|
|
|
#:self-closing-tag-p
|
|
|
|
#:non-escaping-tag-p)
|
2024-05-30 03:55:13 +00:00
|
|
|
(:export #:element
|
|
|
|
#:tag
|
|
|
|
#:html-tag
|
2024-06-09 12:03:21 +00:00
|
|
|
#:self-closing-tag
|
|
|
|
#:non-escaping-tag
|
2024-05-30 03:55:13 +00:00
|
|
|
#:fragment
|
|
|
|
#:component
|
|
|
|
#:create-element
|
2024-05-28 11:15:29 +00:00
|
|
|
#:element-type
|
2024-05-25 03:00:39 +00:00
|
|
|
#:element-props
|
2024-05-25 15:57:06 +00:00
|
|
|
#:element-children
|
2024-05-29 01:30:06 +00:00
|
|
|
#:expand-component
|
2024-06-01 13:49:15 +00:00
|
|
|
#:render-to-string))
|
2024-05-25 12:48:35 +00:00
|
|
|
(in-package #:hsx/element)
|
2024-05-25 03:00:39 +00:00
|
|
|
|
2024-05-26 14:29:30 +00:00
|
|
|
;;;; class definitions
|
|
|
|
|
2024-05-25 03:00:39 +00:00
|
|
|
(defclass element ()
|
2024-05-25 16:29:58 +00:00
|
|
|
((type
|
|
|
|
:reader element-type
|
|
|
|
:initarg :type)
|
2024-05-25 03:00:39 +00:00
|
|
|
(props
|
|
|
|
:reader element-props
|
2024-05-25 15:57:06 +00:00
|
|
|
:initarg :props)
|
|
|
|
(children
|
|
|
|
:reader element-children
|
|
|
|
:initarg :children)))
|
2024-05-25 03:00:39 +00:00
|
|
|
|
2024-05-28 11:48:39 +00:00
|
|
|
(defclass tag (element) ())
|
2024-05-26 14:29:30 +00:00
|
|
|
|
2024-05-28 11:48:39 +00:00
|
|
|
(defclass html-tag (tag) ())
|
2024-05-26 14:29:30 +00:00
|
|
|
|
2024-06-09 12:03:21 +00:00
|
|
|
(defclass self-closing-tag (tag) ())
|
|
|
|
|
|
|
|
(defclass non-escaping-tag (tag) ())
|
|
|
|
|
2024-05-29 03:46:57 +00:00
|
|
|
(defclass fragment (tag) ())
|
2024-05-26 14:29:30 +00:00
|
|
|
|
2024-05-28 11:48:39 +00:00
|
|
|
(defclass component (element) ())
|
2024-05-26 14:29:30 +00:00
|
|
|
|
2024-05-28 10:31:50 +00:00
|
|
|
;;;; factory
|
2024-05-26 14:29:30 +00:00
|
|
|
|
2024-05-30 03:55:13 +00:00
|
|
|
(defun create-element (type props children)
|
2024-06-04 23:22:14 +00:00
|
|
|
(make-instance
|
|
|
|
(cond ((functionp type) 'component)
|
|
|
|
((eq type :<>) 'fragment)
|
|
|
|
((eq type :html) 'html-tag)
|
2024-06-09 12:03:21 +00:00
|
|
|
((self-closing-tag-p type) 'self-closing-tag)
|
|
|
|
((non-escaping-tag-p type) 'non-escaping-tag)
|
2024-06-04 23:22:14 +00:00
|
|
|
((keywordp type) 'tag)
|
|
|
|
(t (error "element-type must be a keyword or a function.")))
|
|
|
|
:type type
|
|
|
|
:props props
|
|
|
|
:children (flatten children)))
|
2024-06-08 05:29:00 +00:00
|
|
|
|
2024-05-25 03:00:39 +00:00
|
|
|
(defun flatten (x)
|
|
|
|
(labels ((rec (x acc)
|
|
|
|
(cond ((null x) acc)
|
|
|
|
((atom x) (cons x acc))
|
|
|
|
(t (rec
|
|
|
|
(car x)
|
|
|
|
(rec (cdr x) acc))))))
|
|
|
|
(rec x nil)))
|
2024-05-26 04:57:51 +00:00
|
|
|
|
2024-05-26 14:29:30 +00:00
|
|
|
;;;; methods
|
|
|
|
|
2024-06-01 13:49:15 +00:00
|
|
|
(defmethod render-to-string ((element element) &key pretty)
|
2024-05-29 03:57:51 +00:00
|
|
|
(with-output-to-string (stream)
|
2024-06-01 13:49:15 +00:00
|
|
|
(write element :stream stream :pretty pretty)))
|
2024-05-29 03:57:51 +00:00
|
|
|
|
2024-05-29 01:30:06 +00:00
|
|
|
(defmethod print-object ((element tag) stream)
|
2024-06-09 12:03:21 +00:00
|
|
|
(with-slots (type props children) element
|
2024-06-07 01:07:37 +00:00
|
|
|
(let ((type-str (string-downcase type))
|
|
|
|
(props-str (render-props props)))
|
2024-05-28 10:31:50 +00:00
|
|
|
(if children
|
2024-05-28 12:42:28 +00:00
|
|
|
(format stream
|
2024-06-07 01:07:37 +00:00
|
|
|
(if (or (rest children)
|
|
|
|
(typep (first children) 'element))
|
|
|
|
"~@<<~a~a>~2I~:@_~<~@{~a~^~:@_~}~:>~0I~:@_</~a>~:>"
|
|
|
|
"~@<<~a~a>~2I~:_~<~a~^~:@_~:>~0I~_</~a>~:>")
|
2024-05-28 10:31:50 +00:00
|
|
|
type-str
|
2024-06-07 01:07:37 +00:00
|
|
|
props-str
|
2024-06-09 12:03:21 +00:00
|
|
|
(render-children element)
|
2024-05-28 10:31:50 +00:00
|
|
|
type-str)
|
2024-06-09 12:03:21 +00:00
|
|
|
(format stream "<~a~a></~a>" type-str props-str type-str)))))
|
|
|
|
|
|
|
|
(defmethod print-object ((element self-closing-tag) stream)
|
|
|
|
(with-slots (type props) element
|
|
|
|
(format stream "<~a~a>" (string-downcase type) (render-props props))))
|
2024-05-26 14:29:30 +00:00
|
|
|
|
2024-06-07 01:07:37 +00:00
|
|
|
(defun render-props (props)
|
2024-05-26 14:29:30 +00:00
|
|
|
(with-output-to-string (stream)
|
|
|
|
(loop
|
|
|
|
:for (key value) :on props :by #'cddr
|
2024-05-28 12:42:28 +00:00
|
|
|
:do (let ((key-str (string-downcase key)))
|
|
|
|
(if (typep value 'boolean)
|
|
|
|
(format stream
|
|
|
|
"~@[ ~a~]"
|
|
|
|
(and value key-str))
|
|
|
|
(format stream
|
|
|
|
" ~a=\"~a\""
|
|
|
|
key-str
|
2024-05-31 09:39:25 +00:00
|
|
|
(escape-html-attribute value)))))))
|
2024-05-26 14:29:30 +00:00
|
|
|
|
2024-06-09 12:03:21 +00:00
|
|
|
(defmethod render-children ((element tag))
|
2024-06-07 01:07:37 +00:00
|
|
|
(mapcar (lambda (child)
|
2024-06-09 12:03:21 +00:00
|
|
|
(if (stringp child)
|
2024-06-07 01:07:37 +00:00
|
|
|
(escape-html-text-content child)
|
|
|
|
child))
|
2024-06-09 12:03:21 +00:00
|
|
|
(element-children element)))
|
|
|
|
|
|
|
|
(defmethod render-children ((element non-escaping-tag))
|
|
|
|
(element-children element))
|
2024-06-07 01:07:37 +00:00
|
|
|
|
2024-05-29 01:30:06 +00:00
|
|
|
(defmethod print-object ((element html-tag) stream)
|
2024-05-26 14:29:30 +00:00
|
|
|
(format stream "<!DOCTYPE html>~%")
|
|
|
|
(call-next-method))
|
|
|
|
|
2024-05-29 01:30:06 +00:00
|
|
|
(defmethod print-object ((element fragment) stream)
|
2024-06-09 12:03:21 +00:00
|
|
|
(with-slots (children) element
|
2024-05-26 14:29:30 +00:00
|
|
|
(if children
|
2024-05-28 12:42:28 +00:00
|
|
|
(format stream
|
|
|
|
(if (rest children)
|
|
|
|
"~<~@{~a~^~:@_~}~:>"
|
|
|
|
"~<~a~:>")
|
2024-05-26 14:29:30 +00:00
|
|
|
children))))
|
|
|
|
|
2024-05-29 01:30:06 +00:00
|
|
|
(defmethod print-object ((element component) stream)
|
|
|
|
(print-object (expand-component element) stream))
|
2024-05-27 07:03:23 +00:00
|
|
|
|
2024-05-29 01:30:06 +00:00
|
|
|
(defmethod expand-component ((element component))
|
2024-06-09 12:03:21 +00:00
|
|
|
(with-slots (type props children) element
|
2024-05-27 07:03:23 +00:00
|
|
|
(apply type (merge-children-into-props props children))))
|
|
|
|
|
|
|
|
(defun merge-children-into-props (props children)
|
|
|
|
(append props
|
|
|
|
(and children
|
|
|
|
(list :children children))))
|