changeset 83:a47036fd0158

group chat
author Franklin Schmidt <fschmidt@gmail.com>
date Tue, 11 Mar 2025 13:56:59 -0600
parents 0bc5e0d098f7
children 83fb90276334
files src/account.html.luan src/add_to_chat.js.luan src/chat.css src/chat.js src/chat.luan src/do_login.html.luan src/get_chat.js.luan src/index.html.luan src/invite.js.luan src/lib/Chat.luan src/lib/Shared.luan src/login.js.luan
diffstat 12 files changed, 155 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/src/account.html.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/account.html.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -47,9 +47,8 @@
 
 			function saveUsername() {
 				let dialog = document.querySelector('dialog[edit_username]');
-				let input = dialog.querySelector('input[name=username]');
-				username = input.value;
-				closeModal(input);
+				closeModal(dialog);
+				username = dialog.querySelector('input[name=username]').value;
 				showUsername();
 				ajax(`save_username.js?username=${encodeURIComponent(username)}`);
 			}
@@ -70,11 +69,10 @@
 
 			function saveNotify() {
 				let dialog = document.querySelector('dialog[edit_notify]');
-				let input = dialog.querySelector('input[name=notify_email]');
-				notifyEmail = input.value;
+				closeModal(dialog);
+				notifyEmail = dialog.querySelector('input[name=notify_email]').value;
 				let radio = dialog.querySelector('input[type=radio]:checked');
 				multiNotify = radio.value === 'true';
-				closeModal(input);
 				showNotify();
 				ajax(`save_notify.js?email=${encodeURIComponent(notifyEmail)}&multi=${multiNotify}`);
 			}
@@ -93,9 +91,8 @@
 
 			function saveVoice() {
 				let dialog = document.querySelector('dialog[edit_voice]');
-				let input = dialog.querySelector('input[name=voice]');
-				voice = input.value;
-				closeModal(input);
+				closeModal(dialog);
+				voice = dialog.querySelector('input[name=voice]').value;
 				showVoice();
 				ajax(`save_voice.js?url=${encodeURIComponent(voice)}`);
 			}
@@ -219,7 +216,7 @@
 		</dialog>
 		<dialog url>
 			<h2>Your chat URL</h2>
-			<p><%= base_url() %>/chat?with=<%=user.email%></p>
+			<p><%= base_url() %>/chat?with=<%=html_encode(user.email)%></p>
 			<p>Use this URL to link to your chat so that other people can chat with you by clicking on the link.</p>
 			<div buttons>
 				<button onclick="closeModal(this)">Close</button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/add_to_chat.js.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -0,0 +1,44 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local pairs = Luan.pairs or error()
+local Table = require "luan:Table.luan"
+local concat = Table.concat or error()
+local Html = require "luan:Html.luan"
+local url_encode = Html.url_encode or error()
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local User = require "site:/lib/User.luan"
+local get_user_by_email = User.get_by_email or error()
+local get_user_by_id = User.get_by_id or error()
+local current_user = User.current or error()
+local Chat = require "site:/lib/Chat.luan"
+local get_chat_by_id = Chat.get_by_id or error()
+local Utils = require "site:/lib/Utils.luan"
+local list_to_set = Utils.list_to_set or error()
+
+
+return function()
+	local email = Http.request.parameters.email or error()
+	local add_user = get_user_by_email(email)
+	Io.stdout = Http.response.text_writer()
+	if add_user == nil then
+%>
+		addToChatError('email not found');
+<%
+		return
+	end
+	local user = current_user() or error()
+	local chat = Http.request.parameters.chat or error()
+	chat = get_chat_by_id(chat) or error()
+	local set = list_to_set(chat.user_ids)
+	set[user.id] or error()
+	set[add_user.id] = true
+	local t = {}
+	for id in pairs(set) do
+		local user = get_user_by_id(id)
+		t[#t+1] = "with="..url_encode(user.email)
+	end
+%>
+	location = 'chat?<%= concat(t,"&") %>';
+<%
+end
--- a/src/chat.css	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/chat.css	Tue Mar 11 13:56:59 2025 -0600
@@ -227,6 +227,14 @@
 	height: 1em;
 }
 
