From 13237626f3956d93a91a94bee6fee6aa86134a06 Mon Sep 17 00:00:00 2001
From: Vitaly Takmazov
Date: Tue, 27 Mar 2018 22:13:16 +0300
Subject: www: spring boot autoconfigured static resources
+ cache busting using Spring
---
juick-www/src/main/assets/embed.js | 336 ++++++++
juick-www/src/main/assets/logo.png | Bin 0 -> 2447 bytes
juick-www/src/main/assets/logo@2x.png | Bin 0 -> 4822 bytes
juick-www/src/main/assets/scripts.js | 736 +++++++++++++++++
juick-www/src/main/assets/style.css | 907 +++++++++++++++++++++
juick-www/src/main/java/com/juick/www/WebApp.java | 25 +-
.../juick/www/configuration/WebSecurityConfig.java | 3 +-
.../www/configuration/WwwAppConfiguration.java | 22 -
juick-www/src/main/js/killy/index.js | 336 --------
juick-www/src/main/js/killy/package.json | 6 -
juick-www/src/main/resources/favicon.png | Bin 244 -> 0 bytes
juick-www/src/main/resources/logo.png | Bin 1184 -> 0 bytes
juick-www/src/main/resources/static/favicon.png | Bin 0 -> 244 bytes
juick-www/src/main/resources/static/logo.png | Bin 0 -> 1184 bytes
juick-www/src/main/resources/static/tagscloud.png | Bin 0 -> 42316 bytes
juick-www/src/main/resources/tagscloud.png | Bin 42316 -> 0 bytes
.../main/resources/templates/layouts/content.html | 4 +-
juick-www/src/main/static/logo.png | Bin 2447 -> 0 bytes
juick-www/src/main/static/logo@2x.png | Bin 4822 -> 0 bytes
juick-www/src/main/static/scripts.js | 736 -----------------
juick-www/src/main/static/style.css | 907 ---------------------
21 files changed, 1987 insertions(+), 2031 deletions(-)
create mode 100644 juick-www/src/main/assets/embed.js
create mode 100644 juick-www/src/main/assets/logo.png
create mode 100644 juick-www/src/main/assets/logo@2x.png
create mode 100644 juick-www/src/main/assets/scripts.js
create mode 100644 juick-www/src/main/assets/style.css
delete mode 100644 juick-www/src/main/js/killy/index.js
delete mode 100644 juick-www/src/main/js/killy/package.json
delete mode 100644 juick-www/src/main/resources/favicon.png
delete mode 100644 juick-www/src/main/resources/logo.png
create mode 100644 juick-www/src/main/resources/static/favicon.png
create mode 100644 juick-www/src/main/resources/static/logo.png
create mode 100644 juick-www/src/main/resources/static/tagscloud.png
delete mode 100644 juick-www/src/main/resources/tagscloud.png
delete mode 100644 juick-www/src/main/static/logo.png
delete mode 100644 juick-www/src/main/static/logo@2x.png
delete mode 100644 juick-www/src/main/static/scripts.js
delete mode 100644 juick-www/src/main/static/style.css
(limited to 'juick-www/src/main')
diff --git a/juick-www/src/main/assets/embed.js b/juick-www/src/main/assets/embed.js
new file mode 100644
index 00000000..25c37142
--- /dev/null
+++ b/juick-www/src/main/assets/embed.js
@@ -0,0 +1,336 @@
+
+function insertAfter(newNode, referenceNode) {
+ referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
+}
+
+function setContent(containerNode, ...newNodes) {
+ removeAllFrom(containerNode);
+ newNodes.forEach(n => containerNode.appendChild(n));
+ return containerNode;
+}
+
+function removeAllFrom(fromNode) {
+ for (let c; c = fromNode.lastChild; ) { fromNode.removeChild(c); }
+}
+
+function htmlEscape(html) {
+ let textarea = document.createElement('textarea');
+ textarea.textContent = html;
+ return textarea.innerHTML;
+}
+
+// rules :: [{pr: number, re: RegExp, with: string}]
+// rules :: [{pr: number, re: RegExp, with: Function}]
+// rules :: [{pr: number, re: RegExp, brackets: true, with: [string, string]}]
+// rules :: [{pr: number, re: RegExp, brackets: true, with: [string, string, Function]}]
+function formatText(txt, rules) {
+ let idCounter = 0;
+ function nextId() { return idCounter++; }
+ function ft(txt, rules) {
+ let matches = rules.map(r => { r.re.lastIndex = 0; return [r, r.re.exec(txt)]; })
+ .filter(([,m]) => m !== null)
+ .sort(([r1,m1],[r2,m2]) => (r1.pr - r2.pr) || (m1.index - m2.index));
+ if (matches && matches.length > 0) {
+ let [rule, match] = matches[0];
+ let subsequentRules = rules.filter(r => r.pr >= rule.pr);
+ let idStr = `<>(${nextId()})<>`;
+ let outerStr = txt.substring(0, match.index) + idStr + txt.substring(rule.re.lastIndex);
+ let innerStr = (rule.brackets)
+ ? (() => { let [l ,r ,f] = rule.with; return l + ft((f ? f(match[1]) : match[1]), subsequentRules) + r; })()
+ : match[0].replace(rule.re, rule.with);
+ return ft(outerStr, subsequentRules).replace(idStr, innerStr);
+ }
+ return txt;
+ }
+ return ft(htmlEscape(txt), rules); // idStr above relies on the fact the text is escaped
+}
+
+function fixWwwLink(url) {
+ return url.replace(/^(?!([a-z]+:)?\/\/)/i, '//');
+}
+
+function makeNewNode(embedType, aNode, reResult) {
+ const withClasses = el => {
+ if (embedType.className) {
+ el.classList.add(...embedType.className.split(' '));
+ }
+ return el;
+ };
+ return embedType.makeNode(aNode, reResult, withClasses(document.createElement('div')));
+}
+
+function makeIframe(src, w, h, scrolling='no') {
+ let iframe = document.createElement('iframe');
+ iframe.style.width = w;
+ iframe.style.height = h;
+ iframe.frameBorder = 0;
+ iframe.scrolling = scrolling;
+ iframe.setAttribute('allowFullScreen', '');
+ iframe.src = src;
+ iframe.innerHTML = 'Cannot show iframes.';
+ return iframe;
+}
+
+function makeResizableToRatio(element, ratio) {
+ element.dataset['ratio'] = ratio;
+ makeResizable(element, w => w * element.dataset['ratio']);
+}
+
+// calcHeight :: Number -> Number -- calculate element height for a given width
+function makeResizable(element, calcHeight) {
+ const setHeight = el => {
+ if (document.body.contains(el) && (el.offsetWidth > 0)) {
+ el.style.height = (calcHeight(el.offsetWidth)).toFixed(2) + 'px';
+ }
+ };
+ window.addEventListener('resize', () => setHeight(element));
+ setHeight(element);
+}
+
+function extractDomain(url) {
+ const domainRe = /^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)/i;
+ return domainRe.exec(url)[1];
+}
+
+function urlReplace(match, p1, p2, p3) {
+ let isBrackets = (p1 !== undefined);
+ return (isBrackets)
+ ? `${p1}`
+ : `${extractDomain(match)}`;
+}
+
+function urlReplaceInCode(match, p1, p2, p3) {
+ let isBrackets = (p1 !== undefined);
+ return (isBrackets)
+ ? `${match}`
+ : `${match}`;
+}
+
+function messageReplyReplace(messageId) {
+ return function(match, mid, rid) {
+ let replyPart = (rid && rid != '0') ? '#' + rid : '';
+ return `${match}`;
+ };
+}
+
+/**
+ * Given "txt" message in unescaped plaintext with Juick markup, this function
+ * returns escaped formatted HTML string.
+ *
+ * @param {string} txt
+ * @param {string} messageId - current message id
+ * @param {boolean} isCode
+ * @returns {string}
+ */
+function juickFormat(txt, messageId, isCode) {
+ const urlRe = /(?:\[([^\]\[]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))/gi;
+ const bqReplace = m => m.replace(/^(?:>|>)\s?/gmi, '');
+ return (isCode)
+ ? formatText(txt, [
+ { pr: 1, re: urlRe, with: urlReplaceInCode },
+ { pr: 1, re: /\B(?:#(\d+))?(?:\/(\d+))?\b/g, with: messageReplyReplace(messageId) },
+ { pr: 1, re: /\B@([\w-]+)\b/gi, with: '@$1' },
+ ])
+ : formatText(txt, [
+ { pr: 0, re: /((?:^(?:>|>)\s?[\s\S]+?$\n?)+)/gmi, brackets: true, with: ['', '
', bqReplace] },
+ { pr: 1, re: urlRe, with: urlReplace },
+ { pr: 1, re: /\B(?:#(\d+))?(?:\/(\d+))?\b/g, with: messageReplyReplace(messageId) },
+ { pr: 1, re: /\B@([\w-]+)\b/gi, with: '@$1' },
+ { pr: 2, re: /\B\*([^\n]+?)\*((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['', ''] },
+ { pr: 2, re: /\B\/([^\n]+?)\/((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['', ''] },
+ { pr: 2, re: /\b\_([^\n]+?)\_((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['', ''] },
+ { pr: 3, re: /\n/g, with: '
' },
+ ]);
+}
+
+function getEmbeddableLinkTypes() {
+ return [
+ {
+ name: 'Jpeg and png images',
+ id: 'embed_jpeg_and_png_images',
+ className: 'picture compact',
+ ctsDefault: false,
+ re: /\.(jpe?g|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
+ div.innerHTML = ``;
+ return div;
+ }
+ },
+ {
+ name: 'Gif images',
+ id: 'embed_gif_images',
+ className: 'picture compact',
+ ctsDefault: true,
+ re: /\.gif(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
+ div.innerHTML = ``;
+ return div;
+ }
+ },
+ {
+ name: 'Video (webm, mp4, ogv)',
+ id: 'embed_webm_and_mp4_videos',
+ className: 'video compact',
+ ctsDefault: false,
+ re: /\.(webm|mp4|m4v|ogv)(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
+ div.innerHTML = ``;
+ return div;
+ }
+ },
+ {
+ name: 'Audio (mp3, ogg, weba, opus, m4a, oga, wav)',
+ id: 'embed_sound_files',
+ className: 'audio singleColumn',
+ ctsDefault: false,
+ re: /\.(mp3|ogg|weba|opus|m4a|oga|wav)(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
+ div.innerHTML = ``;
+ return div;
+ }
+ },
+ {
+ name: 'YouTube videos (and playlists)',
+ id: 'embed_youtube_videos',
+ className: 'youtube resizableV singleColumn',
+ ctsDefault: false,
+ re: /^(?:https?:)?\/\/(?:www\.|m\.|gaming\.)?(?:youtu(?:(?:\.be\/|be\.com\/(?:v|embed)\/)([-\w]+)|be\.com\/watch)((?:(?:\?|&(?:amp;)?)(?:\w+=[-\.\w]*[-\w]))*)|youtube\.com\/playlist\?list=([-\w]*)(&(amp;)?[-\w\?=]*)?)/i,
+ makeNode: function(aNode, reResult, div) {
+ let [url, v, args, plist] = reResult;
+ let iframeUrl;
+ if (plist) {
+ iframeUrl = '//www.youtube-nocookie.com/embed/videoseries?list=' + plist;
+ } else {
+ let pp = {}; args.replace(/^\?/, '')
+ .split('&')
+ .map(s => s.split('='))
+ .forEach(z => pp[z[0]] = z[1]);
+ let embedArgs = { rel: '0' };
+ if (pp.t) {
+ const tre = /^(?:(\d+)|(?:(\d+)h)?(?:(\d+)m)?(\d+)s|(?:(\d+)h)?(\d+)m|(\d+)h)$/i;
+ let [, t, h, m, s, h1, m1, h2] = tre.exec(pp.t);
+ embedArgs['start'] = (+t) || ((+(h || h1 || h2 || 0))*60*60 + (+(m || m1 || 0))*60 + (+(s || 0)));
+ }
+ if (pp.list) {
+ embedArgs['list'] = pp.list;
+ }
+ v = v || pp.v;
+ let argsStr = Object.keys(embedArgs)
+ .map(k => `${k}=${embedArgs[k]}`)
+ .join('&');
+ iframeUrl = `//www.youtube-nocookie.com/embed/${v}?${argsStr}`;
+ }
+ let iframe = makeIframe(iframeUrl, '100%', '360px');
+ iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0);
+ return setContent(div, iframe);
+ }
+ },
+ {
+ name: 'Vimeo videos',
+ id: 'embed_vimeo_videos',
+ className: 'vimeo resizableV',
+ ctsDefault: false,
+ re: /^(?:https?:)?\/\/(?:www\.)?(?:player\.)?vimeo\.com\/(?:video\/|album\/[\d]+\/video\/)?([\d]+)/i,
+ makeNode: function(aNode, reResult, div) {
+ let iframe = makeIframe('//player.vimeo.com/video/' + reResult[1], '100%', '360px');
+ iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0);
+ return setContent(div, iframe);
+ }
+ }
+ ];
+}
+
+function embedLink(aNode, linkTypes, container, afterNode) {
+ let anyEmbed = false;
+ let linkId = (aNode.href.replace(/^https?:/i, '').replace(/\'/gi,''));
+ let sameEmbed = container.querySelector(`*[data-linkid='${linkId}']`); // do not embed the same thing twice
+ if (sameEmbed === null) {
+ anyEmbed = [].some.call(linkTypes, function(linkType) {
+ let reResult = linkType.re.exec(aNode.href);
+ if (reResult) {
+ if (linkType.match && (linkType.match(aNode, reResult) === false)) { return false; }
+ let newNode = makeNewNode(linkType, aNode, reResult);
+ if (!newNode) { return false; }
+ newNode.setAttribute('data-linkid', linkId);
+ if (afterNode) {
+ insertAfter(newNode, afterNode);
+ } else {
+ container.appendChild(newNode);
+ }
+ aNode.classList.add('embedLink');
+ return true;
+ }
+ });
+ }
+ return anyEmbed;
+}
+
+function embedLinks(aNodes, container) {
+ let anyEmbed = false;
+ let embeddableLinkTypes = getEmbeddableLinkTypes();
+ Array.from(aNodes).forEach(aNode => {
+ let isEmbedded = embedLink(aNode, embeddableLinkTypes, container);
+ anyEmbed = anyEmbed || isEmbedded;
+ });
+ return anyEmbed;
+}
+
+/**
+ * Embed all the links inside element "x" that match to "allLinksSelector".
+ * All the embedded media is placed inside "div.embedContainer".
+ * "div.embedContainer" is inserted before an element matched by "beforeNodeSelector"
+ * if not present. Existing container is used otherwise.
+ *
+ * @param {Element} x
+ * @param {string} beforeNodeSelector
+ * @param {string} allLinksSelector
+ */
+function embedLinksToX(x, beforeNodeSelector, allLinksSelector) {
+ let isCtsPost = false;
+ let allLinks = x.querySelectorAll(allLinksSelector);
+
+ let existingContainer = x.querySelector('div.embedContainer');
+ if (existingContainer) {
+ embedLinks(allLinks, existingContainer);
+ } else {
+ let embedContainer = document.createElement('div');
+ embedContainer.className = 'embedContainer';
+
+ let anyEmbed = embedLinks(allLinks, embedContainer);
+ if (anyEmbed) {
+ let beforeNode = x.querySelector(beforeNodeSelector);
+ x.insertBefore(embedContainer, beforeNode);
+ }
+ }
+}
+
+function embedLinksToArticles() {
+ let beforeNodeSelector = 'nav.l';
+ let allLinksSelector = 'p:not(.ir) a, pre a';
+ Array.from(document.querySelectorAll('#content article')).forEach(article => {
+ embedLinksToX(article, beforeNodeSelector, allLinksSelector);
+ });
+}
+
+function embedLinksToPost() {
+ let beforeNodeSelector = '.msg-txt + *';
+ let allLinksSelector = '.msg-txt a';
+ Array.from(document.querySelectorAll('#content .msg-cont')).forEach(msg => {
+ embedLinksToX(msg, beforeNodeSelector, allLinksSelector);
+ });
+}
+
+/**
+ * Embed all the links in all messages/replies on the page.
+ */
+function embedAll() {
+ if (document.querySelector('#content article[data-mid]')) {
+ embedLinksToArticles();
+ } else {
+ embedLinksToPost();
+ }
+}
+
+exports.embedAll = embedAll;
+exports.embedLinksToX = embedLinksToX;
+exports.format = juickFormat;
diff --git a/juick-www/src/main/assets/logo.png b/juick-www/src/main/assets/logo.png
new file mode 100644
index 00000000..4e0f6d56
Binary files /dev/null and b/juick-www/src/main/assets/logo.png differ
diff --git a/juick-www/src/main/assets/logo@2x.png b/juick-www/src/main/assets/logo@2x.png
new file mode 100644
index 00000000..6febeaf9
Binary files /dev/null and b/juick-www/src/main/assets/logo@2x.png differ
diff --git a/juick-www/src/main/assets/scripts.js b/juick-www/src/main/assets/scripts.js
new file mode 100644
index 00000000..9da9ce3c
--- /dev/null
+++ b/juick-www/src/main/assets/scripts.js
@@ -0,0 +1,736 @@
+require('whatwg-fetch');
+require('element-closest');
+require('classlist.js');
+require('url-search-params-polyfill');
+let Awesomplete = require('awesomplete');
+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',
+ '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': 'Войти через ВКонтакте',
+ '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 = (window.location.protocol === 'https:' ? 'wss' : 'ws') + ':'
+ + '//api.juick.com/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);
+ wsIncomingReply(jsonMsg);
+ } catch (err) {
+ console.log(err);
+ }
+ }
+ };
+ setInterval(wsSendKeepAlive, 90000);
+}
+
+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 === undefined) ? '' : `
+ `;
+ let msgContHtml = `
+
+
+
${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.submit();
+ }
+ }
+}
+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 showCommentForm(mid, rid) {
+ let reply = 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 = 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;
+ });
+ }
+ 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 = `
+
+ `;
+ return openDialog(newmessageTemplate);
+}
+
+function openDialog(html, image) {
+ var dialogHtml = `
+ `;
+ 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.juick.com/users?uname=' + uname)
+ .then(function () {
+ style.background = '#FFCCCC';
+ })
+ .catch(function () {
+ style.background = '#CCFFCC';
+ });
+}
+
+/******************************************************************************/
+
+function openDialogLogin() {
+ let html = `
+ `;
+ 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('//juick.com/like?mid=' + mid, {
+ method: 'POST',
+ credentials: 'same-origin'
+ })
+ .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 setPopular(e, mid, popular) {
+ fetch('//api.juick.com/messages/set_popular?mid=' + mid
+ + '&popular=' + popular
+ + '&hash=' + document.getElementById('body').getAttribute('data-hash'), {
+ credentials: 'same-origin'
+ })
+ .then(function () {
+ e.closest('article').append(resultMessage('OK!'));
+ });
+ return false;
+}
+
+function setPrivacy(e, mid) {
+ fetch('//api.juick.com/messages/set_privacy?mid=' + mid
+ + '&hash=' + document.getElementById('body').getAttribute('data-hash'), {
+ credentials: 'same-origin'
+ })
+ .then(function () {
+ e.closest('article').append(resultMessage('OK!'));
+ });
+ return false;
+}
+
+function getTags() {
+ fetch('//api.juick.com/tags?hash=' + document.getElementById('body').getAttribute('data-hash'), {
+ credentials: 'same-origin'
+ })
+ .then(response => {
+ return response.json();
+ })
+ .then(json => {
+ let tags = json.map(t => t.tag);
+ let input = document.getElementById('tags_input');
+ new Awesomplete(input, {list : tags});
+ });
+ return false;
+}
+
+function addTag(tag) {
+ document.forms['postmsg'].body.value='*'+tag+' '+document.forms['postmsg'].body.value;
+ return false;
+}
+
+/******************************************************************************/
+
+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();
+ }
+ });
+ });
+ }
+
+ 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('.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');
+ }
+ });
+ initWS();
+
+ window.addEventListener('pagehide', wsShutdown);
+
+ killy.embedAll();
+});
diff --git a/juick-www/src/main/assets/style.css b/juick-www/src/main/assets/style.css
new file mode 100644
index 00000000..ce80e650
--- /dev/null
+++ b/juick-www/src/main/assets/style.css
@@ -0,0 +1,907 @@
+/* #region generic */
+
+html,
+body,
+div,
+h1,
+h2,
+ul,
+li,
+p,
+form,
+input,
+textarea,
+pre {
+ margin: 0;
+ padding: 0;
+}
+html,
+input,
+textarea {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+ font-size: 12pt;
+ -webkit-font-smoothing: subpixel-antialiased;
+}
+h1,
+h2 {
+ font-weight: normal;
+}
+ul {
+ list-style-type: none;
+}
+a {
+ color: #069;
+ text-decoration: none;
+}
+img,
+hr {
+ border: none;
+}
+hr {
+ background: #CCC;
+ height: 1px;
+ margin: 10px 0;
+}
+pre {
+ background: #222;
+ color: #0f0;
+ overflow-x: auto;
+ padding: 6px;
+ white-space: pre;
+}
+pre::selection {
+ background: #0f0;
+ color: #222;
+}
+pre::-moz-selection {
+ background: #0f0;
+ color: #222;
+}
+.u {
+ text-decoration: underline;
+}
+
+/* #endregion */
+
+/* #region overall layout */
+
+html {
+ background: #f8f8f8;
+ color: #222;
+}
+#wrapper {
+ margin: 0 auto;
+ width: 1000px;
+ margin-top: 50px;
+}
+#column {
+ float: left;
+ margin-left: 10px;
+ overflow: hidden;
+ padding-top: 10px;
+ width: 240px;
+}
+#content {
+ float: right;
+ margin: 12px 0 0 0;
+ width: 728px;
+}
+#minimal_content {
+ margin: 0 auto;
+ min-width: 310px;
+ width: auto;
+}
+body > header {
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.28);
+ background: #fff;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 10;
+}
+#header_wrapper {
+ margin: 0 auto;
+ width: 1000px;
+ display: flex;
+}
+#footer {
+ clear: both;
+ color: #999;
+ font-size: 10pt;
+ margin: 40px;
+ padding: 10px 0;
+}
+
+@media screen and (max-width: 850px) {
+ body {
+ -moz-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ }
+ body,
+ #wrapper,
+ #topwrapper,
+ #content,
+ #footer {
+ float: none;
+ margin: 0 auto;
+ min-width: 310px;
+ width: auto;
+ }
+ #wrapper {
+ margin-top: 50px;
+ }
+ body > header {
+ margin-bottom: 15px;
+ }
+ #column {
+ float: none;
+ margin: 0 10px;
+ padding-top: 0;
+ width: auto;
+ }
+}
+
+/* #endregion */
+
+/* #region header internals */
+
+#logo {
+ height: 36px;
+ margin: 7px 25px 0 20px;
+ width: 110px;
+}
+#logo a {
+ background: url("logo@2x.png") no-repeat;
+ background-size: cover;
+ border: 0;
+ display: block;
+ height: 36px;
+ overflow: hidden;
+ text-indent: 100%;
+ white-space: nowrap;
+ width: 110px;
+}
+#global {
+ flex-grow: 1;
+ display: flex;
+}
+#global a {
+ color: #888;
+ display: inline-block;
+ font-size: 13pt;
+ padding: 14px 6px;
+}
+#global li {
+ display: inline-block;
+}
+#global li:hover,
+.l a:hover {
+ background-color: #fff;
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
+ cursor: pointer;
+ transition: box-shadow 0.2s ease-in;
+}
+#search {
+ margin: 12px 20px 12px 0;
+}
+#search input {
+ background: #FFF;
+ border: 1px solid #DDDDD5;
+ outline: none !important;
+ padding: 4px;
+}
+@media screen and (max-width: 850px) {
+ #logo {
+ display: none;
+ }
+ #search {
+ display: inline-block;
+ float: none;
+ margin: 10px 10px;
+ }
+}
+
+/* #endregion */
+
+/* #region left column internals */
+
+.toolbar {
+ border-top: 1px solid #CCC;
+}
+
+#column ul,
+#column p,
+#column hr {
+ margin: 10px 0;
+}
+#column li > a {
+ display: block;
+ height: 100%;
+ padding: 6px;
+}
+#column li > a:hover {
+ background-color: #fff;
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
+ transition: background-color 0.2s ease-in;
+}
+#column .margtop {
+ margin-top: 15px;
+}
+#column p {
+ font-size: 10pt;
+ line-height: 140%;
+}
+#column .tags {
+ text-align: justify;
+}
+#column .inp {
+ background: #fff;
+ border: 1px solid #ddddd5;
+ outline: none !important;
+ padding: 4px;
+ width: 222px;
+}
+#ctitle {
+ font-size: 14pt;
+}
+#ctitle img {
+ margin-right: 5px;
+ vertical-align: middle;
+ width: 48px;
+}
+#ustats li {
+ font-size: 10pt;
+ margin: 3px 0;
+}
+#column table.iread {
+ width: 100%;
+}
+#column table.iread td {
+ text-align: center;
+}
+#column table.iread img {
+ height: 48px;
+ width: 48px;
+}
+
+/* #endregion */
+
+/* #region main content */
+#content > p,
+#content > h1,
+#content > h2,
+#minimal_content > p,
+#minimal_content > h1,
+#minimal_content > h2 {
+ margin: 1em 0;
+}
+.page {
+ background: #eee;
+ padding: 6px;
+ text-align: center;
+}
+
+.page a {
+ color: #888;
+}
+
+/* #endregion */
+
+/* #region article, message internals */
+
+article {
+ background: #fff;
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
+ line-height: 140%;
+ margin-bottom: 10px;
+ padding: 20px;
+}
+article time {
+ color: #999;
+ font-size: 10pt;
+}
+article p {
+ clear: left;
+ margin: 5px 0 15px 0;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+}
+article .ir {
+ text-align: center;
+}
+article .ir a {
+ cursor: zoom-in;
+ display: block;
+}
+article .ir img {
+ max-width: 100%;
+}
+article > nav.l,
+.msg-cont > nav.l {
+ border-top: 1px solid #eee;
+ display: flex;
+ justify-content: space-around;
+ font-size: 10pt;
+}
+article > nav.l a,
+.msg-cont > nav.l a {
+ color: #888;
+ margin-right: 15px;
+}
+article .likes {
+ padding-left: 20px;
+}
+article .replies {
+ margin-left: 18px;
+}
+article .tags {
+ margin-top: 3px;
+}
+.msg-tags {
+ margin-top: 12px;
+ min-height: 1px;
+}
+article .tags > a,
+.msg-tags > a {
+ background: #eee;
+ border: 1px solid #eee;
+ color: #888;
+ display: inline-block;
+ font-size: 10pt;
+ margin-bottom: 5px;
+ margin-right: 5px;
+ padding: 0 10px;
+}
+.l .msg-button {
+ align-items: center;
+ display: flex;
+ flex-basis: 0;
+ flex-direction: column;
+ flex-grow: 1;
+ padding-top: 12px;
+}
+.l .msg-button-icon {
+ font-weight: bold;
+}
+.msgthread {
+ margin-bottom: 0;
+}
+.msg-avatar {
+ float: left;
+ height: 48px;
+ margin-right: 10px;
+ width: 48px;
+}
+.msg-avatar img {
+ height: 48px;
+ vertical-align: top;
+ width: 48px;
+}
+.msg-cont {
+ background: #FFF;
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
+ line-height: 140%;
+ margin-bottom: 12px;
+ padding: 20px;
+ width: 640px;
+}
+.reply-new .msg-cont {
+ border-right: 5px solid #0C0;
+}
+.msg-ts {
+ font-size: small;
+ vertical-align: top;
+}
+.msg-ts,
+.msg-ts > a {
+ color: #999;
+}
+.msg-txt {
+ clear: both;
+ margin: 0 0 12px;
+ padding-top: 10px;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+}
+.msg-media {
+ text-align: center;
+}
+.msg-links {
+ color: #999;
+ font-size: small;
+ margin: 5px 0 0 0;
+}
+.msg-comments {
+ color: #AAA;
+ font-size: small;
+ margin-top: 10px;
+ overflow: hidden;
+ text-indent: 10px;
+}
+.ta-wrapper {
+ border: 1px solid #DDD;
+ display: flex;
+ flex-grow: 1;
+}
+.msg-comment {
+ display: flex;
+ width: 100%;
+ margin-top: 10px;
+}
+.msg-comment-hidden {
+ display: none;
+}
+.msg-comment textarea {
+ border: 0;
+ flex-grow: 1;
+ outline: none !important;
+ padding: 4px;
+ resize: vertical;
+ vertical-align: top;
+}
+.attach-photo {
+ cursor: pointer;
+}
+.attach-photo-active {
+ color: green;
+}
+.msg-comment input {
+ align-self: flex-start;
+ background: #EEE;
+ border: 1px solid #CCC;
+ color: #999;
+ margin: 0 0 0 6px;
+ position: -webkit-sticky;
+ position: sticky;
+ top: 70px;
+ vertical-align: top;
+ width: 50px;
+}
+.msg-recomms {
+ color: #AAA;
+ font-size: small;
+ margin-top: 10px;
+ overflow: hidden;
+ text-indent: 10px;
+}
+#replies .msg-txt,
+#private-messages .msg-txt {
+ margin: 0;
+}
+.title2 {
+ background: #fff;
+ margin: 20px 0;
+ padding: 10px 20px;
+ width: 640px;
+}
+.title2-right {
+ float: right;
+ line-height: 24px;
+}
+#content .title2 h2 {
+ font-size: x-large;
+ margin: 0;
+}
+
+@media screen and (max-width: 850px) {
+ article {
+ overflow: auto;
+ }
+ article p {
+ margin: 10px 0 8px 0;
+ }
+ .msg,
+ .msg-cont {
+ min-width: 280px;
+ width: auto;
+ }
+ .msg-cont {
+ margin: 8px 0;
+ }
+ .msg-media {
+ overflow: auto;
+ }
+ .title2 h2 {
+ font-size: large;
+ }
+ .msg-comment {
+ flex-direction: column;
+ }
+ .msg-comment input {
+ align-self: flex-end;
+ margin: 6px 0 0 0;
+ width: 100px;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .msg-txt {
+ padding-top: 5px;
+ }
+ .title2 {
+ font-size: 11pt;
+ width: auto;
+ }
+ #content .title2 h2 {
+ font-size: 11pt;
+ }
+ .title2-right {
+ line-height: initial;
+ }
+}
+
+/* #endregion */
+
+/* #region user-generated texts */
+
+q:before,
+q:after {
+ content: "";
+}
+q,
+blockquote {
+ border-left: 3px solid #CCC;
+ color: #666;
+ display: block;
+ margin: 10px 0 10px 10px;
+ padding-left: 10px;
+}
+
+/* #endregion */
+
+/* #region new message form internals */
+
+#newmessage {
+ background: #E5E5E0;
+ margin-bottom: 20px;
+ padding: 15px;
+}
+#newmessage textarea {
+ border: 1px solid #CCC;
+ box-sizing: border-box;
+ margin: 0 0 5px 0;
+ margin-top: 20px;
+ max-height: 6em;
+ min-width: 280px;
+ padding: 4px;
+ width: 100%;
+}
+#newmessage input {
+ border: 1px solid #CCC;
+ margin: 5px 0;
+ padding: 2px 4px;
+}
+#newmessage .img {
+ width: 500px;
+}
+#newmessage .tags {
+ width: 500px;
+}
+#newmessage .subm {
+ background: #EEEEE5;
+ width: 150px;
+}
+@media screen and (max-width: 850px) {
+ #newmessage .img,
+ #newmessage .tags {
+ width: 100%;
+ }
+}
+
+/* #endregion */
+
+/* #region user lists */
+
+.users {
+ margin: 10px 0;
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+}
+.users > span {
+ overflow: hidden;
+ padding: 6px 0;
+ width: 200px;
+}
+.users img {
+ height: 32px;
+ margin-right: 6px;
+ vertical-align: middle;
+ width: 32px;
+}
+
+/* #endregion */
+
+/* #region signup form */
+
+.signup-h1 > img {
+ margin-right: 10px;
+ vertical-align: middle;
+}
+.signup-h1 {
+ font-size: x-large;
+ margin: 20px 0 10px 0;
+}
+.signup-h2 {
+ font-size: large;
+ margin: 10px 0 5px 0;
+}
+.signup-hr {
+ margin: 20px 0;
+}
+
+/* #endregion */
+
+/* #region PM */
+
+.newpm {
+ margin: 20px 60px 30px 60px;
+}
+.newpm textarea {
+ resize: vertical;
+ width: 100%;
+}
+.newpm-send input {
+ width: 100px;
+}
+
+/* #endregion */
+
+/* #region popup dialog (lightbox) */
+
+#dialogb {
+ background: #222;
+ height: 100%;
+ left: 0;
+ opacity: 0.6;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 10;
+}
+#dialogt {
+ height: 100%;
+ left: 0;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+#dialogw {
+ z-index: 11;
+ max-width: 96%;
+ max-height: calc(100% - 100px);
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+#dialogw a {
+ display: block;
+}
+#dialogw img {
+ max-height: 100%;
+ max-height: 90vh;
+ max-width: 100%;
+}
+#dialog_header {
+ width: 100%;
+ height: 44px;
+ position: fixed;
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: center;
+}
+.header_image {
+ background: rgba(0, 0, 0, 0.28);
+}
+#dialogc {
+ cursor: pointer;
+ color: #ccc;
+ padding-right: 6px;
+}
+.dialoglogin {
+ background: #fff;
+ padding: 25px;
+ width: 300px;
+}
+.dialog-opened {
+ overflow: hidden;
+}
+#signfb,
+#signvk {
+ display: block;
+ line-height: 32px;
+ margin: 10px 0;
+ text-decoration: none;
+ width: 100%;
+}
+#signvk {
+ margin-bottom: 30px;
+}
+.dialoglogin form {
+ margin-top: 7px;
+}
+.signinput,
+.signsubmit {
+ border: 1px solid #CCC;
+ margin: 3px 0;
+ padding: 3px;
+}
+.signinput {
+ width: 292px;
+}
+.signsubmit {
+ width: 70px;
+}
+.dialogshare {
+ background: #fff;
+ min-width: 300px;
+ overflow: auto;
+ padding: 20px;
+}
+.dialogl {
+ background: #fff;
+ border: 1px solid #DDD;
+ margin: 3px 0 20px;
+ padding: 5px;
+}
+.dialogshare li {
+ float: left;
+ margin: 5px 10px 0 0;
+}
+.dialogshare a {
+ display: block;
+}
+.dialogtxt {
+ background: #fff;
+ padding: 20px;
+}
+
+@media screen and (max-width: 480px) {
+ .dialog-opened {
+ position: fixed;
+ width: 100%;
+ }
+}
+
+/* #endregion */
+
+/* #region misc */
+
+#wsthread {
+ background: #CCC;
+ bottom: 20px;
+ cursor: pointer;
+ display: none;
+ padding: 5px 10px;
+ position: fixed;
+ right: 20px;
+}
+.sharenew {
+ display: inline-block;
+ line-height: 32px;
+ min-height: 32px;
+ min-width: 200px;
+ padding: 0 12px 0 37px;
+}
+.icon {
+ margin-top: -2px;
+ vertical-align: middle;
+}
+.icon--ei-link {
+ margin-top: -1px;
+}
+.icon--ei-comment {
+ margin-top: -5px;
+}
+.newmessage {
+ /* textarea on the /post page */
+ border: 1px solid #DDD;
+ padding: 2px;
+ resize: vertical;
+ width: 100%;
+}
+
+/* #endregion */
+
+/* #region footer internals */
+
+#footer-social {
+ float: left;
+}
+#footer-social a {
+ border: 0;
+ display: inline-block;
+}
+#footer-left {
+ margin-left: 286px;
+ margin-right: 350px;
+}
+#footer-right {
+ float: right;
+}
+
+@media screen and (max-width: 850px) {
+ #footer {
+ margin: 0 10px;
+ }
+ #footer div {
+ float: none;
+ margin: 10px 0;
+ }
+}
+
+/* #endregion */
+
+/* #region settings */
+
+fieldset {
+ border: 1px dotted #ccc;
+ margin-top: 25px;
+}
+
+/* #endregion */
+
+/* #region embeds */
+
+.embedContainer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ margin: 30px -3px 15px -3px;
+}
+.embedContainer > * {
+ box-sizing: border-box;
+ flex-grow: 1;
+ margin: 3px;
+ min-width: 49%;
+}
+.embedContainer > .compact {
+ flex-grow: 0;
+}
+.embedContainer .picture img {
+ display: block;
+}
+.embedContainer img,
+.embedContainer video {
+ max-width: 100%;
+ max-height: 80vh;
+}
+.embedContainer > .audio,
+.embedContainer > .youtube {
+ min-width: 90%;
+}
+.embedContainer audio {
+ width: 100%;
+}
+.embedContainer iframe {
+ overflow: hidden;
+ resize: vertical;
+ display: block;
+}
+
+/* #endregion */
+
+/* #region nsfw */
+
+article.nsfw .embedContainer img,
+article.nsfw .embedContainer video,
+article.nsfw .embedContainer iframe,
+article.nsfw .ir img {
+ opacity: 0.1;
+}
+article.nsfw .embedContainer img:hover,
+article.nsfw .embedContainer video:hover,
+article.nsfw .embedContainer iframe:hover,
+article.nsfw .ir img:hover {
+ opacity: 1;
+}
+
+/* #endregion */
diff --git a/juick-www/src/main/java/com/juick/www/WebApp.java b/juick-www/src/main/java/com/juick/www/WebApp.java
index 94c12528..4e8b3a11 100644
--- a/juick-www/src/main/java/com/juick/www/WebApp.java
+++ b/juick-www/src/main/java/com/juick/www/WebApp.java
@@ -16,20 +16,16 @@
*/
package com.juick.www;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.juick.Tag;
import com.juick.service.TagService;
-import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.resource.ResourceUrlProvider;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;
@@ -42,20 +38,7 @@ public class WebApp {
@Inject
private TagService tagService;
@Inject
- private ObjectMapper jsonMapper;
-
- private String scriptsUrl;
- private String styleUrl;
-
- @PostConstruct
- public void init() throws IOException {
- String manifestString = IOUtils.toString(getClass().getClassLoader().
- getResourceAsStream("manifest.json"), StandardCharsets.UTF_8);
- HashMap manifest = jsonMapper.readValue(manifestString,
- new TypeReference>() {});
- scriptsUrl = manifest.get("scripts.js");
- styleUrl = manifest.get("style.css");
- }
+ private ResourceUrlProvider resourceUrlProvider;
public List parseTags(String tagsStr) {
List tags = new ArrayList<>();
@@ -79,10 +62,10 @@ public class WebApp {
}
public String getStyleUrl() {
- return styleUrl;
+ return resourceUrlProvider.getForLookupPath("/style.css");
}
public String getScriptsUrl() {
- return scriptsUrl;
+ return resourceUrlProvider.getForLookupPath("/scripts.js");
}
}
diff --git a/juick-www/src/main/java/com/juick/www/configuration/WebSecurityConfig.java b/juick-www/src/main/java/com/juick/www/configuration/WebSecurityConfig.java
index 19329dad..65871088 100644
--- a/juick-www/src/main/java/com/juick/www/configuration/WebSecurityConfig.java
+++ b/juick-www/src/main/java/com/juick/www/configuration/WebSecurityConfig.java
@@ -72,7 +72,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
http.addFilterAfter(hashParamAuthenticationFilter(), BasicAuthenticationFilter.class);
http
.authorizeRequests()
- .antMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post", "/comment").authenticated()
+ .antMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post", "/comment")
+ .authenticated()
.anyRequest().permitAll()
.and()
.anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY)
diff --git a/juick-www/src/main/java/com/juick/www/configuration/WwwAppConfiguration.java b/juick-www/src/main/java/com/juick/www/configuration/WwwAppConfiguration.java
index 9c35f1c2..34720c33 100644
--- a/juick-www/src/main/java/com/juick/www/configuration/WwwAppConfiguration.java
+++ b/juick-www/src/main/java/com/juick/www/configuration/WwwAppConfiguration.java
@@ -41,16 +41,11 @@ import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-import org.springframework.core.Ordered;
-import org.springframework.http.CacheControl;
import org.springframework.web.servlet.ViewResolver;
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import org.springframework.web.servlet.resource.PathResourceResolver;
import javax.inject.Inject;
import java.util.Collections;
-import java.util.concurrent.TimeUnit;
/**
* Created by aalexeev on 11/22/16.
@@ -97,23 +92,6 @@ public class WwwAppConfiguration implements WebMvcConfigurer {
public CloudflareCache cloudflareCache() {
return new CloudflareCache();
}
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
- registry.addResourceHandler(
- "/scripts*.js*",
- "/style*.css*",
- "/*.png",
- "/favicon.ico")
- .addResourceLocations("classpath:/")
- .setCacheControl(CacheControl.maxAge(30, TimeUnit.DAYS).mustRevalidate().cachePublic())
- .resourceChain(true)
- .addResolver(new PathResourceResolver());
-
- registry.addResourceHandler("/static/**")
- .addResourceLocations("/static/")
- .setCacheControl(CacheControl.maxAge(30, TimeUnit.DAYS).mustRevalidate().cachePublic());
- }
@Bean
public Loader templateLoader() {
return new ClasspathLoader();
diff --git a/juick-www/src/main/js/killy/index.js b/juick-www/src/main/js/killy/index.js
deleted file mode 100644
index 25c37142..00000000
--- a/juick-www/src/main/js/killy/index.js
+++ /dev/null
@@ -1,336 +0,0 @@
-
-function insertAfter(newNode, referenceNode) {
- referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
-}
-
-function setContent(containerNode, ...newNodes) {
- removeAllFrom(containerNode);
- newNodes.forEach(n => containerNode.appendChild(n));
- return containerNode;
-}
-
-function removeAllFrom(fromNode) {
- for (let c; c = fromNode.lastChild; ) { fromNode.removeChild(c); }
-}
-
-function htmlEscape(html) {
- let textarea = document.createElement('textarea');
- textarea.textContent = html;
- return textarea.innerHTML;
-}
-
-// rules :: [{pr: number, re: RegExp, with: string}]
-// rules :: [{pr: number, re: RegExp, with: Function}]
-// rules :: [{pr: number, re: RegExp, brackets: true, with: [string, string]}]
-// rules :: [{pr: number, re: RegExp, brackets: true, with: [string, string, Function]}]
-function formatText(txt, rules) {
- let idCounter = 0;
- function nextId() { return idCounter++; }
- function ft(txt, rules) {
- let matches = rules.map(r => { r.re.lastIndex = 0; return [r, r.re.exec(txt)]; })
- .filter(([,m]) => m !== null)
- .sort(([r1,m1],[r2,m2]) => (r1.pr - r2.pr) || (m1.index - m2.index));
- if (matches && matches.length > 0) {
- let [rule, match] = matches[0];
- let subsequentRules = rules.filter(r => r.pr >= rule.pr);
- let idStr = `<>(${nextId()})<>`;
- let outerStr = txt.substring(0, match.index) + idStr + txt.substring(rule.re.lastIndex);
- let innerStr = (rule.brackets)
- ? (() => { let [l ,r ,f] = rule.with; return l + ft((f ? f(match[1]) : match[1]), subsequentRules) + r; })()
- : match[0].replace(rule.re, rule.with);
- return ft(outerStr, subsequentRules).replace(idStr, innerStr);
- }
- return txt;
- }
- return ft(htmlEscape(txt), rules); // idStr above relies on the fact the text is escaped
-}
-
-function fixWwwLink(url) {
- return url.replace(/^(?!([a-z]+:)?\/\/)/i, '//');
-}
-
-function makeNewNode(embedType, aNode, reResult) {
- const withClasses = el => {
- if (embedType.className) {
- el.classList.add(...embedType.className.split(' '));
- }
- return el;
- };
- return embedType.makeNode(aNode, reResult, withClasses(document.createElement('div')));
-}
-
-function makeIframe(src, w, h, scrolling='no') {
- let iframe = document.createElement('iframe');
- iframe.style.width = w;
- iframe.style.height = h;
- iframe.frameBorder = 0;
- iframe.scrolling = scrolling;
- iframe.setAttribute('allowFullScreen', '');
- iframe.src = src;
- iframe.innerHTML = 'Cannot show iframes.';
- return iframe;
-}
-
-function makeResizableToRatio(element, ratio) {
- element.dataset['ratio'] = ratio;
- makeResizable(element, w => w * element.dataset['ratio']);
-}
-
-// calcHeight :: Number -> Number -- calculate element height for a given width
-function makeResizable(element, calcHeight) {
- const setHeight = el => {
- if (document.body.contains(el) && (el.offsetWidth > 0)) {
- el.style.height = (calcHeight(el.offsetWidth)).toFixed(2) + 'px';
- }
- };
- window.addEventListener('resize', () => setHeight(element));
- setHeight(element);
-}
-
-function extractDomain(url) {
- const domainRe = /^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)/i;
- return domainRe.exec(url)[1];
-}
-
-function urlReplace(match, p1, p2, p3) {
- let isBrackets = (p1 !== undefined);
- return (isBrackets)
- ? `${p1}`
- : `${extractDomain(match)}`;
-}
-
-function urlReplaceInCode(match, p1, p2, p3) {
- let isBrackets = (p1 !== undefined);
- return (isBrackets)
- ? `${match}`
- : `${match}`;
-}
-
-function messageReplyReplace(messageId) {
- return function(match, mid, rid) {
- let replyPart = (rid && rid != '0') ? '#' + rid : '';
- return `${match}`;
- };
-}
-
-/**
- * Given "txt" message in unescaped plaintext with Juick markup, this function
- * returns escaped formatted HTML string.
- *
- * @param {string} txt
- * @param {string} messageId - current message id
- * @param {boolean} isCode
- * @returns {string}
- */
-function juickFormat(txt, messageId, isCode) {
- const urlRe = /(?:\[([^\]\[]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))/gi;
- const bqReplace = m => m.replace(/^(?:>|>)\s?/gmi, '');
- return (isCode)
- ? formatText(txt, [
- { pr: 1, re: urlRe, with: urlReplaceInCode },
- { pr: 1, re: /\B(?:#(\d+))?(?:\/(\d+))?\b/g, with: messageReplyReplace(messageId) },
- { pr: 1, re: /\B@([\w-]+)\b/gi, with: '@$1' },
- ])
- : formatText(txt, [
- { pr: 0, re: /((?:^(?:>|>)\s?[\s\S]+?$\n?)+)/gmi, brackets: true, with: ['', '
', bqReplace] },
- { pr: 1, re: urlRe, with: urlReplace },
- { pr: 1, re: /\B(?:#(\d+))?(?:\/(\d+))?\b/g, with: messageReplyReplace(messageId) },
- { pr: 1, re: /\B@([\w-]+)\b/gi, with: '@$1' },
- { pr: 2, re: /\B\*([^\n]+?)\*((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['', ''] },
- { pr: 2, re: /\B\/([^\n]+?)\/((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['', ''] },
- { pr: 2, re: /\b\_([^\n]+?)\_((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['', ''] },
- { pr: 3, re: /\n/g, with: '
' },
- ]);
-}
-
-function getEmbeddableLinkTypes() {
- return [
- {
- name: 'Jpeg and png images',
- id: 'embed_jpeg_and_png_images',
- className: 'picture compact',
- ctsDefault: false,
- re: /\.(jpe?g|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i,
- makeNode: function(aNode, reResult, div) {
- div.innerHTML = ``;
- return div;
- }
- },
- {
- name: 'Gif images',
- id: 'embed_gif_images',
- className: 'picture compact',
- ctsDefault: true,
- re: /\.gif(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i,
- makeNode: function(aNode, reResult, div) {
- div.innerHTML = ``;
- return div;
- }
- },
- {
- name: 'Video (webm, mp4, ogv)',
- id: 'embed_webm_and_mp4_videos',
- className: 'video compact',
- ctsDefault: false,
- re: /\.(webm|mp4|m4v|ogv)(?:\?[\w&;\?=]*)?$/i,
- makeNode: function(aNode, reResult, div) {
- div.innerHTML = ``;
- return div;
- }
- },
- {
- name: 'Audio (mp3, ogg, weba, opus, m4a, oga, wav)',
- id: 'embed_sound_files',
- className: 'audio singleColumn',
- ctsDefault: false,
- re: /\.(mp3|ogg|weba|opus|m4a|oga|wav)(?:\?[\w&;\?=]*)?$/i,
- makeNode: function(aNode, reResult, div) {
- div.innerHTML = ``;
- return div;
- }
- },
- {
- name: 'YouTube videos (and playlists)',
- id: 'embed_youtube_videos',
- className: 'youtube resizableV singleColumn',
- ctsDefault: false,
- re: /^(?:https?:)?\/\/(?:www\.|m\.|gaming\.)?(?:youtu(?:(?:\.be\/|be\.com\/(?:v|embed)\/)([-\w]+)|be\.com\/watch)((?:(?:\?|&(?:amp;)?)(?:\w+=[-\.\w]*[-\w]))*)|youtube\.com\/playlist\?list=([-\w]*)(&(amp;)?[-\w\?=]*)?)/i,
- makeNode: function(aNode, reResult, div) {
- let [url, v, args, plist] = reResult;
- let iframeUrl;
- if (plist) {
- iframeUrl = '//www.youtube-nocookie.com/embed/videoseries?list=' + plist;
- } else {
- let pp = {}; args.replace(/^\?/, '')
- .split('&')
- .map(s => s.split('='))
- .forEach(z => pp[z[0]] = z[1]);
- let embedArgs = { rel: '0' };
- if (pp.t) {
- const tre = /^(?:(\d+)|(?:(\d+)h)?(?:(\d+)m)?(\d+)s|(?:(\d+)h)?(\d+)m|(\d+)h)$/i;
- let [, t, h, m, s, h1, m1, h2] = tre.exec(pp.t);
- embedArgs['start'] = (+t) || ((+(h || h1 || h2 || 0))*60*60 + (+(m || m1 || 0))*60 + (+(s || 0)));
- }
- if (pp.list) {
- embedArgs['list'] = pp.list;
- }
- v = v || pp.v;
- let argsStr = Object.keys(embedArgs)
- .map(k => `${k}=${embedArgs[k]}`)
- .join('&');
- iframeUrl = `//www.youtube-nocookie.com/embed/${v}?${argsStr}`;
- }
- let iframe = makeIframe(iframeUrl, '100%', '360px');
- iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0);
- return setContent(div, iframe);
- }
- },
- {
- name: 'Vimeo videos',
- id: 'embed_vimeo_videos',
- className: 'vimeo resizableV',
- ctsDefault: false,
- re: /^(?:https?:)?\/\/(?:www\.)?(?:player\.)?vimeo\.com\/(?:video\/|album\/[\d]+\/video\/)?([\d]+)/i,
- makeNode: function(aNode, reResult, div) {
- let iframe = makeIframe('//player.vimeo.com/video/' + reResult[1], '100%', '360px');
- iframe.onload = () => makeResizableToRatio(iframe, 9.0 / 16.0);
- return setContent(div, iframe);
- }
- }
- ];
-}
-
-function embedLink(aNode, linkTypes, container, afterNode) {
- let anyEmbed = false;
- let linkId = (aNode.href.replace(/^https?:/i, '').replace(/\'/gi,''));
- let sameEmbed = container.querySelector(`*[data-linkid='${linkId}']`); // do not embed the same thing twice
- if (sameEmbed === null) {
- anyEmbed = [].some.call(linkTypes, function(linkType) {
- let reResult = linkType.re.exec(aNode.href);
- if (reResult) {
- if (linkType.match && (linkType.match(aNode, reResult) === false)) { return false; }
- let newNode = makeNewNode(linkType, aNode, reResult);
- if (!newNode) { return false; }
- newNode.setAttribute('data-linkid', linkId);
- if (afterNode) {
- insertAfter(newNode, afterNode);
- } else {
- container.appendChild(newNode);
- }
- aNode.classList.add('embedLink');
- return true;
- }
- });
- }
- return anyEmbed;
-}
-
-function embedLinks(aNodes, container) {
- let anyEmbed = false;
- let embeddableLinkTypes = getEmbeddableLinkTypes();
- Array.from(aNodes).forEach(aNode => {
- let isEmbedded = embedLink(aNode, embeddableLinkTypes, container);
- anyEmbed = anyEmbed || isEmbedded;
- });
- return anyEmbed;
-}
-
-/**
- * Embed all the links inside element "x" that match to "allLinksSelector".
- * All the embedded media is placed inside "div.embedContainer".
- * "div.embedContainer" is inserted before an element matched by "beforeNodeSelector"
- * if not present. Existing container is used otherwise.
- *
- * @param {Element} x
- * @param {string} beforeNodeSelector
- * @param {string} allLinksSelector
- */
-function embedLinksToX(x, beforeNodeSelector, allLinksSelector) {
- let isCtsPost = false;
- let allLinks = x.querySelectorAll(allLinksSelector);
-
- let existingContainer = x.querySelector('div.embedContainer');
- if (existingContainer) {
- embedLinks(allLinks, existingContainer);
- } else {
- let embedContainer = document.createElement('div');
- embedContainer.className = 'embedContainer';
-
- let anyEmbed = embedLinks(allLinks, embedContainer);
- if (anyEmbed) {
- let beforeNode = x.querySelector(beforeNodeSelector);
- x.insertBefore(embedContainer, beforeNode);
- }
- }
-}
-
-function embedLinksToArticles() {
- let beforeNodeSelector = 'nav.l';
- let allLinksSelector = 'p:not(.ir) a, pre a';
- Array.from(document.querySelectorAll('#content article')).forEach(article => {
- embedLinksToX(article, beforeNodeSelector, allLinksSelector);
- });
-}
-
-function embedLinksToPost() {
- let beforeNodeSelector = '.msg-txt + *';
- let allLinksSelector = '.msg-txt a';
- Array.from(document.querySelectorAll('#content .msg-cont')).forEach(msg => {
- embedLinksToX(msg, beforeNodeSelector, allLinksSelector);
- });
-}
-
-/**
- * Embed all the links in all messages/replies on the page.
- */
-function embedAll() {
- if (document.querySelector('#content article[data-mid]')) {
- embedLinksToArticles();
- } else {
- embedLinksToPost();
- }
-}
-
-exports.embedAll = embedAll;
-exports.embedLinksToX = embedLinksToX;
-exports.format = juickFormat;
diff --git a/juick-www/src/main/js/killy/package.json b/juick-www/src/main/js/killy/package.json
deleted file mode 100644
index 1360826d..00000000
--- a/juick-www/src/main/js/killy/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "main": "../../src/main/js/killy/index.js",
- "name": "killy",
- "version": "0.0.1",
- "private": true
-}
\ No newline at end of file
diff --git a/juick-www/src/main/resources/favicon.png b/juick-www/src/main/resources/favicon.png
deleted file mode 100644
index bc7161e2..00000000
Binary files a/juick-www/src/main/resources/favicon.png and /dev/null differ
diff --git a/juick-www/src/main/resources/logo.png b/juick-www/src/main/resources/logo.png
deleted file mode 100644
index 933f6099..00000000
Binary files a/juick-www/src/main/resources/logo.png and /dev/null differ
diff --git a/juick-www/src/main/resources/static/favicon.png b/juick-www/src/main/resources/static/favicon.png
new file mode 100644
index 00000000..bc7161e2
Binary files /dev/null and b/juick-www/src/main/resources/static/favicon.png differ
diff --git a/juick-www/src/main/resources/static/logo.png b/juick-www/src/main/resources/static/logo.png
new file mode 100644
index 00000000..933f6099
Binary files /dev/null and b/juick-www/src/main/resources/static/logo.png differ
diff --git a/juick-www/src/main/resources/static/tagscloud.png b/juick-www/src/main/resources/static/tagscloud.png
new file mode 100644
index 00000000..3e1bf169
Binary files /dev/null and b/juick-www/src/main/resources/static/tagscloud.png differ
diff --git a/juick-www/src/main/resources/tagscloud.png b/juick-www/src/main/resources/tagscloud.png
deleted file mode 100644
index 3e1bf169..00000000
Binary files a/juick-www/src/main/resources/tagscloud.png and /dev/null differ
diff --git a/juick-www/src/main/resources/templates/layouts/content.html b/juick-www/src/main/resources/templates/layouts/content.html
index 4c283116..2ca9fd7e 100644
--- a/juick-www/src/main/resources/templates/layouts/content.html
+++ b/juick-www/src/main/resources/templates/layouts/content.html
@@ -3,8 +3,8 @@
-
-
+
+
{% block headers %}
{{ headers | default('') | raw }}
{% endblock %}
diff --git a/juick-www/src/main/static/logo.png b/juick-www/src/main/static/logo.png
deleted file mode 100644
index 4e0f6d56..00000000
Binary files a/juick-www/src/main/static/logo.png and /dev/null differ
diff --git a/juick-www/src/main/static/logo@2x.png b/juick-www/src/main/static/logo@2x.png
deleted file mode 100644
index 6febeaf9..00000000
Binary files a/juick-www/src/main/static/logo@2x.png and /dev/null differ
diff --git a/juick-www/src/main/static/scripts.js b/juick-www/src/main/static/scripts.js
deleted file mode 100644
index c6293266..00000000
--- a/juick-www/src/main/static/scripts.js
+++ /dev/null
@@ -1,736 +0,0 @@
-require('whatwg-fetch');
-require('element-closest');
-require('classlist.js');
-require('url-search-params-polyfill');
-let killy = require('killy');
-let Awesomplete = require('awesomplete');
-
-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',
- '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': 'Войти через ВКонтакте',
- '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 = (window.location.protocol === 'https:' ? 'wss' : 'ws') + ':'
- + '//api.juick.com/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);
- wsIncomingReply(jsonMsg);
- } catch (err) {
- console.log(err);
- }
- }
- };
- setInterval(wsSendKeepAlive, 90000);
-}
-
-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 === undefined) ? '' : `
- `;
- let msgContHtml = `
-
-
-
${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.submit();
- }
- }
-}
-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 showCommentForm(mid, rid) {
- let reply = 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 = 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;
- });
- }
- 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 = `
-
- `;
- return openDialog(newmessageTemplate);
-}
-
-function openDialog(html, image) {
- var dialogHtml = `
- `;
- 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.juick.com/users?uname=' + uname)
- .then(function () {
- style.background = '#FFCCCC';
- })
- .catch(function () {
- style.background = '#CCFFCC';
- });
-}
-
-/******************************************************************************/
-
-function openDialogLogin() {
- let html = `
- `;
- 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('//juick.com/like?mid=' + mid, {
- method: 'POST',
- credentials: 'same-origin'
- })
- .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 setPopular(e, mid, popular) {
- fetch('//api.juick.com/messages/set_popular?mid=' + mid
- + '&popular=' + popular
- + '&hash=' + document.getElementById('body').getAttribute('data-hash'), {
- credentials: 'same-origin'
- })
- .then(function () {
- e.closest('article').append(resultMessage('OK!'));
- });
- return false;
-}
-
-function setPrivacy(e, mid) {
- fetch('//api.juick.com/messages/set_privacy?mid=' + mid
- + '&hash=' + document.getElementById('body').getAttribute('data-hash'), {
- credentials: 'same-origin'
- })
- .then(function () {
- e.closest('article').append(resultMessage('OK!'));
- });
- return false;
-}
-
-function getTags() {
- fetch('//api.juick.com/tags?hash=' + document.getElementById('body').getAttribute('data-hash'), {
- credentials: 'same-origin'
- })
- .then(response => {
- return response.json();
- })
- .then(json => {
- let tags = json.map(t => t.tag);
- let input = document.getElementById('tags_input');
- new Awesomplete(input, {list : tags});
- });
- return false;
-}
-
-function addTag(tag) {
- document.forms['postmsg'].body.value='*'+tag+' '+document.forms['postmsg'].body.value;
- return false;
-}
-
-/******************************************************************************/
-
-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();
- }
- });
- });
- }
-
- 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('.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');
- }
- });
- initWS();
-
- window.addEventListener('pagehide', wsShutdown);
-
- killy.embedAll();
-});
diff --git a/juick-www/src/main/static/style.css b/juick-www/src/main/static/style.css
deleted file mode 100644
index ce80e650..00000000
--- a/juick-www/src/main/static/style.css
+++ /dev/null
@@ -1,907 +0,0 @@
-/* #region generic */
-
-html,
-body,
-div,
-h1,
-h2,
-ul,
-li,
-p,
-form,
-input,
-textarea,
-pre {
- margin: 0;
- padding: 0;
-}
-html,
-input,
-textarea {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
- font-size: 12pt;
- -webkit-font-smoothing: subpixel-antialiased;
-}
-h1,
-h2 {
- font-weight: normal;
-}
-ul {
- list-style-type: none;
-}
-a {
- color: #069;
- text-decoration: none;
-}
-img,
-hr {
- border: none;
-}
-hr {
- background: #CCC;
- height: 1px;
- margin: 10px 0;
-}
-pre {
- background: #222;
- color: #0f0;
- overflow-x: auto;
- padding: 6px;
- white-space: pre;
-}
-pre::selection {
- background: #0f0;
- color: #222;
-}
-pre::-moz-selection {
- background: #0f0;
- color: #222;
-}
-.u {
- text-decoration: underline;
-}
-
-/* #endregion */
-
-/* #region overall layout */
-
-html {
- background: #f8f8f8;
- color: #222;
-}
-#wrapper {
- margin: 0 auto;
- width: 1000px;
- margin-top: 50px;
-}
-#column {
- float: left;
- margin-left: 10px;
- overflow: hidden;
- padding-top: 10px;
- width: 240px;
-}
-#content {
- float: right;
- margin: 12px 0 0 0;
- width: 728px;
-}
-#minimal_content {
- margin: 0 auto;
- min-width: 310px;
- width: auto;
-}
-body > header {
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.28);
- background: #fff;
- position: fixed;
- top: 0;
- width: 100%;
- z-index: 10;
-}
-#header_wrapper {
- margin: 0 auto;
- width: 1000px;
- display: flex;
-}
-#footer {
- clear: both;
- color: #999;
- font-size: 10pt;
- margin: 40px;
- padding: 10px 0;
-}
-
-@media screen and (max-width: 850px) {
- body {
- -moz-text-size-adjust: 100%;
- -webkit-text-size-adjust: 100%;
- -ms-text-size-adjust: 100%;
- }
- body,
- #wrapper,
- #topwrapper,
- #content,
- #footer {
- float: none;
- margin: 0 auto;
- min-width: 310px;
- width: auto;
- }
- #wrapper {
- margin-top: 50px;
- }
- body > header {
- margin-bottom: 15px;
- }
- #column {
- float: none;
- margin: 0 10px;
- padding-top: 0;
- width: auto;
- }
-}
-
-/* #endregion */
-
-/* #region header internals */
-
-#logo {
- height: 36px;
- margin: 7px 25px 0 20px;
- width: 110px;
-}
-#logo a {
- background: url("logo@2x.png") no-repeat;
- background-size: cover;
- border: 0;
- display: block;
- height: 36px;
- overflow: hidden;
- text-indent: 100%;
- white-space: nowrap;
- width: 110px;
-}
-#global {
- flex-grow: 1;
- display: flex;
-}
-#global a {
- color: #888;
- display: inline-block;
- font-size: 13pt;
- padding: 14px 6px;
-}
-#global li {
- display: inline-block;
-}
-#global li:hover,
-.l a:hover {
- background-color: #fff;
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
- cursor: pointer;
- transition: box-shadow 0.2s ease-in;
-}
-#search {
- margin: 12px 20px 12px 0;
-}
-#search input {
- background: #FFF;
- border: 1px solid #DDDDD5;
- outline: none !important;
- padding: 4px;
-}
-@media screen and (max-width: 850px) {
- #logo {
- display: none;
- }
- #search {
- display: inline-block;
- float: none;
- margin: 10px 10px;
- }
-}
-
-/* #endregion */
-
-/* #region left column internals */
-
-.toolbar {
- border-top: 1px solid #CCC;
-}
-
-#column ul,
-#column p,
-#column hr {
- margin: 10px 0;
-}
-#column li > a {
- display: block;
- height: 100%;
- padding: 6px;
-}
-#column li > a:hover {
- background-color: #fff;
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
- transition: background-color 0.2s ease-in;
-}
-#column .margtop {
- margin-top: 15px;
-}
-#column p {
- font-size: 10pt;
- line-height: 140%;
-}
-#column .tags {
- text-align: justify;
-}
-#column .inp {
- background: #fff;
- border: 1px solid #ddddd5;
- outline: none !important;
- padding: 4px;
- width: 222px;
-}
-#ctitle {
- font-size: 14pt;
-}
-#ctitle img {
- margin-right: 5px;
- vertical-align: middle;
- width: 48px;
-}
-#ustats li {
- font-size: 10pt;
- margin: 3px 0;
-}
-#column table.iread {
- width: 100%;
-}
-#column table.iread td {
- text-align: center;
-}
-#column table.iread img {
- height: 48px;
- width: 48px;
-}
-
-/* #endregion */
-
-/* #region main content */
-#content > p,
-#content > h1,
-#content > h2,
-#minimal_content > p,
-#minimal_content > h1,
-#minimal_content > h2 {
- margin: 1em 0;
-}
-.page {
- background: #eee;
- padding: 6px;
- text-align: center;
-}
-
-.page a {
- color: #888;
-}
-
-/* #endregion */
-
-/* #region article, message internals */
-
-article {
- background: #fff;
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
- line-height: 140%;
- margin-bottom: 10px;
- padding: 20px;
-}
-article time {
- color: #999;
- font-size: 10pt;
-}
-article p {
- clear: left;
- margin: 5px 0 15px 0;
- word-wrap: break-word;
- overflow-wrap: break-word;
-}
-article .ir {
- text-align: center;
-}
-article .ir a {
- cursor: zoom-in;
- display: block;
-}
-article .ir img {
- max-width: 100%;
-}
-article > nav.l,
-.msg-cont > nav.l {
- border-top: 1px solid #eee;
- display: flex;
- justify-content: space-around;
- font-size: 10pt;
-}
-article > nav.l a,
-.msg-cont > nav.l a {
- color: #888;
- margin-right: 15px;
-}
-article .likes {
- padding-left: 20px;
-}
-article .replies {
- margin-left: 18px;
-}
-article .tags {
- margin-top: 3px;
-}
-.msg-tags {
- margin-top: 12px;
- min-height: 1px;
-}
-article .tags > a,
-.msg-tags > a {
- background: #eee;
- border: 1px solid #eee;
- color: #888;
- display: inline-block;
- font-size: 10pt;
- margin-bottom: 5px;
- margin-right: 5px;
- padding: 0 10px;
-}
-.l .msg-button {
- align-items: center;
- display: flex;
- flex-basis: 0;
- flex-direction: column;
- flex-grow: 1;
- padding-top: 12px;
-}
-.l .msg-button-icon {
- font-weight: bold;
-}
-.msgthread {
- margin-bottom: 0;
-}
-.msg-avatar {
- float: left;
- height: 48px;
- margin-right: 10px;
- width: 48px;
-}
-.msg-avatar img {
- height: 48px;
- vertical-align: top;
- width: 48px;
-}
-.msg-cont {
- background: #FFF;
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.16);
- line-height: 140%;
- margin-bottom: 12px;
- padding: 20px;
- width: 640px;
-}
-.reply-new .msg-cont {
- border-right: 5px solid #0C0;
-}
-.msg-ts {
- font-size: small;
- vertical-align: top;
-}
-.msg-ts,
-.msg-ts > a {
- color: #999;
-}
-.msg-txt {
- clear: both;
- margin: 0 0 12px;
- padding-top: 10px;
- word-wrap: break-word;
- overflow-wrap: break-word;
-}
-.msg-media {
- text-align: center;
-}
-.msg-links {
- color: #999;
- font-size: small;
- margin: 5px 0 0 0;
-}
-.msg-comments {
- color: #AAA;
- font-size: small;
- margin-top: 10px;
- overflow: hidden;
- text-indent: 10px;
-}
-.ta-wrapper {
- border: 1px solid #DDD;
- display: flex;
- flex-grow: 1;
-}
-.msg-comment {
- display: flex;
- width: 100%;
- margin-top: 10px;
-}
-.msg-comment-hidden {
- display: none;
-}
-.msg-comment textarea {
- border: 0;
- flex-grow: 1;
- outline: none !important;
- padding: 4px;
- resize: vertical;
- vertical-align: top;
-}
-.attach-photo {
- cursor: pointer;
-}
-.attach-photo-active {
- color: green;
-}
-.msg-comment input {
- align-self: flex-start;
- background: #EEE;
- border: 1px solid #CCC;
- color: #999;
- margin: 0 0 0 6px;
- position: -webkit-sticky;
- position: sticky;
- top: 70px;
- vertical-align: top;
- width: 50px;
-}
-.msg-recomms {
- color: #AAA;
- font-size: small;
- margin-top: 10px;
- overflow: hidden;
- text-indent: 10px;
-}
-#replies .msg-txt,
-#private-messages .msg-txt {
- margin: 0;
-}
-.title2 {
- background: #fff;
- margin: 20px 0;
- padding: 10px 20px;
- width: 640px;
-}
-.title2-right {
- float: right;
- line-height: 24px;
-}
-#content .title2 h2 {
- font-size: x-large;
- margin: 0;
-}
-
-@media screen and (max-width: 850px) {
- article {
- overflow: auto;
- }
- article p {
- margin: 10px 0 8px 0;
- }
- .msg,
- .msg-cont {
- min-width: 280px;
- width: auto;
- }
- .msg-cont {
- margin: 8px 0;
- }
- .msg-media {
- overflow: auto;
- }
- .title2 h2 {
- font-size: large;
- }
- .msg-comment {
- flex-direction: column;
- }
- .msg-comment input {
- align-self: flex-end;
- margin: 6px 0 0 0;
- width: 100px;
- }
-}
-
-@media screen and (max-width: 480px) {
- .msg-txt {
- padding-top: 5px;
- }
- .title2 {
- font-size: 11pt;
- width: auto;
- }
- #content .title2 h2 {
- font-size: 11pt;
- }
- .title2-right {
- line-height: initial;
- }
-}
-
-/* #endregion */
-
-/* #region user-generated texts */
-
-q:before,
-q:after {
- content: "";
-}
-q,
-blockquote {
- border-left: 3px solid #CCC;
- color: #666;
- display: block;
- margin: 10px 0 10px 10px;
- padding-left: 10px;
-}
-
-/* #endregion */
-
-/* #region new message form internals */
-
-#newmessage {
- background: #E5E5E0;
- margin-bottom: 20px;
- padding: 15px;
-}
-#newmessage textarea {
- border: 1px solid #CCC;
- box-sizing: border-box;
- margin: 0 0 5px 0;
- margin-top: 20px;
- max-height: 6em;
- min-width: 280px;
- padding: 4px;
- width: 100%;
-}
-#newmessage input {
- border: 1px solid #CCC;
- margin: 5px 0;
- padding: 2px 4px;
-}
-#newmessage .img {
- width: 500px;
-}
-#newmessage .tags {
- width: 500px;
-}
-#newmessage .subm {
- background: #EEEEE5;
- width: 150px;
-}
-@media screen and (max-width: 850px) {
- #newmessage .img,
- #newmessage .tags {
- width: 100%;
- }
-}
-
-/* #endregion */
-
-/* #region user lists */
-
-.users {
- margin: 10px 0;
- width: 100%;
- display: flex;
- flex-wrap: wrap;
-}
-.users > span {
- overflow: hidden;
- padding: 6px 0;
- width: 200px;
-}
-.users img {
- height: 32px;
- margin-right: 6px;
- vertical-align: middle;
- width: 32px;
-}
-
-/* #endregion */
-
-/* #region signup form */
-
-.signup-h1 > img {
- margin-right: 10px;
- vertical-align: middle;
-}
-.signup-h1 {
- font-size: x-large;
- margin: 20px 0 10px 0;
-}
-.signup-h2 {
- font-size: large;
- margin: 10px 0 5px 0;
-}
-.signup-hr {
- margin: 20px 0;
-}
-
-/* #endregion */
-
-/* #region PM */
-
-.newpm {
- margin: 20px 60px 30px 60px;
-}
-.newpm textarea {
- resize: vertical;
- width: 100%;
-}
-.newpm-send input {
- width: 100px;
-}
-
-/* #endregion */
-
-/* #region popup dialog (lightbox) */
-
-#dialogb {
- background: #222;
- height: 100%;
- left: 0;
- opacity: 0.6;
- position: fixed;
- top: 0;
- width: 100%;
- z-index: 10;
-}
-#dialogt {
- height: 100%;
- left: 0;
- position: fixed;
- top: 0;
- width: 100%;
- z-index: 10;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-#dialogw {
- z-index: 11;
- max-width: 96%;
- max-height: calc(100% - 100px);
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
-}
-#dialogw a {
- display: block;
-}
-#dialogw img {
- max-height: 100%;
- max-height: 90vh;
- max-width: 100%;
-}
-#dialog_header {
- width: 100%;
- height: 44px;
- position: fixed;
- display: flex;
- flex-direction: row-reverse;
- align-items: center;
-}
-.header_image {
- background: rgba(0, 0, 0, 0.28);
-}
-#dialogc {
- cursor: pointer;
- color: #ccc;
- padding-right: 6px;
-}
-.dialoglogin {
- background: #fff;
- padding: 25px;
- width: 300px;
-}
-.dialog-opened {
- overflow: hidden;
-}
-#signfb,
-#signvk {
- display: block;
- line-height: 32px;
- margin: 10px 0;
- text-decoration: none;
- width: 100%;
-}
-#signvk {
- margin-bottom: 30px;
-}
-.dialoglogin form {
- margin-top: 7px;
-}
-.signinput,
-.signsubmit {
- border: 1px solid #CCC;
- margin: 3px 0;
- padding: 3px;
-}
-.signinput {
- width: 292px;
-}
-.signsubmit {
- width: 70px;
-}
-.dialogshare {
- background: #fff;
- min-width: 300px;
- overflow: auto;
- padding: 20px;
-}
-.dialogl {
- background: #fff;
- border: 1px solid #DDD;
- margin: 3px 0 20px;
- padding: 5px;
-}
-.dialogshare li {
- float: left;
- margin: 5px 10px 0 0;
-}
-.dialogshare a {
- display: block;
-}
-.dialogtxt {
- background: #fff;
- padding: 20px;
-}
-
-@media screen and (max-width: 480px) {
- .dialog-opened {
- position: fixed;
- width: 100%;
- }
-}
-
-/* #endregion */
-
-/* #region misc */
-
-#wsthread {
- background: #CCC;
- bottom: 20px;
- cursor: pointer;
- display: none;
- padding: 5px 10px;
- position: fixed;
- right: 20px;
-}
-.sharenew {
- display: inline-block;
- line-height: 32px;
- min-height: 32px;
- min-width: 200px;
- padding: 0 12px 0 37px;
-}
-.icon {
- margin-top: -2px;
- vertical-align: middle;
-}
-.icon--ei-link {
- margin-top: -1px;
-}
-.icon--ei-comment {
- margin-top: -5px;
-}
-.newmessage {
- /* textarea on the /post page */
- border: 1px solid #DDD;
- padding: 2px;
- resize: vertical;
- width: 100%;
-}
-
-/* #endregion */
-
-/* #region footer internals */
-
-#footer-social {
- float: left;
-}
-#footer-social a {
- border: 0;
- display: inline-block;
-}
-#footer-left {
- margin-left: 286px;
- margin-right: 350px;
-}
-#footer-right {
- float: right;
-}
-
-@media screen and (max-width: 850px) {
- #footer {
- margin: 0 10px;
- }
- #footer div {
- float: none;
- margin: 10px 0;
- }
-}
-
-/* #endregion */
-
-/* #region settings */
-
-fieldset {
- border: 1px dotted #ccc;
- margin-top: 25px;
-}
-
-/* #endregion */
-
-/* #region embeds */
-
-.embedContainer {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: center;
- padding: 0;
- margin: 30px -3px 15px -3px;
-}
-.embedContainer > * {
- box-sizing: border-box;
- flex-grow: 1;
- margin: 3px;
- min-width: 49%;
-}
-.embedContainer > .compact {
- flex-grow: 0;
-}
-.embedContainer .picture img {
- display: block;
-}
-.embedContainer img,
-.embedContainer video {
- max-width: 100%;
- max-height: 80vh;
-}
-.embedContainer > .audio,
-.embedContainer > .youtube {
- min-width: 90%;
-}
-.embedContainer audio {
- width: 100%;
-}
-.embedContainer iframe {
- overflow: hidden;
- resize: vertical;
- display: block;
-}
-
-/* #endregion */
-
-/* #region nsfw */
-
-article.nsfw .embedContainer img,
-article.nsfw .embedContainer video,
-article.nsfw .embedContainer iframe,
-article.nsfw .ir img {
- opacity: 0.1;
-}
-article.nsfw .embedContainer img:hover,
-article.nsfw .embedContainer video:hover,
-article.nsfw .embedContainer iframe:hover,
-article.nsfw .ir img:hover {
- opacity: 1;
-}
-
-/* #endregion */
--
cgit v1.2.3