-
Notifications
You must be signed in to change notification settings - Fork 28
/
ps.lisp
137 lines (120 loc) · 4.65 KB
/
ps.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
(in-package #:spinneret)
(defparameter *props*
'("acceptCharset" "accessKey" "allowTransparency" "bgColor" "cellPadding"
"cellSpacing" "className" "className" "colSpan" "style" "defaultChecked"
"defaultSelected" "defaultValue" "htmlFor" "frameBorder" "hSpace" "htmlFor"
"longDesc" "maxLength" "marginWidth" "marginHeight" "noResize" "noShade"
"readOnly" "rowSpan" "tabIndex" "vAlign" "vSpace"))
(defparameter *ie-attr-props*
'(("for" . "htmlfor")
("class" . "classname")))
(define-ps-symbol-macro *html* (@ window spinneret))
(define-ps-symbol-macro *html-charset* (lisp *html-charset*))
(define-ps-symbol-macro *html-lang* (lisp *html-lang*))
(defpsmacro ch (&rest args)
`(chain ,@args))
(defpsmacro with-html (&rest html-forms)
(with-ps-gensyms (node d)
`(let ((,node (or *html*
(setf *html* (ch document (create-document-fragment)))))
(,d document))
(symbol-macrolet ((*html* ,node)
(document ,d))
,@(with-standard-io-syntax
(parse-html html-forms nil)))
(unless (@ ,node parent-node)
(prog1 ,node
(setf *html* nil))))))
(defpsmacro with-tag ((name &rest attributes) &body body)
`(progn
(setf *html*
(ch *html*
(append-child
(ch document (create-element ,(string-downcase name))))))
,@(loop for (attr val . nil) on attributes by #'cddr
collect (make-attr-setter (string-downcase attr) val))
,@(when body
(loop for form in body
if (and (consp form) (eql (car form) 'with-tag))
collect form
else collect `(ch *html* (append-child
(ch document
(create-text-node
(stringify ,form)))))))
(setf *html* (@ *html* parent-node))
nil))
(defun make-attr-setter (attr val)
;; Compatibility hacks from Laconic.js 0.2.2.
(let ((attr (or (find
(or (cdr (assoc attr *ie-attr-props* :test #'string-equal))
attr)
*props* :test #'string-equal)
attr))
(sval `(stringify ,val)))
(flet ((set-or-remove (object attr val)
(with-ps-gensyms (actual-val)
`(let ((,actual-val ,val))
(if ,actual-val
(ch ,object (set-attribute ,attr (stringify ,actual-val)))
(ch ,object (remove-attribute ,attr)))))))
(cond
((event? attr)
;; Set events as properties, ensuring a href.
`(setf (@ *html* ,attr) ,sval
(@ *html* href)
(or (@ *html* href) "#")))
;; Style requires special handling for IE.
((string-equal attr "style")
`(if (@ *html* style set-attribute)
(ch *html* style (set-attribute 'css-text ,sval))
(ch *html* (set-attribute ,attr ,sval))))
((rassoc attr *ie-attr-props* :test #'string-equal)
;; Other special cases for IE.
`(setf (@ *html* ,attr) ,sval))
((data-attr? attr)
`(setf (@ *html* dataset ,(data-attr-prop attr)) ,sval))
((string-equal attr "attrs")
(with-ps-gensyms (attrs attr)
`(let ((,attrs ,val))
(for-in (,attr ,attrs)
,(set-or-remove '*html* attr `(@ ,attrs ,attr))))))
(t (set-or-remove '*html* attr val))))))
(defun event? (attr)
(starts-with-subseq "on" (string attr)))
(defun data-attr? (attr)
(starts-with-subseq "data-" (string attr)))
(defun data-attr-prop (attr)
(subseq (string-downcase attr) 5))
(defpsmacro comment (text safe?)
(declare (ignore safe?))
`(stringify
,(concat-constant-strings
(list "<!-- " text " -->"))))
(defpsmacro cdata (text safe?)
(declare (ignore safe?))
`(stringify
,(concat-constant-strings
(list cdata-start text cdata-end))))
(defpsmacro format-text (formatter &rest args)
(let ((control-string
(if (listp formatter)
(second formatter)
formatter)))
(prog1 control-string
(when args
(cerror
"Discard arguments and print \"~A\" literally."
"Parenscript doesn't have FORMAT."
control-string)))))
(defpsmacro join-tokens (&rest classes)
`(stringify
,@(concat-constant-strings
(intersperse " "
(remove-duplicates (remove nil classes)
:test #'equal)))))
(defun intersperse (new-elt list)
(cons (car list)
(mapcan
(lambda (elt)
(list new-elt elt))
(cdr list))))