+dialog[add] input[name=email] {
+	width: 300px;
+}
+
+[error] {
+	color: red;
+}
+
 @media (min-width: 700px) {
 	div[chat_content] {
 		margin-left: calc(3% - 8px);
--- a/src/chat.js	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/chat.js	Tue Mar 11 13:56:59 2025 -0600
@@ -304,7 +304,7 @@
 function gotoInvite(el) {
 	let email = document.querySelector('dialog[invite] span[email]').textContent;
 	closeModal(el);
-	location = `chat?with=${email}`;
+	location = `chat?with=${encodeURIComponent(email)}`;
 	ajax(`save_post.js?post=${currentPostId}`,`content=${encodeURIComponent(text)}`);
 }
 
@@ -443,3 +443,20 @@
 	div.setAttribute('hidden','');
 	div.setAttribute('reply','');
 }
+
+function openAddToChat() {
+	let dialog = document.querySelector('dialog[add]');
+	dialog.querySelector('input[name=email]').value = '';
+	openModal(dialog);
+}
+
+function addToChat() {
+	let dialog = document.querySelector('dialog[add]');
+	let email = dialog.querySelector('input[name=email]').value;
+	ajax(`add_to_chat.js?chat=${currentChatId}&email=${encodeURIComponent(email)}`);
+}
+
+function addToChatError(msg) {
+	let dialog = document.querySelector('dialog[add]');
+	dialog.querySelector('span[error]').textContent = msg;
+}
--- a/src/chat.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/chat.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -4,6 +4,8 @@
 local Table = require "luan:Table.luan"
 local concat = Table.concat or error()
 local is_empty = Table.is_empty or error()
+local Html = require "luan:Html.luan"
+local url_encode = Html.url_encode or error()
 local Http = require "luan:http/Http.luan"
 local Shared = require "site:/lib/Shared.luan"
 local http_push_to_users = Shared.http_push_to_users or error()
@@ -27,7 +29,7 @@
 		if not is_empty(with) then
 			local t = {}
 			for email in pairs(with) do
-				t[#t+1] = "with="..email
+				t[#t+1] = "with="..url_encode(email)
 			end
 			url = url.."?"..concat(t,"&")
 		end
--- a/src/do_login.html.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/do_login.html.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -3,6 +3,8 @@
 local ipairs = Luan.ipairs or error()
 local Table = require "luan:Table.luan"
 local concat = Table.concat or error()
+local Html = require "luan:Html.luan"
+local url_encode = Html.url_encode or error()
 local Io = require "luan:Io.luan"
 local Http = require "luan:http/Http.luan"
 local Shared = require "site:/lib/Shared.luan"
@@ -41,7 +43,7 @@
 			with = to_list(with)
 			local t = {}
 			for _, email in ipairs(with) do
-				t[#t+1] = "with="..email
+				t[#t+1] = "with="..url_encode(email)
 			end
 			location = "/chat?"..concat(t,"&")
 		end
--- a/src/get_chat.js.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/get_chat.js.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -18,6 +18,7 @@
 local post_search = Post.search or error()
 local Shared = require "site:/lib/Shared.luan"
 local post_html = Shared.post_html or error()
+local group_name = Shared.group_name or error()
 local Logging = require "luan:logging/Logging.luan"
 local logger = Logging.logger "get_chat.js"
 
@@ -29,20 +30,33 @@
 	<div top>
 		<h3>
 			<img back onclick="back()" src="/images/arrow_back.svg">
-			<span><%
-				local user_id = chat.other_user_id(user.id)
-				local other_user = get_user_by_id(user_id) or error(user_id)
-				%><%= other_user.name_html() %><span online="<%= other_user.id %>"></span><%
-				local voice_url = other_user.voice_url
-				if voice_url ~= nil then
-					%> <a href="<%=voice_url%>" title="Call" target="voice"><img phone src="/images/call.svg"></a><%
-				end
-			%></span>
+			<span>
+<%
+	local user_ids = chat.other_user_ids(user.id)
+	if #user_ids > 1 then
+%>
+				<%= group_name(user_ids) %>
+<%
+	else
+		local other_user = get_user_by_id(user_ids[1]) or error()
+%>
+				<%= other_user.name_html() %><span online="<%= other_user.id %>"></span>
+<%
+		local voice_url = other_user.voice_url
+		if voice_url ~= nil then
+%>
+				<a href="<%=voice_url%>" title="Call" target="voice"><img phone src="/images/call.svg"></a>
+<%
+		end
+	end
+%>
+			</span>
 		</h3>
 		<span pulldown>
 			<img onclick="clickMenu(this)" src="/images/menu.svg">
 			<div>
 				<span onclick="openPeople()">People in Chat</span>
+				<span onclick="openAddToChat()">Add Someone to Chat</span>
 				<span onclick="deleteChat()">Delete Chat</span>
 			</div>
 		</span>
--- a/src/index.html.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/index.html.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -111,6 +111,20 @@
 				<button onclick="closeModal(this)">Close</button>
 			</div>
 		</dialog>
+		<dialog add>
+			<h2>Add Someone to Chat</h2>
+			<form action="javascript:addToChat()">
+				<p>
+					<label>Person's email</label><br> 
+					<input type=email name=email required><br>
+					<span error></span>
+				</p>
+				<div buttons>
+					<button onclick="closeModal(this)">Cancel</button>
+					<button type=submit>Add</button>
+				</div>
+			</form>
+		</dialog>
 		<input type="file" required onchange="loadedFile(this)">
 		<script>
 			'use strict';
--- a/src/invite.js.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/invite.js.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -2,8 +2,10 @@
 local error = Luan.error
 local Parsers = require "luan:Parsers.luan"
 local json_string = Parsers.json_string or error()
+local Html = require "luan:Html.luan"
+local url_encode = Html.url_encode or error()
+local Io = require "luan:Io.luan"
 local Http = require "luan:http/Http.luan"
-local Io = require "luan:Io.luan"
 local User = require "site:/lib/User.luan"
 local get_user_by_email = User.get_by_email or error()
 local get_or_create_user_by_email = User.get_or_create_by_email or error()
@@ -20,13 +22,13 @@
 	Io.stdout = Http.response.text_writer()
 	if get_user_by_email(email) ~= nil then
 %>
-		location = 'chat?with=<%=email%>';
+		location = 'chat?with=<%=url_encode(email)%>';
 <%
 		return
 	end
 	local invitee = get_or_create_user_by_email(email)
 	local password = invitee.password
-	local url = base_url().."/do_login.html?user="..invitee.id.."&password="..password.."&with="..user.email
+	local url = base_url().."/do_login.html?user="..invitee.id.."&password="..password.."&with="..url_encode(user.email)
 	local who = user.name or user.email
 	send_mail {
 		To = email
--- a/src/lib/Chat.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/lib/Chat.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -65,13 +65,14 @@
 		Http.push(url,message)
 	end
 
-	function chat.other_user_id(my_id)
+	function chat.other_user_ids(my_id)
+		local t = {nil}
 		for _, user_id in ipairs(chat.user_ids) do
 			if user_id ~= my_id then
-				return user_id
+				t[#t+1] = user_id
 			end
 		end
-		error()
+		return t
 	end
 
 	local function get_chatuser_key(user)
--- a/src/lib/Shared.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/lib/Shared.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -2,6 +2,8 @@
 local error = Luan.error
 local ipairs = Luan.ipairs or error()
 local parse = Luan.parse or error()
+local Table = require "luan:Table.luan"
+local concat = Table.concat or error()
 local Time = require "luan:Time.luan"
 local Thread = require "luan:Thread.luan"
 local thread_run = Thread.run or error()
@@ -145,18 +147,37 @@
 <%
 end
 
+local function group_name(user_ids)
+	local t = {nil}
+	for _, user_id in ipairs(user_ids) do
+		t[#t+1] = get_user_by_id(user_id).name_html()
+	end
+	return concat(t,", ")
+end
+Shared.group_name = group_name
+
 function Shared.chats_html()
 	local user = current_user() or error()
 	local chats = chat_search( "chat_user_ids:"..user.id, "chat_updated desc" )
 	for _, chat in ipairs(chats) do
 		local chat_id = chat.id
-		local user_id = chat.other_user_id(user.id)
-		local other_user = get_user_by_id(user_id) or error()
 		local unread = chat.unread(user)
 %>
 		<div chat="<%=chat_id%>" onclick="selectChat('<%=chat_id%>')">
+<%
+		local user_ids = chat.other_user_ids(user.id)
+		if #user_ids > 1 then
+%>
+			<%= group_name(user_ids) %>
+<%
+		else
+			local other_user = get_user_by_id(user_ids[1]) or error()
+%>
 			<%= other_user.name_html() %>
 			<span online="<%= other_user.id %>"></span>
+<%
+		end
+%>
 			<span unread="<%=unread%>"><%=unread%></span>
 		</div>
 <%
--- a/src/login.js.luan	Mon Mar 10 22:42:46 2025 -0600
+++ b/src/login.js.luan	Tue Mar 11 13:56:59 2025 -0600
@@ -1,6 +1,8 @@
 local Luan = require "luan:Luan.luan"
 local error = Luan.error
 local ipairs = Luan.ipairs or error()
+local Html = require "luan:Html.luan"
+local url_encode = Html.url_encode or error()
 local Io = require "luan:Io.luan"
 local Http = require "luan:http/Http.luan"
 local Shared = require "site:/lib/Shared.luan"
@@ -19,7 +21,7 @@
 	local with = Http.request.parameters.with
 	with = to_list(with)
 	for _, email in ipairs(with) do
-		url = url.."&with="..email
+		url = url.."&with="..url_encode(email)
 	end
 	send_mail_async {
 		To = email