cl-chat-web/live-chat-ui.lisp
2024-10-13 01:26:33 -04:00

123 lines
5.8 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))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Render chat messages
(defun render-chat-messages ()
"Render the list of chat messages as HTML."
(let ((messages (fetch-messages)))
(if messages
(cl-who:with-html-output-to-string (*standard-output* nil :indent t)
(loop for msg in messages
do (cl-who:htm
(:div :class "box"
:style "overflow: auto;"
(cl-who:str 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 t)
(:html :lang "en"
:class "has-navbar-fixed-bottom"
(:head
(:meta :charset "utf-8")
(:meta :name "viewport" :content "width=device-width, initial-scale=1")
(:title "Live Chat")
(: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 :src "https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.3/htmx.min.js"
:integrity "sha512-dQu3OKLMpRu85mW24LA1CUZG67BgLPR8Px3mcxmpdyijgl1UpCM1RtJoQP6h8UkufSnaHVRTUx98EQT9fcKohw=="
:crossorigin "anonymous"
:referrerpolicy "no-referrer")
;; (:script :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"
;; :hx-ext "ws"
;; :ws-connect (subpath-prefix "/ws-chat-messages")
(: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"))
(: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 "navbar-middle container"
(:div :class "field has-addons"
(:div :class "control is-expanded"
(:input :class "input"
:id "chat-input"
:autocomplete "off"
:placeholder "Enter your message..."
:type "text" :name "message"))
(:div :class "control"
(:button :class "button is-link is-light"
: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
"function receivedMessage(msg) {
document.querySelector('#chat-messages').insertAdjacentHTML('afterbegin', msg.data);
}
const ws = new WebSocket('wss://' + window.location.host + '~a');
ws.addEventListener('message', receivedMessage);
const inputField = document.getElementById('chat-input');
inputField.addEventListener('keyup', (evt) => {
if (evt.key === 'Enter') {
ws.send(evt.target.value);
evt.target.value = '';
}
});
// https://bulma.io/documentation/components/navbar/#navbar-menu
document.addEventListener('DOMContentLoaded', () => {
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
// Add a click event on each of them
$navbarBurgers.forEach( el => {
el.addEventListener('click', () => {
const target = el.dataset.target;
const $target = document.getElementById(target);
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
});
});
});" path))