;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (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" (: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" (:h1 :class "title" "Live Chat") (:div :class "field has-addons" (:div :class "control is-expanded" (:input :class "input is-expanded" :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"))))) (:section :class "section" (:div :class "container" :hx-ext "ws" :ws-connect (subpath-prefix "/ws-chat-messages") (:h3 :class "title is-3" "Chat Messages") (:div :class "container" :id "chat-messages" :hx-get "/chat-messages" (cl-who:str (render-chat-messages))))) (:script " //////////////////////////////////////////////////////////////////////////////// function receivedMessage(msg) { document.querySelector('#chat-messages') .insertAdjacentHTML('afterbegin', msg.data); } // TODO {{ server-name }}:{{ server-port }} const ws = new WebSocket('ws://' + window.location.host + '/ws-chat-messages'); 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 = ''; } });")))))