構(gòu)建一個即時消息應(yīng)用(八):Home 頁面
本文是該系列的第八篇。
繼續(xù)前端部分,讓我們在本文中完成 home
頁面的開發(fā)。 我們將添加一個開始對話的表單和一個包含最新對話的列表。
對話表單
轉(zhuǎn)到 static/ages/home-page.js
文件,在 HTML 視圖中添加一些標(biāo)記。
- <form id="conversation-form">
- <input type="search" placeholder="Start conversation with..." required>
- </form>
將該表單添加到我們顯示 “auth user” 和 “logout” 按鈕部分的下方。
- page.getElementById('conversation-form').onsubmit = onConversationSubmit
現(xiàn)在我們可以監(jiān)聽 “submit” 事件來創(chuàng)建對話了。
- import http from '../http.js'
- import { navigate } from '../router.js'
- async function onConversationSubmit(ev) {
- ev.preventDefault()
- const form = ev.currentTarget
- const input = form.querySelector('input')
- input.disabled = true
- try {
- const conversation = await createConversation(input.value)
- input.value = ''
- navigate('/conversations/' + conversation.id)
- } catch (err) {
- if (err.statusCode === 422) {
- input.setCustomValidity(err.body.errors.username)
- } else {
- alert(err.message)
- }
- setTimeout(() => {
- input.focus()
- }, 0)
- } finally {
- input.disabled = false
- }
- }
- function createConversation(username) {
- return http.post('/api/conversations', { username })
- }
在提交時,我們使用用戶名對 /api/conversations
進行 POST 請求,并重定向到 conversation
頁面(用于下一篇文章)。
對話列表
還是在這個文件中,我們將創(chuàng)建 homePage()
函數(shù)用來先異步加載對話。
- export default async function homePage() {
- const conversations = await getConversations().catch(err => {
- console.error(err)
- return []
- })
- /*...*/
- }
- function getConversations() {
- return http.get('/api/conversations')
- }
然后,在標(biāo)記中添加一個列表來渲染對話。
- <ol id="conversations"></ol>
將其添加到當(dāng)前標(biāo)記的正下方。
- const conversationsOList = page.getElementById('conversations')
- for (const conversation of conversations) {
- conversationsOList.appendChild(renderConversation(conversation))
- }
因此,我們可以將每個對話添加到這個列表中。
- import { avatar, escapeHTML } from '../shared.js'
- function renderConversation(conversation) {
- const messageContent = escapeHTML(conversation.lastMessage.content)
- const messageDate = new Date(conversation.lastMessage.createdAt).toLocaleString()
- const li = document.createElement('li')
- li.dataset['id'] = conversation.id
- if (conversation.hasUnreadMessages) {
- li.classList.add('has-unread-messages')
- }
- li.innerHTML = `
- <a href="/conversations/${conversation.id}">
- <div>
- ${avatar(conversation.otherParticipant)}
- <span>${conversation.otherParticipant.username}</span>
- </div>
- <div>
- <p>${messageContent}</p>
- <time>${messageDate}</time>
- </div>
- </a>
- `
- return li
- }
每個對話條目都包含一個指向?qū)υ掜撁娴逆溄?,并顯示其他參與者信息和最后一條消息的預(yù)覽。另外,您可以使用 .hasUnreadMessages
向該條目添加一個類,并使用 CSS 進行一些樣式設(shè)置。也許是粗體字體或強調(diào)顏色。
請注意,我們需要轉(zhuǎn)義信息的內(nèi)容。該函數(shù)來自于 static/shared.js
文件:
- export function escapeHTML(str) {
- return str
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
- }
這會阻止將用戶編寫的消息顯示為 HTML。如果用戶碰巧編寫了類似以下內(nèi)容的代碼:
<script>alert('lololo')</script>
這將非常煩人,因為該腳本將被執(zhí)行😅。所以,永遠記住要轉(zhuǎn)義來自不可信來源的內(nèi)容。
消息訂閱
最后但并非最不重要的一點,我想在這里訂閱消息流。
- const unsubscribe = subscribeToMessages(onMessageArrive)
- page.addEventListener('disconnect', unsubscribe)
在 homePage()
函數(shù)中添加這一行。
- function subscribeToMessages(cb) {
- return http.subscribe('/api/messages', cb)
- }
函數(shù) subscribe()
返回一個函數(shù),該函數(shù)一旦調(diào)用就會關(guān)閉底層連接。這就是為什么我把它傳遞給 “斷開連接”事件的原因;因此,當(dāng)用戶離開頁面時,事件流將被關(guān)閉。
- async function onMessageArrive(message) {
- const conversationLI = document.querySelector(`li[data-id="${message.conversationID}"]`)
- if (conversationLI !== null) {
- conversationLI.classList.add('has-unread-messages')
- conversationLI.querySelector('a > div > p').textContent = message.content
- conversationLI.querySelector('a > div > time').textContent = new Date(message.createdAt).toLocaleString()
- return
- }
- let conversation
- try {
- conversation = await getConversation(message.conversationID)
- conversation.lastMessage = message
- } catch (err) {
- console.error(err)
- return
- }
- const conversationsOList = document.getElementById('conversations')
- if (conversationsOList === null) {
- return
- }
- conversationsOList.insertAdjacentElement('afterbegin', renderConversation(conversation))
- }
- function getConversation(id) {
- return http.get('/api/conversations/' + id)
- }
每次有新消息到達時,我們都會在 DOM 中查詢會話條目。如果找到,我們會將 has-unread-messages
類添加到該條目中,并更新視圖。如果未找到,則表示該消息來自剛剛創(chuàng)建的新對話。我們?nèi)プ鲆粋€對 /api/conversations/{conversationID}
的 GET 請求,以獲取在其中創(chuàng)建消息的對話,并將其放在對話列表的前面。
以上這些涵蓋了主頁的所有內(nèi)容 😊。 在下一篇文章中,我們將對 conversation 頁面進行編碼。