(function() {
var container = document.getElementById('letters-container');
var username = container.dataset.username;
var currentUser = container.dataset.currentUser;
var olderUrl = container.dataset.olderUrl;
var labelYou = container.dataset.labelYou;
var labelLoading = container.dataset.labelLoading;
var labelLoadOlder = container.dataset.labelLoadOlder;
// --- WebSocket ---
var wsProtocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
var wsUrl = wsProtocol + location.host + '/ws/letters/' + username + '/';
var socket = null;
function connectSocket() {
socket = new WebSocket(wsUrl);
socket.onopen = function() {};
socket.onclose = function() {
setTimeout(connectSocket, 3000);
};
socket.onmessage = function(e) {
var data = JSON.parse(e.data);
if (data.type === 'letter.new') {
appendLetter(data);
}
};
}
connectSocket();
function appendLetter(data) {
var table = document.getElementById('letters-list');
var tbody = table.querySelector('tbody');
var empty = table.querySelector('.empty-conversation');
if (empty) empty.remove();
var isSelf = data.sender === currentUser;
var tr = document.createElement('tr');
tr.className = 'letter-note ' + (isSelf ? 'from-self' : 'from-other');
tr.dataset.letterId = data.letter_id;
var time = new Date(data.created_at);
var hours = String(time.getHours()).padStart(2, '0');
var mins = String(time.getMinutes()).padStart(2, '0');
var attachmentsHtml = '';
if (data.attachments && data.attachments.length > 0) {
attachmentsHtml = '
';
for (var a = 0; a < data.attachments.length; a++) {
var att = data.attachments[a];
if (att.content_type && att.content_type.startsWith('image/')) {
attachmentsHtml += '

';
} else {
attachmentsHtml += '
' + att.original_name + '';
}
}
attachmentsHtml += '
';
}
tr.innerHTML =
'' +
' ' + hours + ':' + mins + ' ' +
'' + (isSelf ? labelYou : data.sender) + ' ' +
' | ' +
'' + data.content + attachmentsHtml + ' | ';
tbody.appendChild(tr);
var atBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
if (atBottom || isSelf) {
container.scrollTop = container.scrollHeight;
}
if (!isSelf && document.hasFocus()) {
socket.send(JSON.stringify({ type: 'letter.read' }));
}
}
// --- Editor ---
var editorConfig = JSON.parse(document.getElementById('editor-config').textContent);
var editor = new MarkMiku('#markmiku-editor', {
placeholder: editorConfig.placeholder,
emojis: editorConfig.emojis,
attachmentUploadUrl: editorConfig.attachmentUploadUrl,
attachmentRemoveUrl: editorConfig.attachmentRemoveUrl,
csrfToken: editorConfig.csrfToken,
maxAttachments: editorConfig.maxAttachments,
maxAttachmentSize: editorConfig.maxAttachmentSize,
onSend: function(html, attachmentIds) {
socket.send(JSON.stringify({ type: 'letter.send', content: html, attachment_ids: attachmentIds }));
}
});
document.addEventListener('click', function(e) {
if (e.target.classList.contains('markmiku-spoiler')) {
e.target.classList.toggle('revealed');
}
});
// --- Scroll & Load Older ---
container.scrollTop = container.scrollHeight;
var hasMore = container.dataset.hasMore === 'true';
var loading = false;
function loadOlderLetters() {
if (loading || !hasMore) return;
loading = true;
var oldestId = container.dataset.oldestId;
if (!oldestId) { loading = false; return; }
var btn = document.getElementById('load-older-btn');
if (btn) btn.textContent = labelLoading;
var prevHeight = container.scrollHeight;
fetch(olderUrl + '?before=' + oldestId)
.then(function(r) { return r.text(); })
.then(function(html) {
var temp = document.createElement('table');
temp.innerHTML = html;
var wrapper = temp.querySelector('tbody[data-has-more]');
if (!wrapper) { loading = false; return; }
var rows = wrapper.querySelectorAll('tr.letter-note');
if (rows.length > 0) {
var tbody = document.getElementById('letters-list').querySelector('tbody');
var firstChild = tbody.firstChild;
for (var i = 0; i < rows.length; i++) {
tbody.insertBefore(rows[i], firstChild);
}
container.dataset.oldestId = wrapper.dataset.oldestId;
container.scrollTop = container.scrollHeight - prevHeight;
}
hasMore = wrapper.dataset.hasMore === 'true';
var loadOlderDiv = document.getElementById('load-older');
if (!hasMore && loadOlderDiv) {
loadOlderDiv.remove();
} else if (hasMore) {
var btn = document.getElementById('load-older-btn');
if (btn) btn.textContent = labelLoadOlder;
}
loading = false;
})
.catch(function() {
loading = false;
var btn = document.getElementById('load-older-btn');
if (btn) btn.textContent = labelLoadOlder;
});
}
container.addEventListener('scroll', function() {
if (container.scrollTop < 100 && hasMore) {
loadOlderLetters();
}
});
var loadBtn = document.getElementById('load-older-btn');
if (loadBtn) {
loadBtn.addEventListener('click', loadOlderLetters);
}
})();