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>
 <%