Mercurial Hosting > chat
changeset 79:b5a316575e64
reply
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Mon, 10 Mar 2025 21:41:53 -0600 |
parents | 2a602ef53eef |
children | cb2808b8b1ad |
files | src/add_post.js.luan src/chat.css src/chat.js src/get_chat.js.luan src/images/close.svg src/index.html.luan src/lib/Post.luan src/lib/Shared.luan |
diffstat | 8 files changed, 108 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- a/src/add_post.js.luan Sun Mar 09 22:20:36 2025 -0600 +++ b/src/add_post.js.luan Mon Mar 10 21:41:53 2025 -0600 @@ -3,6 +3,8 @@ local ipairs = Luan.ipairs or error() local Time = require "luan:Time.luan" local time_now = Time.now or error() +local String = require "luan:String.luan" +local to_number = String.to_number or error() local Parsers = require "luan:Parsers.luan" local json_string = Parsers.json_string or error() local Io = require "luan:Io.luan" @@ -29,12 +31,17 @@ local now = time_now() run_in_transaction( function() chat = get_chat_by_id(chat) or error() - post = new_post{ + post = { chat_id = chat.id author_id = user.id date = now content = content } + local reply = Http.request.parameters.reply + if reply ~= nil then + post.reply = to_number(reply) or error() + end + post = new_post(post) post.save() chat.updated = now chat.save()
--- a/src/chat.css Sun Mar 09 22:20:36 2025 -0600 +++ b/src/chat.css Mon Mar 10 21:41:53 2025 -0600 @@ -135,9 +135,21 @@ background-color: #428bca; } +blockquote, div[text] { white-space: pre-wrap; } +blockquote { + margin: 0; +} +div[quote] { + border-left: 1px solid #888888; + padding-left: 8px; +} +div[reply] a, +div[quote] [when] { + font-size: 12px; +} div[input] { padding-top: 1em; @@ -147,9 +159,25 @@ align-items: flex-end; } -div[input] textarea { +div[input] span[textarea] { flex-grow: 1; max-height: 150px; +} + +div[input] div[reply] { + border: 1px solid #888888; + padding: 2px; +} + +div[input] div[reply_top] { + display: flex; + justify-content: space-between; + color: #5f6368; +} + +div[input] textarea { + display: block; + width: 100%; resize: none; }
--- a/src/chat.js Sun Mar 09 22:20:36 2025 -0600 +++ b/src/chat.js Mon Mar 10 21:41:53 2025 -0600 @@ -60,8 +60,13 @@ let text = textarea.value; if( text.trim() === '' ) return; - ajax(`add_post.js?chat=${currentChatId}`,`content=${encodeURIComponent(text)}`); + let url = `add_post.js?chat=${currentChatId}`; + let reply = document.querySelector('div[reply]').getAttribute('reply'); + if( reply ) + url += `&reply=${reply}`; + ajax(url,`content=${encodeURIComponent(text)}`); textarea.value = ''; + closeReply(); } function textareaKey(event) { @@ -81,12 +86,21 @@ function fixPosts() { let divs = document.querySelectorAll('div[post][fix]'); for( let div of divs ) { - let whenSpan = div.querySelector('span[when]'); - whenSpan.textContent = new Date(Number(whenSpan.textContent)).toLocaleString([],{dateStyle:'short',timeStyle:'short'}); + for( let whenSpan of div.querySelectorAll('[when]') ) { + whenSpan.textContent = new Date(Number(whenSpan.textContent)).toLocaleString([],{dateStyle:'short',timeStyle:'short'}); + } let textDiv = div.querySelector('div[text]'); textDiv.innerHTML = urlsToLinks(textDiv.innerHTML); - if( div.getAttribute('author') === userId ) - div.querySelector('span[pulldown]').innerHTML = document.querySelector('div[hidden] span[pulldown]').innerHTML; + let reply = div.querySelector('blockquote'); + if( reply ) + reply.innerHTML = urlsToLinks(reply.innerHTML); + let html; + if( div.getAttribute('author') === userId ) { + html = document.querySelector('div[hidden] span[pulldown=author]').innerHTML; + } else { + html = document.querySelector('div[hidden] span[pulldown=other]').innerHTML; + } + div.querySelector('span[pulldown]').innerHTML = html; div.removeAttribute('fix'); } } @@ -406,3 +420,22 @@ let html = `<div user="${userId}" unread="${unread}">read by ${userNameHtml}</div>`; div.insertAdjacentHTML('beforeend',html); } + +function replyPost(el) { + let postId = getPostId(el); + let div = document.querySelector('div[reply]'); + div.removeAttribute('hidden'); + div.setAttribute('reply',postId); + document.querySelector('div[reply] div[text]').innerHTML = document.querySelector(`div[post="${postId}"] div[text]`).innerHTML + let a = document.querySelector('div[reply] a'); + a.href = `#p${postId}`; + a.textContent = document.querySelector(`div[post="${postId}"] span[when]`).textContent; + document.querySelector('div[input] textarea').focus(); + document.querySelector('div[input]').scrollIntoView({block: 'end'}); +} + +function closeReply() { + let div = document.querySelector('div[reply]'); + div.setAttribute('hidden',''); + div.setAttribute('reply',''); +}
--- a/src/get_chat.js.luan Sun Mar 09 22:20:36 2025 -0600 +++ b/src/get_chat.js.luan Mon Mar 10 21:41:53 2025 -0600 @@ -54,7 +54,17 @@ end %> <div input> - <textarea oninput="fixTextarea(event)" onkeydown="textareaKey(event)"></textarea> + <span textarea> + <div reply hidden> + <div reply_top> + <span>Reply to</span> + <img src="/images/close.svg" onclick="closeReply()"> + </div> + <div text></div> + <div><a when></a></div> + </div> + <textarea oninput="fixTextarea(event)" onkeydown="textareaKey(event)"></textarea> + </span> <button onclick="uploadFile()" title="Add file"><img src="/images/upload_file.svg"></button> <button onclick="addPost()" title="Send"><img src="/images/send.svg"></button> </div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/images/close.svg Mon Mar 10 21:41:53 2025 -0600 @@ -0,0 +1,1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg> \ No newline at end of file
--- a/src/index.html.luan Sun Mar 09 22:20:36 2025 -0600 +++ b/src/index.html.luan Mon Mar 10 21:41:53 2025 -0600 @@ -58,13 +58,20 @@ <div posts></div> </div> <div hidden> - <span pulldown> + <span pulldown=author> <img onclick="clickMenu(this)" src="/images/more_vert.svg"> <div> <span onclick="editPost(this)">Edit</span> + <span onclick="replyPost(this)">Reply</span> <span onclick="deletePost(this)">Delete</span> </div> </span> + <span pulldown=other> + <img onclick="clickMenu(this)" src="/images/more_vert.svg"> + <div> + <span onclick="replyPost(this)">Reply</span> + </div> + </span> </div> <dialog delete_chat> <h2>Delete Chat</h2>
--- a/src/lib/Post.luan Sun Mar 09 22:20:36 2025 -0600 +++ b/src/lib/Post.luan Mon Mar 10 21:41:53 2025 -0600 @@ -17,6 +17,7 @@ author_id = doc.author_id date = doc.post_date content = doc.content + reply = doc.reply } end @@ -28,6 +29,7 @@ author_id = long(post.author_id) post_date = long(post.date) content = post.content or error() + reply = post.reply and long(post.reply) } end
--- a/src/lib/Shared.luan Sun Mar 09 22:20:36 2025 -0600 +++ b/src/lib/Shared.luan Mon Mar 10 21:41:53 2025 -0600 @@ -17,6 +17,8 @@ local Utils = require "site:/lib/Utils.luan" local base_url = Utils.base_url or error() local Db = require "site:/lib/Db.luan" +local Post = require "site:/lib/Post.luan" +local get_post_by_id = Post.get_by_id or error() local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "Shared" @@ -121,8 +123,10 @@ local user = current_user() or error() local author = get_user_by_id(author_id) local id = post.id + local reply = post.reply + reply = reply and get_post_by_id(reply) %> - <div post="<%=id%>" author="<%=author.id%>" fix> + <div post="<%=id%>" author="<%=author.id%>" id="p<%=id%>" fix> <div who> <span author><%=author.name_html()%></span> <span right> @@ -130,6 +134,12 @@ <span pulldown></span> </span> </div> +<% if reply ~= nil then %> + <div quote> + <blockquote><%= html_encode(reply.content) %></blockquote> + <div><a when href="#p<%=reply.id%>"><%=reply.date%></a></div> + </div> +<% end %> <div text><%= html_encode(post.content) %></div> </div> <%