require('whatwg-fetch'); require('element-closest'); require('classlist.js'); require('url-search-params-polyfill'); import * as killy from './embed'; if (!('remove' in Element.prototype)) { // Firefox <23 Element.prototype.remove = function() { if (this.parentNode) { this.parentNode.removeChild(this); } }; } NodeList.prototype.forEach = Array.prototype.forEach; HTMLCollection.prototype.forEach = Array.prototype.forEach; NodeList.prototype.filter = Array.prototype.filter; HTMLCollection.prototype.filter = Array.prototype.filter; Element.prototype.selectText = function() { let d = document; if (d.body.createTextRange) { let range = d.body.createTextRange(); range.moveToElementText(this); range.select(); } else if (window.getSelection) { let selection = window.getSelection(); let rangeSel = d.createRange(); rangeSel.selectNodeContents(this); selection.removeAllRanges(); selection.addRange(rangeSel); } }; function autosize(el) { let offset = (!window.opera) ? (el.offsetHeight - el.clientHeight) : (el.offsetHeight + parseInt(window.getComputedStyle(el, null).getPropertyValue('border-top-width'))); let resize = function(el) { el.style.height = 'auto'; el.style.height = (el.scrollHeight + offset) + 'px'; }; if (el.addEventListener) { el.addEventListener('input', () => resize(el)); } else if (el.attachEvent) { el.attachEvent('onkeyup', () => resize(el)); } } 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 10 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, до 10Мб', '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 */ 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; } var ws, pageTitle; function initWS() { let url = new URL('/ws/', window.location.href); url.protocol = url.protocol.replace('http', 'ws'); let hash = document.getElementById('body').getAttribute('data-hash'); if (hash) { url += '?hash=' + hash; } else { let content = document.getElementById('content'); if (content) { let pageMID = content.getAttribute('data-mid'); if (pageMID) { url += pageMID; } } } ws = new WebSocket(url); ws.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; } }; ws.onclose = function() { console.log('offline'); ws = false; setTimeout(function() { initWS(); }, 2000); }; ws.onmessage = function(msg) { if (msg.data == ' ') { ws.send(' '); } else { try { var jsonMsg = JSON.parse(msg.data); console.log('data: ' + msg.data); if (jsonMsg.service) { return; } wsIncomingReply(jsonMsg); } catch (err) { console.log(err); } } }; var keepAlive = setInterval(wsSendKeepAlive, 90000); window.addEventListener('beforeunload', () => { clearInterval(keepAlive); ws.close(); }); } function wsSendKeepAlive() { if (ws) { ws.send(' '); } } function wsShutdown() { if (ws) { ws.onclose = function() { }; ws.close(); } } 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 = `
${msg.user.uname}:
${msg.user.uname}
${killy.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(); }); killy.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 = 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; } } /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ function postformListener(formEl, ev) { if (ev.ctrlKey && (ev.keyCode == 10 || ev.keyCode == 13)) { let form = formEl.closest('form'); if (!form.onsubmit || form.onsubmit()) { form.querySelector('input[type="submit"]').click(); } } } function closeDialogListener(ev) { ev = ev || window.event; if (ev.keyCode == 27) { closeDialog(); } } function newMessage(evt) { document.querySelectorAll('#newmessage .dialogtxt').forEach(t => { t.remove(); }); if (document.querySelector('#newmessage textarea').value.length == 0 && document.querySelector('#newmessage .img').value.length == 0 && !document.querySelector('#newmessage input[type="file"]')) { document.querySelector('#newmessage').insertAdjacentHTML('afterbegin', `

${i18n('postForm.pleaseInputMessageText')}

`); evt.preventDefault(); } } function handleErrors(response) { if (!response.ok) { throw Error(response.statusText); } return response; } function showCommentForm(mid, rid) { let reply = document.getElementById(rid); let formTarget = reply.querySelector('div.msg-cont .msg-comment-target'); if (formTarget) { let formHtml = `
${evilIcon('ei-camera')}
`; formTarget.insertAdjacentHTML('afterend', formHtml); formTarget.remove(); let form = reply.querySelector('form'); let submitButton = form.querySelector('input[type="submit"]'); let attachButton = form.querySelector('.msg-comment .attach-photo'); attachButton.addEventListener('click', e => attachCommentPhoto(e.target)); let textarea = form.querySelector('.msg-comment textarea'); textarea.addEventListener('keypress', e => postformListener(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.href = new URL(`${mid}#${result.newMessage.rid}`, window.location.href); } else { alert(result.text); } window.location.reload(true); }); } }).catch(error => { alert(error.message); }); e.preventDefault(); }); } 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; } 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 attachMessagePhoto(div) { var f = div.closest('form'), finput = f.querySelector('input[type="file"]'); if (!finput) { var inp = attachInput(); inp.style.float = 'left'; inp.style.width = 0; inp.style.height = 0; inp.addEventListener('change', function() { div.textContent = i18n('postForm.upload') + ' (✓)'; }); f.appendChild(inp); inp.click(); } else { finput.remove(); div.textContent = i18n('postForm.upload'); } } function showMessageLinksDialog(mid, rid) { let hlink = window.location.protocol + '//juick.com/' + mid; let mlink = '#' + mid; if (rid > 0) { hlink += '#' + rid; mlink += '/' + rid; } let hlinkenc = encodeURIComponent(hlink); let html = `
${i18n('shareDialog.linkToMessage')}:
${hlink}
${i18n('shareDialog.messageNumber')}:
${mlink}
${i18n('shareDialog.share')}:
`; openDialog(html); } 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 openPostDialog() { let newmessageTemplate = `
${i18n('postForm.or')} ${i18n('postForm.upload')}

`; return openDialog(newmessageTemplate); } function openDialog(html, image) { var dialogHtml = `
${evilIcon('ei-close')}
${html}
`; let body = document.querySelector('body'); body.classList.add('dialog-opened'); body.insertAdjacentHTML('afterbegin', dialogHtml); if (image) { let header = 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() { let draft = document.querySelector('#newmessage textarea'); if (draft) { window.draft = draft.value; } document.querySelector('body').classList.remove('dialog-opened'); document.querySelector('#dialogb').remove(); document.querySelector('#dialogt').remove(); } function openSocialWindow(a) { var w = window.open(a.href, 'juickshare', 'width=640,height=400'); if (window.focus) { w.focus(); } return false; } function checkUsername() { var uname = document.querySelector('#username').textContent, style = document.querySelector('#username').style; fetch('/api/users?uname=' + uname) .then(handleErrors) .then(function() { style.background = '#FFCCCC'; }) .catch(function() { style.background = '#CCFFCC'; }); } /******************************************************************************/ function openDialogLogin() { let html = `

${i18n('loginDialog.pleaseIntroduceYourself')}:

${evilIcon('ei-envelope')}${i18n('loginDialog.email')} ${evilIcon('ei-sc-facebook')}${i18n('loginDialog.facebook')} ${evilIcon('ei-sc-vk')}${i18n('loginDialog.vk')}

${i18n('loginDialog.registeredAlready')}



`; openDialog(html); return false; } /******************************************************************************/ 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(true); } else { alert('Something went wrong :('); } }) .catch(error => { alert(error.message); }); return false; } /******************************************************************************/ function setPopular(e, mid, popular) { fetch('/api/messages/set_popular?mid=' + mid + '&popular=' + popular + '&hash=' + document.getElementById('body').getAttribute('data-hash'), { credentials: 'same-origin' }) .then(handleErrors) .then(function() { e.closest('article').append(resultMessage('OK!')); }); 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; } function fetchUserUri(dataUri, callback) { let data = new FormData(); data.append('uri', dataUri); fetch('/u/', { method: 'POST', body: data }).then(handleErrors) .then(response => { return response.json(); }) .then(json => { callback(json); }); } /******************************************************************************/ function ready(fn) { if (document.readyState != 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } } ready(function() { document.querySelectorAll('textarea').forEach((ta) => { autosize(ta); }); var insertPMButtons = function(e) { e.target.classList.add('narrowpm'); e.target.parentNode.insertAdjacentHTML('afterend', ''); e.target.removeEventListener('click', insertPMButtons); e.preventDefault(); }; document.querySelectorAll('textarea.replypm').forEach(function(e) { e.addEventListener('click', insertPMButtons); e.addEventListener('keypress', function(e) { postformListener(e.target, e); }); }); document.querySelectorAll('#postmsg textarea').forEach(function(e) { e.addEventListener('keypress', function(e) { postformListener(e.target, e); }); }); var content = document.getElementById('content'); if (content) { var pageMID = content.getAttribute('data-mid'); if (pageMID > 0) { 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 = opMessage.querySelector('textarea.reply'); if (replyTextarea) { replyTextarea.addEventListener('focus', e => showCommentForm(pageMID, 0)); replyTextarea.addEventListener('keypress', e => postformListener(e.target, e)); if (!window.location.hash) { replyTextarea.focus(); } } } } } var postmsg = document.getElementById('postmsg'); if (postmsg) { 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 = new URL(`/m/${result.newMessage.mid}`, window.location.href); } else { alert(result.text); } }); } else { alert('Something went wrong :('); } }).catch(error => { alert(error.message); }); e.preventDefault(); }); } 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 = new URL('/pm/sent', window.location.href); } else { alert('Something went wrong :('); } }); } else { alert('Something went wrong :('); } }).catch(error => { alert(error.message); }); e.preventDefault(); }); }); document.querySelectorAll('.msg-menu').forEach(function(el) { el.addEventListener('click', function(e) { var reply = e.target.closest('li'); var rid = reply ? parseInt(reply.id) : 0; var message = e.target.closest('section'); var mid = message.getAttribute('data-mid') || e.target.closest('article').getAttribute('data-mid'); showMessageLinksDialog(mid, rid); e.preventDefault(); }); }); document.querySelectorAll('.l .a-privacy').forEach(function(e) { e.addEventListener('click', function(e) { setPrivacy( e.target, e.target.closest('article').getAttribute('data-mid')); e.preventDefault(); }); }); document.querySelectorAll('.ir a[data-fname], .msg-media a[data-fname]').forEach(function(el) { el.addEventListener('click', function(e) { let fname = e.target.closest('[data-fname]').getAttribute('data-fname'); if (!showPhotoDialog(fname)) { e.preventDefault(); } }); }); document.querySelectorAll('.social a').forEach(function(e) { e.addEventListener('click', function(e) { openSocialWindow(e.target); e.preventDefault(); }); }); var username = document.getElementById('username'); if (username) { username.addEventListener('blur', function() { checkUsername(); }); } document.querySelectorAll('.l .a-like').forEach(function(e) { e.addEventListener('click', function(e) { likeMessage( e.target, e.target.closest('article').getAttribute('data-mid')); e.preventDefault(); }); }); document.querySelectorAll('.l .a-sub').forEach(function(e) { e.addEventListener('click', function(e) { subscribeMessage( e.target, document.getElementById('content').getAttribute('data-mid')); e.preventDefault(); }); }); document.querySelectorAll('.a-login').forEach(function(el) { el.addEventListener('click', function(e) { openDialogLogin(); e.preventDefault(); }); }); var unfoldall = document.getElementById('unfoldall'); if (unfoldall) { unfoldall.addEventListener('click', function(e) { document.querySelectorAll('#replies>li').forEach(function(e) { e.style.display = 'block'; }); document.querySelectorAll('#replies .msg-comments').forEach(function(e) { e.style.display = 'none'; }); e.preventDefault(); }); } document.querySelectorAll('article').forEach(function(article) { if (Array.prototype.some.call( article.querySelectorAll('.msg-tags a'), function(a) { return a.textContent === 'NSFW'; } )) { article.classList.add('nsfw'); } }); document.querySelectorAll('[data-uri]').forEach(el => { let dataUri = el.getAttribute('data-uri'); if (dataUri) { fetchUserUri(dataUri, user => { let header = el.closest('.msg-header'); header.querySelectorAll('.a-username').forEach(a => { a.setAttribute('href', user.uri); let img = a.querySelector('img'); if (img && user.avatar) { img.setAttribute('src', user.avatar); img.setAttribute('alt', user.uname); } let textNode = a.childNodes[0]; if (textNode.nodeType === Node.TEXT_NODE && textNode.nodeValue.trim().length > 0) { let uname = document.createTextNode(user.uname); a.replaceChild(uname, a.firstChild); } }); }); } }); initWS(); window.addEventListener('pagehide', wsShutdown); killy.embedAll(); var elSelector = 'header', elClassHidden = 'header--hidden', elClassBackground = 'header--background', throttleTimeout = 500, element = document.querySelector(elSelector); if (element) { var dHeight = 0, wHeight = 0, wScrollCurrent = 0, wScrollBefore = 0, wScrollDiff = 0, throttle = function(delay, fn) { var last, deferTimer; return function() { var context = this, args = arguments, now = +new Date; if (last && now < last + delay) { clearTimeout(deferTimer); deferTimer = setTimeout( function() { last = now; fn.apply(context, args); }, delay); } else { last = now; fn.apply(context, args); } }; }; window.addEventListener('scroll', throttle(throttleTimeout, function() { dHeight = document.body.offsetHeight; wHeight = window.innerHeight; wScrollCurrent = window.pageYOffset; wScrollDiff = wScrollBefore - wScrollCurrent; if (wScrollCurrent <= 0) { // scrolled to the very top; element sticks to the top element.classList.remove(elClassHidden); element.classList.remove(elClassBackground); } else if (wScrollDiff > 0 && element.classList.contains(elClassHidden)) { // scrolled up; element slides in element.classList.remove(elClassHidden); element.classList.add(elClassBackground); } else if (wScrollDiff < 0) { // scrolled down if (wScrollCurrent + wHeight >= dHeight && element.classList.contains(elClassHidden)) { // scrolled to the very bottom; element slides in element.classList.remove(elClassHidden); element.classList.add(elClassBackground); } else { // scrolled down; element slides out element.classList.add(elClassHidden); } } wScrollBefore = wScrollCurrent; })); } });