Merge branch 'dev'
このコミットが含まれているのは:
コミット
4d192392c4
|
@ -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.routes.wrappers import with_user_from, render_template_with_etag
|
||||||
from anonstream.helpers.chat import get_scrollback
|
from anonstream.helpers.chat import get_scrollback
|
||||||
from anonstream.helpers.user import get_default_name
|
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.security import generate_csp
|
||||||
from anonstream.utils.user import concatenate_for_notice
|
from anonstream.utils.user import concatenate_for_notice
|
||||||
|
|
||||||
CONFIG = current_app.config
|
CONFIG = current_app.config
|
||||||
|
MESSAGES = current_app.messages
|
||||||
USERS_BY_TOKEN = current_app.users_by_token
|
USERS_BY_TOKEN = current_app.users_by_token
|
||||||
|
|
||||||
@current_app.route('/stream.html')
|
@current_app.route('/stream.html')
|
||||||
|
@ -47,15 +48,17 @@ async def nojs_info(timestamp, user):
|
||||||
@with_user_from(request)
|
@with_user_from(request)
|
||||||
async def nojs_chat_messages(timestamp, user):
|
async def nojs_chat_messages(timestamp, user):
|
||||||
reading(user)
|
reading(user)
|
||||||
|
messages = get_scrollback(MESSAGES)
|
||||||
return await render_template_with_etag(
|
return await render_template_with_etag(
|
||||||
'nojs_chat_messages.html',
|
'nojs_chat_messages.html',
|
||||||
{'csp': generate_csp()},
|
{'csp': generate_csp()},
|
||||||
refresh=CONFIG['NOJS_REFRESH_MESSAGES'],
|
refresh=CONFIG['NOJS_REFRESH_MESSAGES'],
|
||||||
user=user,
|
user=user,
|
||||||
users_by_token=USERS_BY_TOKEN,
|
users_by_token=USERS_BY_TOKEN,
|
||||||
messages=get_scrollback(current_app.messages),
|
messages=messages,
|
||||||
timeout=CONFIG['NOJS_TIMEOUT_CHAT'],
|
timeout=CONFIG['NOJS_TIMEOUT_CHAT'],
|
||||||
get_default_name=get_default_name,
|
get_default_name=get_default_name,
|
||||||
|
show_initial_date=should_show_initial_date(timestamp, messages),
|
||||||
)
|
)
|
||||||
|
|
||||||
@current_app.route('/chat/messages')
|
@current_app.route('/chat/messages')
|
||||||
|
|
|
@ -189,6 +189,7 @@ const create_chat_message = (object) => {
|
||||||
chat_message.classList.add("chat-message");
|
chat_message.classList.add("chat-message");
|
||||||
chat_message.dataset.seq = object.seq;
|
chat_message.dataset.seq = object.seq;
|
||||||
chat_message.dataset.tokenHash = object.token_hash;
|
chat_message.dataset.tokenHash = object.token_hash;
|
||||||
|
chat_message.dataset.date = object.date;
|
||||||
|
|
||||||
const chat_message_time = document.createElement("time");
|
const chat_message_time = document.createElement("time");
|
||||||
chat_message_time.classList.add("chat-message__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]);
|
result.push(...[chat_user_name, chat_user_tripcode_nbsp, chat_user_tripcode]);
|
||||||
return result;
|
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) => {
|
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);
|
const chat_message = create_chat_message(object);
|
||||||
chat_messages.insertAdjacentElement("beforeend", chat_message);
|
chat_messages.insertAdjacentElement("beforeend", chat_message);
|
||||||
while (chat_messages.children.length > max_chat_scrollback) {
|
const first_chat_message = chat_messages.querySelector(".chat-message");
|
||||||
chat_messages.children[0].remove();
|
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) => {
|
const delete_chat_messages = ({string_seqs, keep=false}) => {
|
||||||
string_seqs = new Set(seqs.map(n => n.toString()));
|
const keep_dates = new Set();
|
||||||
to_delete = [];
|
for (const chat_message of chat_messages.querySelectorAll(".chat-message")) {
|
||||||
for (const chat_message of chat_messages.children) {
|
if (string_seqs.has(chat_message.dataset.seq) === keep)
|
||||||
if (string_seqs.has(chat_message.dataset.seq))
|
keep_dates.add(chat_message.dataset.date);
|
||||||
to_delete.push(chat_message);
|
|
||||||
}
|
}
|
||||||
for (const chat_message of to_delete) {
|
const to_delete = [];
|
||||||
chat_message.remove();
|
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 = {};
|
let users = {};
|
||||||
|
@ -340,7 +383,7 @@ const get_user_name = ({user=null, token_hash}) => {
|
||||||
}
|
}
|
||||||
const update_user_names = (token_hash=null) => {
|
const update_user_names = (token_hash=null) => {
|
||||||
const token_hashes = token_hash === null ? Object.keys(users) : [token_hash];
|
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;
|
const this_token_hash = chat_message.dataset.tokenHash;
|
||||||
if (token_hashes.includes(this_token_hash)) {
|
if (token_hashes.includes(this_token_hash)) {
|
||||||
const user = users[this_token_hash];
|
const user = users[this_token_hash];
|
||||||
|
@ -422,7 +465,7 @@ const update_user_tripcodes = (token_hash=null) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update inner texts
|
// 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 this_token_hash = chat_message.dataset.tokenHash;
|
||||||
const tripcode = users[this_token_hash].tripcode;
|
const tripcode = users[this_token_hash].tripcode;
|
||||||
if (token_hashes.includes(this_token_hash)) {
|
if (token_hashes.includes(this_token_hash)) {
|
||||||
|
@ -632,17 +675,8 @@ const on_websocket_message = async (event) => {
|
||||||
chat_form_submit.disabled = false;
|
chat_form_submit.disabled = false;
|
||||||
|
|
||||||
// remove messages the server isn't acknowledging the existence of
|
// remove messages the server isn't acknowledging the existence of
|
||||||
const seqs = new Set(receipt.messages.map((message) => {return message.seq;}));
|
const string_seqs = new Set(receipt.messages.map(message => message.seq.toString()));
|
||||||
const to_delete = [];
|
delete_chat_messages({string_seqs, keep: true});
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
default_name = receipt.default;
|
default_name = receipt.default;
|
||||||
|
@ -677,7 +711,8 @@ const on_websocket_message = async (event) => {
|
||||||
chat_appearance_form_color.setAttribute("value", user.color);
|
chat_appearance_form_color.setAttribute("value", user.color);
|
||||||
|
|
||||||
// insert new messages
|
// 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);
|
const last_seq = last === null ? null : parseInt(last.dataset.seq);
|
||||||
for (const message of receipt.messages) {
|
for (const message of receipt.messages) {
|
||||||
if (message.seq > last_seq) {
|
if (message.seq > last_seq) {
|
||||||
|
@ -744,7 +779,7 @@ const on_websocket_message = async (event) => {
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
console.log("ws delete", receipt);
|
console.log("ws delete", receipt);
|
||||||
delete_chat_messages(receipt.seqs);
|
delete_chat_messages({string_seqs: new Set(receipt.seqs.map(n => n.toString()))});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "set-users":
|
case "set-users":
|
||||||
|
@ -969,6 +1004,14 @@ chat_messages_unlock.addEventListener("click", (event) => {
|
||||||
chat_messages.scrollTop = chat_messages.scrollTopMax;
|
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 */
|
/* close websocket after prolonged absence of pings */
|
||||||
|
|
||||||
const rotate_websocket = () => {
|
const rotate_websocket = () => {
|
||||||
|
|
|
@ -273,6 +273,29 @@ noscript {
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
cursor: default;
|
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 {
|
#chat__body__users {
|
||||||
background-color: #121214;
|
background-color: #121214;
|
||||||
mask-image: linear-gradient(black calc(100% - 0.625rem), transparent calc(100% - 0.125rem));
|
mask-image: linear-gradient(black calc(100% - 0.625rem), transparent calc(100% - 0.125rem));
|
||||||
|
|
|
@ -144,6 +144,27 @@
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
cursor: default;
|
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 %}
|
{% for token in messages | map(attribute='token') | list | unique %}
|
||||||
{% with this_user = users_by_token[token] %}
|
{% with this_user = users_by_token[token] %}
|
||||||
|
@ -172,7 +193,7 @@
|
||||||
<ol id="chat-messages">
|
<ol id="chat-messages">
|
||||||
{% for message in messages | reverse %}
|
{% for message in messages | reverse %}
|
||||||
{% with this_user = users_by_token[message.token] %}
|
{% with this_user = users_by_token[message.token] %}
|
||||||
<li class="chat-message" data-seq="{{ message.seq }}" data-token-hash="{{ this_user.token_hash }}">
|
<li class="chat-message" data-seq="{{ message.seq }}" data-token-hash="{{ this_user.token_hash }}" data-date="{{ message.date }}">
|
||||||
<time class="chat-message__time" datetime="{{ message.date }}T{{ message.time_seconds }}Z" title="{{ message.date }} {{ message.time_seconds }}">{{ message.time_minutes }}</time>
|
<time class="chat-message__time" datetime="{{ message.date }}T{{ message.time_seconds }}Z" title="{{ message.date }} {{ message.time_seconds }}">{{ message.time_minutes }}</time>
|
||||||
{{- ' ' | safe -}}
|
{{- ' ' | safe -}}
|
||||||
{{ appearance(this_user, insignia_class='chat-message__insignia', name_class='chat-message__name', tag_class='chat-message__name__tag') }}
|
{{ appearance(this_user, insignia_class='chat-message__insignia', name_class='chat-message__name', tag_class='chat-message__name__tag') }}
|
||||||
|
@ -180,6 +201,15 @@
|
||||||
<span class="chat-message__markup">{{ message.markup }}</span>
|
<span class="chat-message__markup">{{ message.markup }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{%
|
||||||
|
if loop.nextitem is defined and loop.nextitem.date != message.date
|
||||||
|
or loop.nextitem is not defined and show_initial_date
|
||||||
|
%}
|
||||||
|
<li class="chat-date" data-date="{{ message.date }}">
|
||||||
|
<hr>
|
||||||
|
<div><time datetime="{{ message.date }}">{{ message.date }}</time></div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
<aside id="timeout-dismiss">
|
<aside id="timeout-dismiss">
|
||||||
|
|
|
@ -6,6 +6,7 @@ import hashlib
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
|
from datetime import datetime
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
from quart import escape
|
from quart import escape
|
||||||
|
@ -30,3 +31,15 @@ def get_approx_linespan(text):
|
||||||
linespan = sum(map(height, text.splitlines()))
|
linespan = sum(map(height, text.splitlines()))
|
||||||
linespan = linespan if linespan > 0 else 1
|
linespan = linespan if linespan > 0 else 1
|
||||||
return linespan
|
return linespan
|
||||||
|
|
||||||
|
def should_show_initial_date(timestamp, messages):
|
||||||
|
try:
|
||||||
|
first_message = next(iter(messages))
|
||||||
|
except StopIteration:
|
||||||
|
return False
|
||||||
|
if any(message['date'] != first_message['date'] for message in messages):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
latest_date = max(map(lambda message: message['date'], messages))
|
||||||
|
date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')
|
||||||
|
return date != latest_date
|
||||||
|
|
読み込み中…
新しいイシューから参照