diff --git a/anonstream/routes/nojs.py b/anonstream/routes/nojs.py index e34c52e..fb5aaf2 100644 --- a/anonstream/routes/nojs.py +++ b/anonstream/routes/nojs.py @@ -10,11 +10,12 @@ from anonstream.user import add_state, pop_state, try_change_appearance, update_ from anonstream.routes.wrappers import with_user_from, render_template_with_etag from anonstream.helpers.chat import get_scrollback from anonstream.helpers.user import get_default_name -from anonstream.utils.chat import generate_nonce +from anonstream.utils.chat import generate_nonce, should_show_initial_date from anonstream.utils.security import generate_csp from anonstream.utils.user import concatenate_for_notice CONFIG = current_app.config +MESSAGES = current_app.messages USERS_BY_TOKEN = current_app.users_by_token @current_app.route('/stream.html') @@ -47,15 +48,17 @@ async def nojs_info(timestamp, user): @with_user_from(request) async def nojs_chat_messages(timestamp, user): reading(user) + messages = get_scrollback(MESSAGES) return await render_template_with_etag( 'nojs_chat_messages.html', {'csp': generate_csp()}, refresh=CONFIG['NOJS_REFRESH_MESSAGES'], user=user, users_by_token=USERS_BY_TOKEN, - messages=get_scrollback(current_app.messages), + messages=messages, timeout=CONFIG['NOJS_TIMEOUT_CHAT'], get_default_name=get_default_name, + show_initial_date=should_show_initial_date(timestamp, messages), ) @current_app.route('/chat/messages') diff --git a/anonstream/static/anonstream.js b/anonstream/static/anonstream.js index 1625afb..295ec19 100644 --- a/anonstream/static/anonstream.js +++ b/anonstream/static/anonstream.js @@ -189,6 +189,7 @@ const create_chat_message = (object) => { chat_message.classList.add("chat-message"); chat_message.dataset.seq = object.seq; chat_message.dataset.tokenHash = object.token_hash; + chat_message.dataset.date = object.date; const chat_message_time = document.createElement("time"); chat_message_time.classList.add("chat-message__time"); @@ -256,23 +257,65 @@ const create_chat_user_components = (user) => { result.push(...[chat_user_name, chat_user_tripcode_nbsp, chat_user_tripcode]); return result; } +const zeropad = (n) => ("0" + n).slice(-2); +const datestamp = () => { + const date = new Date(); + return `${date.getUTCFullYear()}-${zeropad(date.getUTCMonth() + 1)}-${zeropad(date.getUTCDate())}`; +} const create_and_add_chat_message = (object) => { + // date + last_chat_message = chat_messages.querySelector(".chat-message:last-of-type"); + if (last_chat_message === null || last_chat_message.dataset.date !== object.date) { + const chat_date = document.createElement("li"); + chat_date.classList.add("chat-date"); + chat_date.dataset.date = object.date; + + const chat_date_hr = document.createElement("hr"); + const chat_date_div = document.createElement("div"); + + const chat_date_div_time = document.createElement("time"); + chat_date_div_time.datetime = object.date; + chat_date_div_time.innerText = object.date; + + chat_date_div.insertAdjacentElement("beforeend", chat_date_div_time); + chat_date.insertAdjacentElement("beforeend", chat_date_hr); + chat_date.insertAdjacentElement("beforeend", chat_date_div); + if (last_chat_message === null && object.date === datestamp()) + chat_date.dataset.hidden = ""; + chat_messages.insertAdjacentElement("beforeend", chat_date); + } + + // message const chat_message = create_chat_message(object); chat_messages.insertAdjacentElement("beforeend", chat_message); - while (chat_messages.children.length > max_chat_scrollback) { - chat_messages.children[0].remove(); + const first_chat_message = chat_messages.querySelector(".chat-message"); + if (first_chat_message !== null) { + const first_chat_date = chat_messages.querySelector(".chat-date"); + if (first_chat_date !== null && first_chat_date.hasAttribute("data-hidden") && (object.date !== first_chat_message.dataset.date || object.date !== datestamp())) + first_chat_date.removeAttribute("data-hidden"); } + const string_seqs = new Set(); + for (const this_chat_message of chat_messages.querySelectorAll(".chat-message")) { + if (chat_messages.querySelectorAll(".chat-message").length - string_seqs.size > max_chat_scrollback) + string_seqs.add(this_chat_message.dataset.seq); + else + break; + } + delete_chat_messages({string_seqs}); } -const delete_chat_messages = (seqs) => { - string_seqs = new Set(seqs.map(n => n.toString())); - to_delete = []; - for (const chat_message of chat_messages.children) { - if (string_seqs.has(chat_message.dataset.seq)) - to_delete.push(chat_message); +const delete_chat_messages = ({string_seqs, keep=false}) => { + const keep_dates = new Set(); + for (const chat_message of chat_messages.querySelectorAll(".chat-message")) { + if (string_seqs.has(chat_message.dataset.seq) === keep) + keep_dates.add(chat_message.dataset.date); } - for (const chat_message of to_delete) { - chat_message.remove(); + const to_delete = []; + for (const child of chat_messages.children) { + if (child.classList.contains("chat-date") && !keep_dates.has(child.dataset.date) || child.classList.contains("chat-message") && string_seqs.has(child.dataset.seq) !== keep) + to_delete.push(child); } + for (const element of to_delete) + element.remove(); } let users = {}; @@ -340,7 +383,7 @@ const get_user_name = ({user=null, token_hash}) => { } const update_user_names = (token_hash=null) => { const token_hashes = token_hash === null ? Object.keys(users) : [token_hash]; - for (const chat_message of chat_messages.children) { + for (const chat_message of chat_messages.querySelectorAll(".chat-message")) { const this_token_hash = chat_message.dataset.tokenHash; if (token_hashes.includes(this_token_hash)) { const user = users[this_token_hash]; @@ -422,7 +465,7 @@ const update_user_tripcodes = (token_hash=null) => { } // update inner texts - for (const chat_message of chat_messages.children) { + for (const chat_message of chat_messages.querySelectorAll(".chat-message")) { const this_token_hash = chat_message.dataset.tokenHash; const tripcode = users[this_token_hash].tripcode; if (token_hashes.includes(this_token_hash)) { @@ -632,17 +675,8 @@ const on_websocket_message = async (event) => { chat_form_submit.disabled = false; // remove messages the server isn't acknowledging the existence of - const seqs = new Set(receipt.messages.map((message) => {return message.seq;})); - const to_delete = []; - for (const chat_message of chat_messages.children) { - const chat_message_seq = parseInt(chat_message.dataset.seq); - if (!seqs.has(chat_message_seq)) { - to_delete.push(chat_message); - } - } - for (const chat_message of to_delete) { - chat_message.remove(); - } + const string_seqs = new Set(receipt.messages.map(message => message.seq.toString())); + delete_chat_messages({string_seqs, keep: true}); // settings default_name = receipt.default; @@ -665,7 +699,7 @@ const on_websocket_message = async (event) => { left: 0, top: chat_messages.scrollTopMax, behavior: "instant", - }); + }); } // appearance form default values @@ -677,7 +711,8 @@ const on_websocket_message = async (event) => { chat_appearance_form_color.setAttribute("value", user.color); // insert new messages - const last = chat_messages.children.length == 0 ? null : chat_messages.children[chat_messages.children.length - 1]; + const chat_messages_messages = chat_messages.querySelectorAll(".chat-message"); + const last = chat_messages_messages.length == 0 ? null : chat_messages_messages[chat_messages_messages.length - 1]; const last_seq = last === null ? null : parseInt(last.dataset.seq); for (const message of receipt.messages) { if (message.seq > last_seq) { @@ -744,7 +779,7 @@ const on_websocket_message = async (event) => { case "delete": console.log("ws delete", receipt); - delete_chat_messages(receipt.seqs); + delete_chat_messages({string_seqs: new Set(receipt.seqs.map(n => n.toString()))}); break; case "set-users": @@ -969,6 +1004,14 @@ chat_messages_unlock.addEventListener("click", (event) => { chat_messages.scrollTop = chat_messages.scrollTopMax; }); +/* show initial chat date if a day has passed */ +const show_initial_date = () => { + const chat_date = chat_messages.querySelector(".chat-date:first-child"); + if (chat_date !== null && chat_date.hasAttribute("data-hidden") && chat_date.dataset.date !== datestamp()) + chat_date.removeAttribute("data-hidden"); +} +setInterval(show_initial_date, 30000); + /* close websocket after prolonged absence of pings */ const rotate_websocket = () => { diff --git a/anonstream/static/style.css b/anonstream/static/style.css index 836230a..24212b2 100644 --- a/anonstream/static/style.css +++ b/anonstream/static/style.css @@ -273,6 +273,29 @@ noscript { font-size: 9pt; cursor: default; } +.chat-date { + text-align: center; + position: relative; + display: grid; + align-items: center; + margin: 8px 0; + color: #b2b2b3; + cursor: default; +} +.chat-date[data-hidden] { + display: none; +} +.chat-date > hr { + margin: 0; + position: absolute; + width: 100%; + box-sizing: border-box; +} +.chat-date > :not(hr) > time { + padding: 0 1ch; + background-color: #232327; + position: relative; +} #chat__body__users { background-color: #121214; mask-image: linear-gradient(black calc(100% - 0.625rem), transparent calc(100% - 0.125rem)); diff --git a/anonstream/templates/nojs_chat_messages.html b/anonstream/templates/nojs_chat_messages.html index 91999cd..349dc45 100644 --- a/anonstream/templates/nojs_chat_messages.html +++ b/anonstream/templates/nojs_chat_messages.html @@ -144,6 +144,27 @@ font-size: 9pt; cursor: default; } + .chat-date { + transform: rotate(-180deg); + text-align: center; + position: relative; + display: grid; + align-items: center; + margin: 8px 0; + color: #b2b2b3; + cursor: default; + } + .chat-date > hr { + margin: 0; + position: absolute; + width: 100%; + box-sizing: border-box; + } + .chat-date > :not(hr) > time { + padding: 0 1ch; + background-color: #232327; + position: relative; + } {% for token in messages | map(attribute='token') | list | unique %} {% with this_user = users_by_token[token] %} @@ -172,7 +193,7 @@
    {% for message in messages | reverse %} {% with this_user = users_by_token[message.token] %} -
  1. +
  2. {{- ' ' | safe -}} {{ appearance(this_user, insignia_class='chat-message__insignia', name_class='chat-message__name', tag_class='chat-message__name__tag') }} @@ -180,6 +201,15 @@ {{ message.markup }}
  3. {% endwith %} + {% + if loop.nextitem is defined and loop.nextitem.date != message.date + or loop.nextitem is not defined and show_initial_date + %} +
  4. +
    +
    +
  5. + {% endif %} {% endfor %}