changeset 10:f9e6a4cc4f7d

add Post
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 30 Oct 2024 23:18:45 -0600
parents b8b12fd8be22
children 563a5358f2ee
files src/add_post.js.luan src/chat.css src/chat.html.luan src/chat.js src/chat.js.luan src/get_chat.js.luan src/images/send.svg src/lib/Db.luan src/lib/Post.luan src/lib/Shared.luan src/site.css
diffstat 11 files changed, 283 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/add_post.js.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/add_post.js.luan	Wed Oct 30 23:18:45 2024 -0600
@@ -0,0 +1,34 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local Time = require "luan:Time.luan"
+local time_now = Time.now or error()
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local User = require "site:/lib/User.luan"
+local current_user = User.current or error()
+local Db = require "site:/lib/Db.luan"
+local run_in_transaction = Db.run_in_transaction or error()
+local Chat = require "site:/lib/Chat.luan"
+local get_chat_by_id = Chat.get_by_id or error()
+local Post = require "site:/lib/Post.luan"
+local new_post = Post.new or error()
+
+
+return function()
+	local user = current_user() or error()
+	local chat = Http.request.parameters.chat or error()
+	local content = Http.request.parameters.content or error()
+	run_in_transaction( function()
+		chat = get_chat_by_id(chat) or error()
+		local now = time_now()
+		local post = new_post{
+			chat_id = chat.id
+			author_id = user.id
+			date = now
+			content = content
+		}
+		post.save()
+		chat.updated = now
+		chat.save()
+	end )
+end
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/chat.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/chat.css	Wed Oct 30 23:18:45 2024 -0600
@@ -0,0 +1,75 @@
+div[content] {
+	margin-bottom: 0;
+	display: flex;
+	xheight: 100%;
+	height: calc(100vh - 32px);
+	margin-left: calc(3% - 8px);
+	margin-right: 0;
+}
+
+div[chats] {
+	width: 250px;
+	padding-right: 8px;
+	flex-shrink: 0;
+	overflow-y: auto;
+}
+
+div[chats] > div {
+	margin-top: 2px;
+	margin-bottom: 2px;
+	padding-top: 16px;
+	padding-bottom: 16px;
+	padding-left: 8px;
+	padding-right: 8px;
+	border-radius: 4px;
+}
+
+div[chats] > div:hover,
+div[chats] > div[selected] {
+	background-color: LightBlue;
+}
+
+div[posts] {
+	padding-left: 8px;
+	border-left: 1px solid;
+	width: 100%;
+	display: flex;
+	flex-direction: column;
+}
+
+div[posts] > * {
+	padding-right: 3vw;
+}
+
+div[top] {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+
+div[main] {
+	overflow-y: auto;
+}
+
+div[post] {
+	margin-top: 16px;
+	margin-bottom: 16px;
+}
+
+div[text] {
+	white-space-collapse: preserve;
+}
+
+div[input] {
+	padding-top: 1em;
+	padding-bottom: 1em;
+	display: flex;
+	gap: 8px;
+	align-items: flex-end;
+}
+
+div[input] textarea {
+	flex-grow: 1;
+	max-height: 150px;
+	resize: none;
+}
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/chat.html.luan
--- a/src/chat.html.luan	Wed Oct 30 10:18:13 2024 -0600
+++ b/src/chat.html.luan	Wed Oct 30 23:18:45 2024 -0600
@@ -2,6 +2,7 @@
 local error = Luan.error
 local ipairs = Luan.ipairs or error()
 local pairs = Luan.pairs or error()
+local range = Luan.range or error()
 local Table = require "luan:Table.luan"
 local concat = Table.concat or error()
 local is_empty = Table.is_empty or error()
@@ -11,6 +12,7 @@
 local Shared = require "site:/lib/Shared.luan"
 local head = Shared.head or error()
 local header = Shared.header or error()
+local started = Shared.started or error()
 local User = require "site:/lib/User.luan"
 local current_user = User.current or error()
 local get_user_by_email = User.get_by_email or error()
@@ -82,54 +84,9 @@
 	<head>
 <%		head() %>
 		<style>
-			body {
-				height: 100vh;
-				display: flex;
-				flex-direction: column;
-			}
-			div[content] {
-				margin-bottom: 0;
-				display: flex;
-				height: 100%;
-				margin-left: calc(3% - 8px);
-			}
-			div[chats] {
-				width: 250px;
-				padding-right: 8px;
-			}
-			div[chats] > div {
-				margin-top: 2px;
-				margin-bottom: 2px;
-				padding-top: 16px;
-				padding-bottom: 16px;
-				padding-left: 8px;
-				padding-right: 8px;
-				border-radius: 4px;
-			}
-			div[chats] > div:hover,
-			div[chats] > div[selected] {
-				background-color: LightBlue;
-			}
-			div[posts] {
-				padding-left: 8px;
-				border-left: 1px solid;
-			}
+			@import "chat.css?s=<%=started%>";
 		</style>
-		<script>
-			'use strict';
-
-			let currentChatId = null;
-
-			function selectChat(div) {
-				let chatId = div.getAttribute('chat');
-				if( chatId === currentChatId )
-					return;
-				let selected = div.parentNode.querySelector('[selected]');
-				if( selected )  selected.removeAttribute('selected');
-				div.setAttribute('selected','');
-				ajax(`chat.js?chat=${chatId}`);
-			}
-		</script>
+		<script src="chat.js?s=<%=started%>"></script>
 	</head>
 	<body>
 <%		header() %>
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/chat.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/chat.js	Wed Oct 30 23:18:45 2024 -0600
@@ -0,0 +1,48 @@
+'use strict';
+
+let currentChatId = null;
+
+function selectChat(div) {
+	let chatId = div.getAttribute('chat');
+	if( chatId === currentChatId )
+		return;
+	let selected = div.parentNode.querySelector('[selected]');
+	if( selected )  selected.removeAttribute('selected');
+	div.setAttribute('selected','');
+	ajax(`get_chat.js?chat=${chatId}`);
+	currentChatId = chatId;
+}
+
+function fixTextarea(event) {
+	let textarea = event.target;
+	textarea.style.height = 'initial';
+	textarea.style.height = (textarea.scrollHeight+2) + 'px';
+	textarea.scrollIntoViewIfNeeded(false);
+}
+
+const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
+
+function addPost() {
+	let textarea = document.querySelector('div[input] textarea');
+	let text = textarea.value;
+	if( text.trim() === '' )
+		return;
+	ajax(`add_post.js?chat=${currentChatId}&content=${encodeURIComponent(text)}`);
+	textarea.value = '';
+	console.log('addPost');
+}
+
+function textareaKey(event) {
+	if( event.keyCode===13 && !event.shiftKey && !event.ctrlKey && !isMobile ) {
+		event.preventDefault();
+		addPost();
+	}
+}
+
+function fixDates() {
+	let spans = document.querySelectorAll('span[when][fix]');
+	for( let span of spans ) {
+		span.textContent = new Date(Number(span.textContent)).toLocaleString();
+		span.removeAttribute('fix');
+	}
+}
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/chat.js.luan
--- a/src/chat.js.luan	Wed Oct 30 10:18:13 2024 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-local Luan = require "luan:Luan.luan"
-local error = Luan.error
-local Parsers = require "luan:Parsers.luan"
-local json_string = Parsers.json_string or error()
-local Io = require "luan:Io.luan"
-local Http = require "luan:http/Http.luan"
-local User = require "site:/lib/User.luan"
-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 function html()
-	local user = current_user() or error()
-	local chat = Http.request.parameters.chat or error()
-	chat = get_chat_by_id(chat) or error()
-%>
-	<p><%= chat.other_users_email(user) %></p>
-<%
-end
-
-return function()
-	Io.stdout = Http.response.text_writer()
-%>
-	document.querySelector('div[posts]').innerHTML = <%=json_string(`html()`)%>;
-<%
-end
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/get_chat.js.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/get_chat.js.luan	Wed Oct 30 23:18:45 2024 -0600
@@ -0,0 +1,64 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local Parsers = require "luan:Parsers.luan"
+local json_string = Parsers.json_string or error()
+local Html = require "luan:Html.luan"
+local html_encode = Html.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_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 Post = require "site:/lib/Post.luan"
+local post_search = Post.search or error()
+
+
+local function post_html(post)
+	local author = get_user_by_id(post.author_id)
+	local id = post.id
+%>
+		<div post="<%=id%>">
+			<div who>
+				<span author><%=author.email%></span>
+				<span when fix><%=post.date%></span>
+			</div>
+			<div text><%= html_encode(post.content) %></div>
+		</div>
+<%
+end
+
+local function html()
+	local user = current_user() or error()
+	local chat = Http.request.parameters.chat or error()
+	chat = get_chat_by_id(chat) or error()
+	local posts = post_search( "post_chat_id:"..chat.id, "id" )
+%>
+	<div top>
+		<h3><%= chat.other_users_email(user) %></h3>
+		<button>delete</button>
+	</div>
+	<div main>
+<%
+	for _, post in ipairs(posts) do
+		post_html(post)
+	end
+%>
+		<div input>
+			<textarea oninput="fixTextarea(event)" onkeydown="textareaKey(event)"></textarea>
+			<button onclick="addPost()" title="Send"><img src="/images/send.svg"></button>
+		</div>
+	</div>
+<%
+end
+
+return function()
+	Io.stdout = Http.response.text_writer()
+%>
+	document.querySelector('div[posts]').innerHTML = <%=json_string(`html()`)%>;
+	fixDates();
+	document.querySelector('div[input] textarea').focus();
+<%
+end
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/images/send.svg
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/images/send.svg	Wed Oct 30 23:18:45 2024 -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="M120-160v-640l760 320-760 320Zm80-120 474-200-474-200v140l240 60-240 60v140Zm0 0v-400 400Z"/></svg>
\ No newline at end of file
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/lib/Db.luan
--- a/src/lib/Db.luan	Wed Oct 30 10:18:13 2024 -0600
+++ b/src/lib/Db.luan	Wed Oct 30 23:18:45 2024 -0600
@@ -24,6 +24,8 @@
 Db.indexed_fields.chat_user_ids = Lucene.type.long
 Db.indexed_fields.chat_updated = Lucene.type.long
 
+Db.indexed_fields.post_chat_id = Lucene.type.long
+
 function Db.not_in_transaction()
 	logger.error(new_error("not in transaction"))
 end
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/lib/Post.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/Post.luan	Wed Oct 30 23:18:45 2024 -0600
@@ -0,0 +1,53 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local Number = require "luan:Number.luan"
+local long = Number.long or error()
+local Db = require "site:/lib/Db.luan"
+
+
+local Post = {}
+
+local function from_doc(doc)
+	doc.type == "post" or error "wrong type"
+	return Post.new {
+		id = doc.id
+		chat_id = doc.post_chat_id
+		author_id = doc.author_id
+		date = doc.date
+		content = doc.content
+	}
+end
+
+local function to_doc(post)
+	return {
+		type = "post"
+		id = post.id
+		post_chat_id = long(post.chat_id)
+		author_id = long(post.author_id)
+		date = long(post.date)
+		content = post.content or error()
+	}
+end
+
+function Post.new(post)
+	function post.save()
+		local doc = to_doc(post)
+		Db.save(doc)
+		post.id = doc.id
+	end
+
+	return post
+end
+
+function Post.search(query,sort,rows)
+	rows = rows or 1000000
+	local posts = {}
+	local docs = Db.search(query,1,rows,{sort=sort})
+	for _, doc in ipairs(docs) do
+		posts[#posts+1] = from_doc(doc)
+	end
+	return posts
+end
+
+return Post
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/lib/Shared.luan
--- a/src/lib/Shared.luan	Wed Oct 30 10:18:13 2024 -0600
+++ b/src/lib/Shared.luan	Wed Oct 30 23:18:45 2024 -0600
@@ -15,6 +15,7 @@
 local Shared = {}
 
 local started = Time.now()
+Shared.started = started
 
 function Shared.head()
 %>
diff -r b8b12fd8be22 -r f9e6a4cc4f7d src/site.css
--- a/src/site.css	Wed Oct 30 10:18:13 2024 -0600
+++ b/src/site.css	Wed Oct 30 23:18:45 2024 -0600
@@ -14,6 +14,7 @@
 	text-decoration: underline;
 }
 
+button,
 input[type="submit"] {
 	cursor: pointer;
 }