ec06b36df0
Why? I dunno. I think it looks cool.
143 lines
7.6 KiB
Common Lisp
143 lines
7.6 KiB
Common Lisp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(in-package #:live-chat-ui)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(defvar *default-prefix* "/lisp-chat")
|
|
|
|
(defun set-subpath-prefix (prefix)
|
|
(setf *default-prefix* prefix))
|
|
|
|
(defun subpath-prefix (path)
|
|
(format nil "~a~a" *default-prefix* path))
|
|
|
|
(defun static-asset-string (path)
|
|
(uiop:read-file-string
|
|
(merge-pathnames
|
|
path
|
|
(asdf:system-relative-pathname
|
|
:live-chat #P"static/"))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Render chat messages
|
|
(defun generate-html-message (content)
|
|
(cl-who:with-html-output-to-string (*standard-output*)
|
|
(:div :class "block"
|
|
:hx-swap-oob "afterbegin:#chat-messages"
|
|
(:div :class "box"
|
|
:style "overflow: auto; white-space: nowrap;"
|
|
(cl-who:str content)))))
|
|
|
|
(defun render-chat-messages ()
|
|
"Render the list of chat messages as HTML."
|
|
(let ((messages (fetch-messages)))
|
|
(cl-who:with-html-output-to-string (*standard-output* nil :indent nil)
|
|
(loop for msg in messages
|
|
collect (cl-who:str (generate-html-message msg))))))
|
|
|
|
;; Render chat UI
|
|
(defun render-chat-ui ()
|
|
"Render the main chat page with HTMX integration."
|
|
(cl-who:with-html-output-to-string (*standard-output* nil :prologue t :indent nil)
|
|
(:html :lang "en"
|
|
:class "has-navbar-fixed-bottom"
|
|
(:head
|
|
(:meta :charset "utf-8")
|
|
(:meta :name "viewport" :content "width=device-width, initial-scale=1")
|
|
;; (:meta :http-equiv "Content-Security-Policy"
|
|
;; :content "default-src *; style-src self unsafe-inline; script-src self unsafe-inline unsafe-eval https://phntsm.ddns.net")
|
|
(:title "Live Chat")
|
|
(:style
|
|
(cl-who:str (static-asset-string #P"bulma.min.css")))
|
|
;; (:link :rel "stylesheet"
|
|
;; :href "https://cdnjs.cloudflare.com/ajax/libs/bulma/1.0.2/css/bulma.min.css"
|
|
;; :integrity "sha512-RpeJZX3aH5oZN3U3JhE7Sd+HG8XQsqmP3clIbu4G28p668yNsRNj3zMASKe1ATjl/W80wuEtCx2dFA8xaebG5w=="
|
|
;; :crossorigin "anonymous"
|
|
;; :referrerpolicy "no-referrer")
|
|
(:script
|
|
(cl-who:str (static-asset-string #P"htmx.min.js")))
|
|
;; :integrity "sha512-dQu3OKLMpRu85mW24LA1CUZG67BgLPR8Px3mcxmpdyijgl1UpCM1RtJoQP6h8UkufSnaHVRTUx98EQT9fcKohw"
|
|
;; :src "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.3/htmx.min.js"
|
|
;; :crossorigin "anonymous"
|
|
;; :referrerpolicy "no-referrer")
|
|
(:script
|
|
(cl-who:str (static-asset-string #P"ws.min.js"))))
|
|
;; :src "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.3/ext/ws.min.js"
|
|
;; :integrity "sha512-1OIiXEswZd/etj60BUwFmyoi0OhrWdoYlzayJpSBivoMV0pQPIQr+vtAn3W3htsbWtLRU8DrBl0epdK4DQbj/w"
|
|
;; :crossorigin "anonymous"
|
|
;; :referrerpolicy "no-referrer"))
|
|
(:body
|
|
(:section :class "section"
|
|
(:div :class "container"
|
|
(:div :class "container"
|
|
:id "chat-messages"
|
|
:hx-get "/chat-messages"
|
|
(cl-who:str (render-chat-messages)))))
|
|
(:nav :role "navigation"
|
|
:class "navbar is-fixed-bottom is-spaced has-shadow"
|
|
:aria-label "main navigation"
|
|
(:div :class "navbar-brand"
|
|
(:a :class "navbar-item"
|
|
:href "https://phntsm.ddns.net/"
|
|
(:img :src "/favicon.png"
|
|
:alt "icon"))
|
|
(:div :class "navbar-item"
|
|
(:div :class "columns is-mobile"
|
|
(:div :class "column"
|
|
(:button :class "button is-link"
|
|
:onclick "moreInfo();"
|
|
"More Info 1"))
|
|
(:div :class "column"
|
|
(:button :class "button is-link"
|
|
:onclick "moreInfo();"
|
|
"More Info 2"))
|
|
(:div :class "column"
|
|
(:button :class "button is-link"
|
|
:onclick "moreInfo();"
|
|
"More Info 3"))))
|
|
(:a :role "button"
|
|
:class "navbar-burger"
|
|
:aria-label "menu"
|
|
:aria-expanded "false"
|
|
:data-target "navbarBasicExample"
|
|
(:span :aria-hidden "true")
|
|
(:span :aria-hidden "true")
|
|
(:span :aria-hidden "true")
|
|
(:span :aria-hidden "true")))
|
|
(:div :class "navbar-menu is-active"
|
|
:id "navbarBasicExample"
|
|
(:div :class "navbar-start"
|
|
:style "display: none;")
|
|
(:div :class "container navbar-middle"
|
|
(:div :class "navbar-item is-expanded"
|
|
:hx-ext "ws"
|
|
:ws-connect (subpath-prefix "/ws-chat-messages")
|
|
(:form :class "form is-expanded"
|
|
:style "width:100%;"
|
|
:id "chat-form"
|
|
:name "chat-form"
|
|
:ws-send ""
|
|
;; :method :post
|
|
;; :action (subpath-prefix "/post-message")
|
|
;; :hx-post (subpath-prefix "/post-message")
|
|
;; :hx-target "#chat-messages"
|
|
(:div :class "field has-addons is-expanded"
|
|
(:div :class "control is-expanded"
|
|
(:input :class "input is-expanded"
|
|
:id "chat-input"
|
|
:form "chat-form"
|
|
:autocomplete "off"
|
|
:placeholder "Enter your message..."
|
|
:type "text" :name "message"))
|
|
(:div :class "control"
|
|
(:button :class "button is-link is-light"
|
|
:id "submit-button"
|
|
:form "chat-form"
|
|
:type "submit" "Send"))))))
|
|
|
|
(:div :class "navbar-end"
|
|
:style "display: none;")))
|
|
(:script (cl-who:str (js-snippet (subpath-prefix "/ws-chat-messages"))))))))
|
|
|
|
(defun js-snippet (path)
|
|
(format nil (static-asset-string #P"chat.js") path))
|