import elementClosest from 'element-closest';
import 'formdata-polyfill';
import 'classlist.js';
import 'whatwg-fetch';
import 'core-js/stable';
import { embedLinksToX, embedAll, format } from './embed';
import renderIcons from './icon';
import svg4everybody from 'svg4everybody';
/**
* Autosize textarea
*
* @param {HTMLTextAreaElement} el textarea element
*/
function autosize(el) {
let offset = el.offsetHeight - el.clientHeight;
el.addEventListener('input', (ev) => {
const textarea = /** @type {HTMLTextAreaElement} */ (ev.target);
textarea.style.height = 'auto';
textarea.style.height = (textarea.scrollHeight + offset) + 'px';
});
}
/**
* Display an icon from the evil-icons set
*
* @param {string} name Icon name from the iconset
* @returns {string} HTML markup for the selected icon
*/
function evilIcon(name) {
return `
`;
}
/* eslint-disable only-ascii/only-ascii */
const translations = {
'en': {
'message.inReplyTo': 'in reply to',
'message.reply': 'Reply',
'message.likeThisMessage?': 'Recommend this message?',
'postForm.pleaseInputMessageText': 'Please input message text',
'postForm.upload': 'Upload',
'postForm.newMessage': 'New message...',
'postForm.imageLink': 'Link to image',
'postForm.imageFormats': 'JPG/PNG, up to 100 MB',
'postForm.or': 'or',
'postForm.tags': 'Tags (space separated)',
'postForm.submit': 'Send',
'comment.writeComment': 'Write a comment...',
'shareDialog.linkToMessage': 'Link to message',
'shareDialog.messageNumber': 'Message number',
'shareDialog.share': 'Share',
'loginDialog.pleaseIntroduceYourself': 'Please introduce yourself',
'loginDialog.registeredAlready': 'Registered already?',
'loginDialog.username': 'Username',
'loginDialog.password': 'Password',
'loginDialog.facebook': 'Login with Facebook',
'loginDialog.vk': 'Login with VK',
'loginDialog.email': 'Registration',
'error.error': 'Error'
},
'ru': {
'message.inReplyTo': 'в ответ на',
'message.reply': 'Ответить',
'message.likeThisMessage?': 'Рекомендовать это сообщение?',
'postForm.pleaseInputMessageText': 'Пожалуйста, введите текст сообщения',
'postForm.upload': 'загрузить',
'postForm.newMessage': 'Новое сообщение...',
'postForm.imageLink': 'Ссылка на изображение',
'postForm.imageFormats': 'JPG/PNG, до 100 Мб',
'postForm.or': 'или',
'postForm.tags': 'Теги (через пробел)',
'postForm.submit': 'Отправить',
'comment.writeComment': 'Написать комментарий...',
'shareDialog.linkToMessage': 'Ссылка на сообщение',
'shareDialog.messageNumber': 'Номер сообщения',
'shareDialog.share': 'Поделиться',
'loginDialog.pleaseIntroduceYourself': 'Пожалуйста, представьтесь',
'loginDialog.registeredAlready': 'Уже зарегистрированы?',
'loginDialog.username': 'Имя пользователя',
'loginDialog.password': 'Пароль',
'loginDialog.facebook': 'Войти через Facebook',
'loginDialog.vk': 'Войти через ВКонтакте',
'loginDialog.email': 'Регистрация',
'error.error': 'Ошибка'
}
};
/* eslint-enable only-ascii/only-ascii */
/**
* Detect window language
*
* @returns {string} Detected language
*/
function getLang() {
return (window.navigator.languages && window.navigator.languages[0])
|| window.navigator['userLanguage']
|| window.navigator.language;
}
function i18n(key = '', lang = undefined) {
const fallbackLang = 'ru';
lang = lang || getLang().split('-')[0];
return (translations[lang] && translations[lang][key])
|| translations[fallbackLang][key]
|| key;
}
/** @type { EventSource } */
var es;
var pageTitle;
function initES() {
if (!('EventSource' in window)) {
return;
}
let url = '/api/events';
let hash = document.getElementById('body').getAttribute('data-hash');
if (hash) {
url += '?hash=' + hash;
}
es = new EventSource(url);
es.onopen = function() {
console.log('online');
if (!document.querySelector('#wsthread')) {
var d = document.createElement('div');
d.id = 'wsthread';
d.addEventListener('click', nextReply);
document.querySelector('body').appendChild(d);
pageTitle = document.title;
}
};
es.addEventListener('msg', msg => {
try {
var jsonMsg = JSON.parse(msg.data);
console.log('data: ' + msg.data);
if (jsonMsg.service) {
return;
}
wsIncomingReply(jsonMsg);
} catch (err) {
console.log(err);
}
});
}
function wsIncomingReply(msg) {
let content = document.getElementById('content');
if (!content) { return; }
let pageMID = content.getAttribute('data-mid');
if (!pageMID || pageMID != msg.mid) { return; }
let msgNum = '/' + msg.rid;
if (msg.replyto > 0) {
msgNum += ` ${i18n('message.inReplyTo')} /${msg.replyto}`;
}
let photoDiv = (msg.attach == null) ? '' : `
`;
let msgContHtml = `
${format(msg.body, msg.mid, false)}
${photoDiv}
`;
let li = document.createElement('li');
li.setAttribute('class', 'msg reply-new');
li.setAttribute('id', msg.rid);
li.innerHTML = msgContHtml;
li.addEventListener('click', newReply);
li.addEventListener('mouseover', newReply);
li.querySelector('a.msg-reply-link').addEventListener('click', function(e) {
showCommentForm(msg.mid, msg.rid);
e.preventDefault();
});
embedLinksToX(li.querySelector('.msg-cont'), '.msg-links', '.msg-txt a');
document.getElementById('replies').appendChild(li);
updateRepliesCounter();
}
function newReply(e) {
var li = e.target;
li.classList.remove('reply-new');
li.removeEventListener('click', e);
li.removeEventListener('mouseover', e);
updateRepliesCounter();
}
function nextReply() {
var li = document.querySelector('#replies>li.reply-new');
if (li) {
li.classList.remove('reply-new');
li.removeEventListener('click', this);
li.children[0].scrollIntoView();
updateRepliesCounter();
}
}
function updateRepliesCounter() {
var replies = Array.from(document.querySelectorAll('#replies>li.reply-new')).length;
var wsthread = document.getElementById('wsthread');
if (replies) {
wsthread.textContent = `${replies}`;
wsthread.style.display = 'block';
document.title = '[' + replies + '] ' + pageTitle;
} else {
wsthread.style.display = 'none';
document.title = pageTitle;
}
}
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
/**
* Submit form on Ctrl+Enter
*
* @param {HTMLElement} formEl Target form element
* @param {KeyboardEvent} ev Keyboard event
*/
function postformListener(formEl, ev) {
if (ev.ctrlKey && (ev.keyCode == 10 || ev.keyCode == 13)) {
let form = formEl.closest('form');
if (!form.onsubmit || form.submit()) {
/** @type {HTMLInputElement} */ (form.querySelector('input[type="submit"]')).click();
}
}
}
/**
* Close dialog on Esc
*
* @param {KeyboardEvent} ev Keyboard event
*/
function closeDialogListener(ev) {
ev = ev || /** @type {KeyboardEvent} */ (window.event);
if (ev.keyCode == 27) {
closeDialog();
}
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
/**
*
* @param {number} mid message id
* @param {string} rid reply id
*/
function showCommentForm(mid, rid) {
let reply = /** @type { HTMLElement } */ (document.getElementById(rid));
let formTarget = reply.querySelector('div.msg-cont .msg-comment-target');
if (formTarget) {
let formHtml = `
`;
formTarget.insertAdjacentHTML('afterend', formHtml);
formTarget.remove();
let form = /** @type {HTMLFormElement} */ (reply.querySelector('form'));
let submitButton = /** @type {HTMLInputElement} */ (form.querySelector('input[type="submit"]'));
let attachButton = /** @type {HTMLInputElement} */ (form.querySelector('.msg-comment .attach-photo'));
attachButton.addEventListener('click', e => attachCommentPhoto(/** @type {HTMLDivElement} */(e.target)));
let textarea = /** @type {HTMLTextAreaElement} */ (form.querySelector('.msg-comment textarea'));
textarea.addEventListener('keypress', e => postformListener(/** @type {HTMLElement} */(e.target), e));
autosize(textarea);
let validateMessage = () => {
let len = textarea.value.length;
if (len > 4096) { return 'Message is too long'; }
return '';
};
form.addEventListener('submit', e => {
let validationResult = validateMessage();
if (validationResult) {
e.preventDefault();
alert(validationResult);
return false;
}
submitButton.disabled = true;
let formData = new FormData(form);
fetch('/api/comment' + '?hash=' + document.getElementById('body').getAttribute('data-hash'), {
method: 'POST',
body: formData,
credentials: 'omit'
}).then(handleErrors)
.then(response => {
if (response.ok) {
response.json().then(result => {
if (result.newMessage) {
window.location.hash = `#${result.newMessage.rid}`;
} else {
alert(result.text);
}
window.location.reload();
});
}
}).catch(error => {
alert(error.message);
});
e.preventDefault();
});
}
/** @type {HTMLTextAreaElement} */ (reply.querySelector('.msg-comment textarea')).focus();
}
function attachInput() {
let inp = document.createElement('input');
inp.setAttribute('type', 'file');
inp.setAttribute('name', 'attach');
inp.setAttribute('accept', 'image/jpeg,image/png');
inp.style.visibility = 'hidden';
return inp;
}
/**
* "Attach" button
*
* @param {HTMLDivElement} div element attach to
*/
function attachCommentPhoto(div) {
let input = div.querySelector('input');
if (input) {
input.remove();
div.classList.remove('attach-photo-active');
} else {
let newInput = attachInput();
newInput.addEventListener('change', function() {
div.classList.add('attach-photo-active');
});
newInput.click();
div.appendChild(newInput);
}
}
function showPhotoDialog(fname) {
let width = window.innerWidth;
let height = window.innerHeight;
let minDimension = (width < height) ? width : height;
if (minDimension < 640) {
return true; // no dialog, open the link
} else if (minDimension < 1280) {
openDialog(``, true);
return false;
} else {
openDialog(``, true);
return false;
}
}
function openDialog(html, image) {
var dialogHtml = `
`;
let body = /** @type {HTMLElement} */ (document.querySelector('body'));
body.classList.add('dialog-opened');
body.insertAdjacentHTML('afterbegin', dialogHtml);
if (image) {
let header = /** @type {HTMLElement} */ (document.querySelector('#dialog_header'));
header.classList.add('header_image');
}
document.addEventListener('keydown', closeDialogListener);
document.querySelector('#dialogb').addEventListener('click', closeDialog);
document.querySelector('#dialogc').addEventListener('click', closeDialog);
}
function closeDialog() {
document.querySelector('body').classList.remove('dialog-opened');
document.querySelector('#dialogb').remove();
document.querySelector('#dialogt').remove();
}
function checkUsername() {
var uname = document.querySelector('#username').textContent,
style = /** @type {HTMLElement} */ (document.querySelector('#username')).style;
fetch('/api/users?uname=' + uname)
.then(handleErrors)
.then(function() {
style.background = '#FFCCCC';
})
.catch(function() {
style.background = '#CCFFCC';
});
}
/******************************************************************************/
function resultMessage(str) {
var result = document.createElement('p');
result.textContent = str;
return result;
}
function likeMessage(e, mid) {
if (confirm(i18n('message.likeThisMessage?'))) {
fetch('/api/like?mid=' + mid
+ '&hash=' + document.getElementById('body').getAttribute('data-hash'), {
method: 'POST',
credentials: 'omit'
})
.then(handleErrors)
.then(function(response) {
if (response.ok) {
e.closest('article').appendChild(resultMessage('OK!'));
}
})
.catch(function() {
e.closest('article').appendChild(resultMessage(i18n('error.error')));
});
}
return false;
}
function subscribeMessage(e, mid) {
fetch('/api/subscribe?mid=' + mid
+ '&hash=' + document.getElementById('body').getAttribute('data-hash'), {
method: 'POST',
credentials: 'omit'
})
.then(handleErrors)
.then(function(response) {
if (response.ok) {
window.location.reload();
} else {
alert('Something went wrong :(');
}
})
.catch(error => {
alert(error.message);
});
return false;
}
/******************************************************************************/
function setPrivacy(e, mid) {
fetch('/api/messages/set_privacy?mid=' + mid
+ '&hash=' + document.getElementById('body').getAttribute('data-hash'), {
credentials: 'same-origin'
})
.then(handleErrors)
.then(function() {
e.closest('article').append(resultMessage('OK!'));
});
return false;
}
function getTags() {
fetch('/api/tags?hash=' + document.getElementById('body').getAttribute('data-hash'), {
credentials: 'omit'
})
.then(handleErrors)
.then(response => {
return response.json();
})
.then(json => {
let tags = json.map(t => t.tag);
let input = document.getElementById('tags_input');
});
return false;
}
function addTag(tag) {
document.forms['postmsg'].body.value = '*' + tag + ' ' + document.forms['postmsg'].body.value;
return false;
}
var users = {};
/**
* Fetch ActivityPub user profile info
*
* @param {string} dataUri user URI
* @param {*} callback callback
*/
function fetchUserUri(dataUri, callback) {
if (users[dataUri]) {
callback(users[dataUri]);
} else {
fetch(dataUri, {
headers: {
'Accept': 'application/ld+json'
}
}).then(handleErrors)
.then(response => {
return response.json();
})
.then(json => {
users[dataUri] = json;
callback(json);
})
.catch(e => {
callback({ preferredUsername: dataUri });
});
}
}
/******************************************************************************/
function ready(fn) {
if (document.readyState != 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(() => {
elementClosest(window);
Array.from(document.querySelectorAll('textarea')).forEach((ta) => {
autosize(ta);
});
svg4everybody();
renderIcons();
var insertPMButtons = function(e) {
e.target.classList.add('narrowpm');
e.target.parentNode.insertAdjacentHTML('afterend', '');
e.target.removeEventListener('click', insertPMButtons);
e.preventDefault();
};
/** @type {HTMLTextAreaElement[]} */ (Array.from(document.querySelectorAll('textarea.replypm'))).forEach(function(e) {
e.addEventListener('click', insertPMButtons);
e.addEventListener('keypress', function(e) {
postformListener(/** @type {HTMLElement} */(e.target), e);
});
});
/** @type {HTMLTextAreaElement[]} */ (Array.from(document.querySelectorAll('#postmsg textarea'))).forEach(function(e) {
e.addEventListener('keypress', function(e) {
postformListener(/** @type {HTMLElement} */(e.target), e);
});
});
var content = document.getElementById('content');
if (content) {
var pageMID = +content.getAttribute('data-mid');
if (pageMID > 0) {
Array.from(document.querySelectorAll('li.msg')).forEach(li => {
let showReplyFormBtn = li.querySelector('.a-thread-comment');
if (showReplyFormBtn) {
showReplyFormBtn.addEventListener('click', function(e) {
showCommentForm(pageMID, li.id);
e.preventDefault();
});
}
});
let opMessage = document.querySelector('.msgthread');
if (opMessage) {
let replyTextarea = /** @type {HTMLTextAreaElement} */ (opMessage.querySelector('textarea.reply'));
if (replyTextarea) {
replyTextarea.addEventListener('focus', e => showCommentForm(pageMID, 0));
replyTextarea.addEventListener('keypress', e => postformListener(/** @type {HTMLElement} */(e.target), e));
if (!window.location.hash) {
replyTextarea.focus();
}
}
}
}
}
var postmsg = /** @type {HTMLFormElement} */(document.getElementById('postmsg'));
if (postmsg) {
Array.from(document.querySelectorAll('a')).filter(t => t.href.indexOf('?') >= 0).forEach(t => {
t.addEventListener('click', e => {
let params = new URLSearchParams(t.href.slice(t.href.indexOf('?') + 1));
if (params.has('tag')) {
addTag(params.get('tag'));
e.preventDefault();
}
});
});
postmsg.addEventListener('submit', e => {
let formData = new FormData(postmsg);
fetch('/api/post' + '?hash=' + document.getElementById('body').getAttribute('data-hash'), {
method: 'POST',
body: formData,
credentials: 'omit'
}).then(handleErrors)
.then(response => {
if (response.ok) {
response.json().then(result => {
if (result.newMessage) {
window.location.href = new URL(`/m/${result.newMessage.mid}`, window.location.href).href;
} else {
alert(result.text);
}
});
} else {
alert('Something went wrong :(');
}
}).catch(error => {
alert(error.message);
});
e.preventDefault();
});
}
/** @type {HTMLFormElement[]} */ (Array.from(document.querySelectorAll('.pmmsg'))).forEach(pmmsg => {
pmmsg.addEventListener('submit', e => {
let formData = new FormData(pmmsg);
fetch('/api/pm' + '?hash=' + document.getElementById('body').getAttribute('data-hash'), {
method: 'POST',
body: formData,
credentials: 'omit'
}).then(handleErrors)
.then(response => {
if (response.ok) {
response.json().then(result => {
if (result.to) {
window.location.href = new URL('/pm/sent', window.location.href).href;
} else {
alert('Something went wrong :(');
}
});
} else {
alert('Something went wrong :(');
}
}).catch(error => {
alert(error.message);
});
e.preventDefault();
});
});
Array.from(document.querySelectorAll('.l .a-privacy')).forEach(function(e) {
e.addEventListener('click', function(e) {
const article = /** @type {HTMLElement} */((e.target).closest('article'));
setPrivacy(
e.target,
article.getAttribute('data-mid'));
e.preventDefault();
});
});
Array.from(document.querySelectorAll('.ir a[data-fname], .msg-media a[data-fname]')).forEach(function(el) {
el.addEventListener('click', function(e) {
let fname = /** @type {HTMLElement} */ (e.target).closest('[data-fname]').getAttribute('data-fname');
if (!showPhotoDialog(fname)) {
e.preventDefault();
}
});
});
var username = document.getElementById('username');
if (username) {
username.addEventListener('blur', function() {
checkUsername();
});
}
Array.from(document.querySelectorAll('.l .a-like')).forEach(function(e) {
e.addEventListener('click', function(e) {
likeMessage(
e.target,
/** @type {HTMLElement} */(e.target).closest('article').getAttribute('data-mid'));
e.preventDefault();
});
});
Array.from(document.querySelectorAll('.l .a-sub')).forEach(function(e) {
e.addEventListener('click', function(e) {
subscribeMessage(
e.target,
document.getElementById('content').getAttribute('data-mid'));
e.preventDefault();
});
});
var unfoldall = document.getElementById('unfoldall');
if (unfoldall) {
unfoldall.addEventListener('click', function(e) {
/** @type {HTMLElement[]} */ (Array.from(document.querySelectorAll('#replies>li'))).forEach(function(e) {
e.style.display = 'block';
});
/** @type {HTMLElement[]} */ (Array.from(document.querySelectorAll('#replies .msg-comments'))).forEach(function(e) {
e.style.display = 'none';
});
e.preventDefault();
});
}
Array.from(document.querySelectorAll('article')).forEach(function(article) {
if (Array.prototype.some.call(
Array.from(article.querySelectorAll('.msg-tags a')),
function(a) {
return a.textContent === 'NSFW';
}
)) {
article.classList.add('nsfw');
}
});
Array.from(document.querySelectorAll('[data-uri]')).forEach(el => {
let dataUri = el.getAttribute('data-uri') || '';
if (dataUri) {
setTimeout(() => fetchUserUri(dataUri, user => {
let header = el.closest('.msg-header');
if (header) {
Array.from(header.querySelectorAll('.a-username')).forEach(a => {
a.setAttribute('href', dataUri);
let img = a.querySelector('img');
if (img && user.icon) {
img.setAttribute('src', user.icon.url);
img.setAttribute('alt', user.preferredUsername);
}
let textNode = a.childNodes[0];
if (textNode && textNode.nodeType === Node.TEXT_NODE && textNode.nodeValue && textNode.nodeValue.trim().length > 0) {
let uname = document.createTextNode(user.preferredUsername || dataUri);
if (a.firstChild) {
a.replaceChild(uname, a.firstChild);
}
}
});
}
}), 100);
}
});
Array.from(document.querySelectorAll('[data-user-uri]')).forEach(el => {
let dataUri = el.getAttribute('href') || '';
if (dataUri) {
setTimeout(() => fetchUserUri(dataUri, user => {
let textNode = el.childNodes[0];
if (textNode && textNode.nodeType === Node.TEXT_NODE && textNode.nodeValue && textNode.nodeValue.trim().length > 0) {
let uname = document.createTextNode(`@${user.preferredUsername}`);
if (el.firstChild) {
el.replaceChild(uname, el.firstChild);
}
}
}), 100);
}
});
let location = window.location.href;
/** @type {HTMLLinkElement[]} */ (Array.from(document.querySelectorAll('#header_wrapper a'))).forEach(el => {
if (el.href === location) {
el.classList.add('active');
el.setAttribute('disabled', 'disabled');
}
});
initES();
addEventListener('beforeunload', () => {
if (es) {
es.close();
}
});
embedAll();
});