diff --git a/assets/js/misuzu/__extensions.js b/assets/js/misuzu/__extensions.js index 962bcc27..7eff7e23 100644 --- a/assets/js/misuzu/__extensions.js +++ b/assets/js/misuzu/__extensions.js @@ -15,6 +15,40 @@ Array.prototype.removeFind = function(predicate) { return this; }; +HTMLCollection.prototype.toArray = function() { + return Array.prototype.slice.call(this); +}; + +HTMLTextAreaElement.prototype.insertTags = function(tagOpen, tagClose) { + tagOpen = tagOpen || ''; + tagClose = tagClose || ''; + + if(document.selection) { + this.focus(); + var selected = document.selection.createRange(); + selected.text = tagOpen + selected.text + tagClose; + this.focus(); + } else if(this.selectionStart || this.selectionStart === 0) { + var startPos = this.selectionStart, + endPos = this.selectionEnd, + scrollTop = this.scrollTop; + + this.value = this.value.substring(0, startPos) + + tagOpen + + this.value.substring(startPos, endPos) + + tagClose + + this.value.substring(endPos, this.value.length); + + this.focus(); + this.selectionStart = startPos + tagOpen.length; + this.selectionEnd = endPos + tagOpen.length; + this.scrollTop + scrollTop; + } else { + this.value += tagOpen + tagClose; + this.focus(); + } +}; + var CreateElement = function(elemInfo) { elemInfo = elemInfo || {}; var elem = document.createElement(elemInfo.tag || 'div'); diff --git a/assets/js/misuzu/_main.js b/assets/js/misuzu/_main.js index aa673e7f..36ab2b35 100644 --- a/assets/js/misuzu/_main.js +++ b/assets/js/misuzu/_main.js @@ -4,7 +4,7 @@ var Misuzu = function() { Misuzu.started = true; console.log( - "%cMisuzu", + "%cMisuzu%c\nhttps://github.com/flashwave/misuzu", 'font-size: 48px; color: #8559a5; background: #111;' + 'border-radius: 5px; padding: 0 10px; text-shadow: 0 0 1em #fff;', ); @@ -12,11 +12,15 @@ var Misuzu = function() { timeago.render(document.querySelectorAll('time')); hljs.initHighlighting(); - //initCSRF(); - //urlRegistryInit(); + Misuzu.CSRF.init(); + Misuzu.Urls.loadFromDocument(); Misuzu.User.refreshLocalUser(); - //userRelationsInit(); - //initDataRequestMethod(); + Misuzu.UserRelations.init(); + Misuzu.FormUtils.initDataRequestMethod(); + Misuzu.initQuickSubmit(); + Misuzu.Comments.init(); + Misuzu.Forum.Editor.init(); + Misuzu.Forum.Polls.init(); if(Misuzu.User.isLoggedIn()) console.log( @@ -29,5 +33,122 @@ var Misuzu = function() { console.log('You aren\'t logged in.'); Misuzu.Events.dispatch(); + + Misuzu.initLoginPage(); }; +Misuzu.Parser = DefineEnum({ + plain: 0, + bbcode: 1, + markdown: 2, +}); Misuzu.supportsSidewaysText = function() { return CSS.supports('writing-mode', 'sideways-lr'); }; +Misuzu.showMessageBox = function(text, title, buttons) { + if(document.querySelector('.messagebox')) + return false; + + text = text || ''; + title = title || ''; + buttons = buttons || []; + + var element = document.createElement('div'); + element.className = 'messagebox'; + + var container = element.appendChild(document.createElement('div')); + container.className = 'container messagebox__container'; + + var titleElement = container.appendChild(document.createElement('div')), + titleBackground = titleElement.appendChild(document.createElement('div')), + titleText = titleElement.appendChild(document.createElement('div')); + + titleElement.className = 'container__title'; + titleBackground.className = 'container__title__background'; + titleText.className = 'container__title__text'; + titleText.textContent = title || 'Information'; + + var textElement = container.appendChild(document.createElement('div')); + textElement.className = 'container__content'; + textElement.textContent = text; + + var buttonsContainer = container.appendChild(document.createElement('div')); + buttonsContainer.className = 'messagebox__buttons'; + + var firstButton = null; + + if(buttons.length < 1) { + firstButton = buttonsContainer.appendChild(document.createElement('button')); + firstButton.className = 'input__button'; + firstButton.textContent = 'OK'; + firstButton.addEventListener('click', function() { element.remove(); }); + } else { + for(var i = 0; i < buttons.length; i++) { + var button = buttonsContainer.appendChild(document.createElement('button')); + button.className = 'input__button'; + button.textContent = buttons[i].text; + button.addEventListener('click', function() { + element.remove(); + buttons[i].callback(); + }); + + if(firstButton === null) + firstButton = button; + } + } + + document.body.appendChild(element); + firstButton.focus(); + return true; +}; +Misuzu.initLoginPage = function() { + var updateForm = function(avatarElem, usernameElem) { + var xhr = new XMLHttpRequest; + xhr.addEventListener('readystatechange', function() { + if(xhr.readyState !== 4) + return; + + avatarElem.src = Misuzu.Urls.format('user-avatar', [ + { name: 'user', value: xhr.responseText.indexOf('<') !== -1 ? '0' : xhr.responseText }, + { name: 'res', value: 100 }, + ]); + }); + xhr.open('GET', Misuzu.Urls.format('auth-resolve-user', [{name: 'username', value: encodeURIComponent(usernameElem.value)}])); + xhr.send(); + }; + + var loginForms = document.getElementsByClassName('js-login-form'); + + for(var i = 0; i < loginForms.length; ++i) + (function(form) { + var loginTimeOut = 0, + loginAvatar = form.querySelector('.js-login-avatar'), + loginUsername = form.querySelector('.js-login-username'); + + updateForm(loginAvatar, loginUsername); + loginUsername.addEventListener('keyup', function() { + if(loginTimeOut) + return; + loginTimeOut = setTimeout(function() { + updateForm(loginAvatar, loginUsername); + clearTimeout(loginTimeOut); + loginTimeOut = 0; + }, 750); + }); + })(loginForms[i]); +}; +Misuzu.initQuickSubmit = function() { + var ctrlSubmit = document.getElementsByClassName('js-quick-submit').toArray().concat(document.getElementsByClassName('js-ctrl-enter-submit').toArray()); + if(!ctrlSubmit) + return; + + for(var i = 0; i < ctrlSubmit.length; ++i) + ctrlSubmit[i].addEventListener('keydown', function(ev) { + if((ev.code === 'Enter' || ev.code === 'NumpadEnter') // i hate this fucking language so much + && ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) { + // hack: prevent forum editor from screaming when using this keycombo + // can probably be done in a less stupid manner + Misuzu.Forum.Editor.allowWindowClose = true; + + this.form.submit(); + ev.preventDefault(); + } + }); +}; diff --git a/assets/js/misuzu/comments.js b/assets/js/misuzu/comments.js new file mode 100644 index 00000000..e405a70c --- /dev/null +++ b/assets/js/misuzu/comments.js @@ -0,0 +1,491 @@ +Misuzu.Comments = {}; +Misuzu.Comments.Vote = DefineEnum({ + none: 0, + like: 1, + dislike: -1, +}); +Misuzu.Comments.init = function() { + var commentDeletes = document.getElementsByClassName('comment__action--delete'); + for(var i = 0; i < commentDeletes.length; ++i) { + commentDeletes[i].addEventListener('click', Misuzu.Comments.deleteCommentHandler); + commentDeletes[i].dataset.href = commentDeletes[i].href; + commentDeletes[i].href = 'javascript:;'; + } + + var commentInputs = document.getElementsByClassName('comment__text--input'); + for(var i = 0; i < commentInputs.length; ++i) { + commentInputs[i].form.action = ''; + commentInputs[i].form.addEventListener('submit', Misuzu.Comments.postCommentHandler); + commentInputs[i].addEventListener('keydown', Misuzu.Comments.inputCommentHandler); + } + + var voteButtons = document.getElementsByClassName('comment__action--vote'); + for(var i = 0; i < voteButtons.length; ++i) { + voteButtons[i].href = 'javascript:;'; + voteButtons[i].addEventListener('click', Misuzu.Comments.voteCommentHandler); + } + + var pinButtons = document.getElementsByClassName('comment__action--pin'); + for(var i = 0; i < pinButtons.length; ++i) { + pinButtons[i].href = 'javascript:;'; + pinButtons[i].addEventListener('click', Misuzu.Comments.pinCommentHandler); + } +}; +Misuzu.Comments.postComment = function(formData, onSuccess, onFailure) { + if(!Misuzu.User.isLoggedIn() + || !Misuzu.User.localUser.perms.canCreateComment()) { + if(onFailure) + onFailure("You aren't allowed to post comments."); + return; + } + + var xhr = new XMLHttpRequest; + xhr.addEventListener('readystatechange', function() { + if(xhr.readyState !== 4) + return; + + Misuzu.CSRF.setToken(xhr.getResponseHeader('X-Misuzu-CSRF')); + + var json = JSON.parse(xhr.responseText), + message = json.error || json.message; + + if(message && onFailure) + onFailure(message); + else if(!message && onSuccess) + onSuccess(json); + }); + xhr.open('POST', Misuzu.Urls.format('comment-create')); + xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); + xhr.send(formData); +}; +Misuzu.Comments.postCommentHandler = function() { + if(this.dataset.disabled) + return; + this.dataset.disabled = '1'; + this.style.opacity = '0.5'; + + Misuzu.Comments.postComment( + Misuzu.FormUtils.extractFormData(this, true), + Misuzu.Comments.postCommentSuccess.bind(this), + Misuzu.Comments.postCommentFailed.bind(this) + ); +}; +Misuzu.Comments.inputCommentHandler = function(ev) { + if(ev.code === 'Enter' && ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) { + Misuzu.Comments.postComment( + Misuzu.FormUtils.extractFormData(this.form, true), + Misuzu.Comments.postCommentSuccess.bind(this.form), + Misuzu.Comments.postCommentFailed.bind(this.form) + ); + } +}; +Misuzu.Comments.postCommentSuccess = function(comment) { + if(this.classList.contains('comment--reply')) + this.parentNode.parentNode.querySelector('label.comment__action').click(); + + Misuzu.Comments.insertComment(comment, this); + this.style.opacity = '1'; + this.dataset.disabled = ''; +}; +Misuzu.Comments.postCommentFailed = function(message) { + Misuzu.showMessageBox(message); + this.style.opacity = '1'; + this.dataset.disabled = ''; +}; +Misuzu.Comments.deleteComment = function(commentId, onSuccess, onFailure) { + if(!Misuzu.User.isLoggedIn() + || !Misuzu.User.localUser.perms.canDeleteOwnComment()) { + if(onFailure) + onFailure('You aren\'t allowed to delete comments.'); + return; + } + + var xhr = new XMLHttpRequest; + xhr.addEventListener('readystatechange', function() { + if(xhr.readyState !== 4) + return; + + Misuzu.CSRF.setToken(xhr.getResponseHeader('X-Misuzu-CSRF')); + + var json = JSON.parse(xhr.responseText), + message = json.error || json.message; + + if(message && onFailure) + onFailure(message); + else if(!message && onSuccess) + onSuccess(json); + }); + xhr.open('GET', Misuzu.Urls.format('comment-delete', [Misuzu.Urls.v('comment', commentId)])); + xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); + xhr.send(); +}; +Misuzu.Comments.deleteCommentHandler = function() { + var commentId = parseInt(this.dataset.commentId); + if(commentId < 1) + return; + + Misuzu.Comments.deleteComment( + commentId, + function(info) { + var elem = document.getElementById('comment-' + info.id); + + if(elem) + elem.parentNode.removeChild(elem); + }, + function(message) { Misuzu.showMessageBox(message); } + ); +}; +Misuzu.Comments.pinComment = function(commentId, pin, onSuccess, onFailure) { + if(!Misuzu.User.isLoggedIn() + || !Misuzu.User.localUser.perms.canPinComment()) { + if(onFailure) + onFailure("You aren't allowed to pin comments."); + return; + } + + var xhr = new XMLHttpRequest; + xhr.onreadystatechange = function() { + if(xhr.readyState !== 4) + return; + + Misuzu.CSRF.setToken(xhr.getResponseHeader('X-Misuzu-CSRF')); + + var json = JSON.parse(xhr.responseText), + message = json.error || json.message; + + if(message && onFailure) + onFailure(message); + else if(!message && onSuccess) + onSuccess(json); + }; + xhr.open('GET', Misuzu.Urls.format('comment-' + (pin ? 'pin' : 'unpin'), [Misuzu.Urls.v('comment', commentId)])); + xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); + xhr.send(); +}; +Misuzu.Comments.pinCommentHandler = function() { + var target = this, + commentId = parseInt(target.dataset.commentId), + isPinned = target.dataset.commentPinned !== '0'; + + target.textContent = '...'; + + Misuzu.Comments.pinComment( + commentId, + !isPinned, + function(info) { + if(info.comment_pinned === null) { + target.textContent = 'Pin'; + target.dataset.commentPinned = '0'; + var pinElement = document.querySelector('#comment-' + info.comment_id + ' .comment__pin'); + pinElement.parentElement.removeChild(pinElement); + } else { + target.textContent = 'Unpin'; + target.dataset.commentPinned = '1'; + + var pinInfo = document.querySelector('#comment-' + info.comment_id + ' .comment__info'), + pinElement = document.createElement('div'), + pinTime = document.createElement('time'), + pinDateTime = new Date(info.comment_pinned + 'Z'); + + pinTime.title = pinDateTime.toLocaleString(); + pinTime.dateTime = pinDateTime.toISOString(); + pinTime.textContent = timeago.format(pinDateTime); + timeago.render(pinTime); + + pinElement.className = 'comment__pin'; + pinElement.appendChild(document.createTextNode('Pinned ')); + pinElement.appendChild(pinTime); + pinInfo.appendChild(pinElement); + } + }, + function(message) { + target.textContent = isPinned ? 'Unpin' : 'Pin'; + Misuzu.showMessageBox(message); + } + ); +}; +Misuzu.Comments.voteComment = function(commentId, vote, onSuccess, onFailure) { + if(!Misuzu.User.isLoggedIn() + || !Misuzu.User.localUser.perms.canVoteOnComment()) { + if(onFailure) + onFailure("You aren't allowed to vote on comments."); + return; + } + + var xhr = new XMLHttpRequest; + xhr.onreadystatechange = function() { + if(xhr.readyState !== 4) + return; + + Misuzu.CSRF.setToken(xhr.getResponseHeader('X-Misuzu-CSRF')); + + var json = JSON.parse(xhr.responseText), + message = json.error || json.message; + + if(message && onFailure) + onFailure(message); + else if(!message && onSuccess) + onSuccess(json); + }; + xhr.open('GET', Misuzu.Urls.format('comment-vote', [Misuzu.Urls.v('comment', commentId), Misuzu.Urls.v('vote', vote)])); + xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); + xhr.send(); +}; +Misuzu.Comments.voteCommentHandler = function() { + var commentId = parseInt(this.dataset.commentId), + voteType = parseInt(this.dataset.commentVote), + buttons = document.querySelectorAll('.comment__action--vote[data-comment-id="' + commentId + '"]'), + likeButton = document.querySelector('.comment__action--like[data-comment-id="' + commentId + '"]'), + dislikeButton = document.querySelector('.comment__action--dislike[data-comment-id="' + commentId + '"]'), + classVoted = 'comment__action--voted'; + + for(var i = 0; i < buttons.length; ++i) { + buttons[i].textContent = buttons[i] === this ? '...' : ''; + buttons[i].classList.remove(classVoted); + buttons[i].dataset.commentVote = buttons[i] === likeButton + ? (voteType === Misuzu.Comments.Vote.like ? Misuzu.Comments.Vote.none : Misuzu.Comments.Vote.like ).toString() + : (voteType === Misuzu.Comments.Vote.dislike ? Misuzu.Comments.Vote.none : Misuzu.Comments.Vote.dislike).toString(); + } + + Misuzu.Comments.voteComment( + commentId, + voteType, + function(info) { + switch(voteType) { + case Misuzu.Comments.Vote.like: + likeButton.classList.add(classVoted); + break; + case Misuzu.Comments.Vote.dislike: + dislikeButton.classList.add(classVoted); + break; + } + + likeButton.textContent = info.likes > 0 ? ('Like (' + info.likes.toLocaleString() + ')') : 'Like'; + dislikeButton.textContent = info.dislikes > 0 ? ('Dislike (' + info.dislikes.toLocaleString() + ')') : 'Dislike'; + }, + function(message) { + likeButton.textContent = 'Like'; + dislikeButton.textContent = 'Dislike'; + Misuzu.showMessageBox(message); + } + ); +}; +Misuzu.Comments.insertComment = function(comment, form) { + var isReply = form.classList.contains('comment--reply'), + parent = isReply + ? form.parentElement + : form.parentElement.parentElement.getElementsByClassName('comments__listing')[0], + repliesIndent = isReply + ? (parseInt(parent.classList[1].substr(25)) + 1) + : 1, + commentElement = Misuzu.Comments.buildComment(comment, repliesIndent); + + if(isReply) + parent.appendChild(commentElement); + else + parent.insertBefore(commentElement, parent.firstElementChild); + + var placeholder = document.getElementById('_no_comments_notice_' + comment.category_id); + if(placeholder) + placeholder.parentNode.removeChild(placeholder); +}; +Misuzu.Comments.buildComment = function(comment, layer) { + comment = comment || {}; + layer = parseInt(layer || 0); + + var date = new Date(comment.comment_created + 'Z'), + colour = new Misuzu.Colour(comment.user_colour), + actions = [], + commentTime = CreateElement({ + tag: 'time', + props: { + className: 'comment__date', + title: date.toLocaleString(), + datetime: date.toISOString(), + }, + children: timeago.format(date), + }); + + if(Misuzu.User.isLoggedIn() && Misuzu.User.localUser.perms.canVoteOnComment()) { + actions.push(CreateElement({ + tag: 'a', + props: { + className: 'comment__action comment__action--link comment__action--vote comment__action--like', + 'data-comment-id': comment.comment_id, + 'data-comment-vote': Misuzu.Comments.Vote.like, + href: 'javascript:;', + onclick: Misuzu.Comments.voteCommentHandler, + }, + children: 'Like', + })); + actions.push(CreateElement({ + tag: 'a', + props: { + className: 'comment__action comment__action--link comment__action--vote comment__action--dislike', + 'data-comment-id': comment.comment_id, + 'data-comment-vote': Misuzu.Comments.Vote.dislike, + href: 'javascript:;', + onclick: Misuzu.Comments.voteCommentHandler, + }, + children: 'Dislike', + })); + } + + actions.push(CreateElement({ + tag: 'label', + props: { + className: 'comment__action comment__action--link', + 'for': 'comment-reply-toggle-' + comment.comment_id.toString() + }, + children: 'Reply', + })); + + var commentText = CreateBasicElement('comment__text'); + if(comment.comment_html) + commentText.innerHTML = comment.comment_html; + else + commentText.textContent = comment.comment_text; + + var commentElem = CreateElement({ + props: { + className: 'comment', + id: 'comment-' + comment.comment_id.toString(), + }, + children: [ + { + props: { className: 'comment__container', }, + children: [ + { + tag: 'a', + props: { + className: 'comment__avatar', + href: Misuzu.Urls.format('user-profile', [{name:'user',value:comment.user_id}]), + }, + children: { + tag: 'img', + props: { + className: 'avatar', + alt: comment.username, + width: (layer <= 1 ? 50 : 40), + height: (layer <= 1 ? 50 : 40), + src: Misuzu.Urls.format('user-avatar', [ + { name: 'user', value: comment.user_id }, + { name: 'res', value: layer <= 1 ? 100 : 80 } + ]), + }, + }, + }, + { + props: { className: 'comment__content', }, + children: [ + { + props: { className: 'comment__info', }, + children: [ + { + tag: 'a', + props: { + className: 'comment__user comment__user--link', + href: Misuzu.Urls.format('user-profile', [{name:'user',value:comment.user_id}]), + style: '--user-colour: ' + colour.getCSS(), + }, + children: comment.username, + }, + { + tag: 'a', + props: { + className: 'comment__link', + href: '#comment-' + comment.comment_id.toString(), + }, + children: commentTime, + }, + ], + }, + commentText, + { + props: { className: 'comment__actions', }, + children: actions, + }, + ], + }, + ], + }, + { + props: { + className: 'comment__replies comment__replies--indent-' + layer.toString(), + id: 'comment-' + comment.comment_id.toString() + '-replies', + }, + children: [ + { + tag: 'input', + props: { + className: 'comment__reply-toggle', + type: 'checkbox', + id: ('comment-reply-toggle-' + comment.comment_id.toString()), + }, + }, + { + tag: 'form', + props: { + className: 'comment comment--input comment--reply', + id: 'comment-reply-' + comment.comment_id.toString(), + method: 'post', + action: 'javascript:;', + onsubmit: Misuzu.Comments.postCommentHandler, + }, + children: [ + { tag: 'input', props: { type: 'hidden', name: 'csrf', value: Misuzu.CSRF.getToken() } }, + { tag: 'input', props: { type: 'hidden', name: 'comment[category]', value: comment.category_id } }, + { tag: 'input', props: { type: 'hidden', name: 'comment[reply]', value: comment.comment_id } }, + { + props: { className: 'comment__container' }, + children: [ + { + props: { className: 'avatar comment__avatar' }, + children: { + tag: 'img', + props: { + className: 'avatar', + width: 40, + height: 40, + src: Misuzu.Urls.format('user-avatar', [{name: 'user', value: comment.user_id}, {name: 'res', value: 80}]), + }, + }, + }, + { + props: { className: 'comment__content' }, + children: [ + { props: { className: 'comment__info' } }, + { + tag: 'textarea', + props: { + className: 'comment__text input__textarea comment__text--input', + name: 'comment[text]', + placeholder: 'Share your extensive insights...', + onkeydown: Misuzu.Comments.inputCommentHandler, + }, + }, + { + props: { className: 'comment__actions' }, + children: { + tag: 'button', + props: { + className: 'input__button comment__action comment__action--button comment__action--post', + }, + children: 'Reply', + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }); + + timeago.render(commentTime); + + return commentElem; +}; \ No newline at end of file diff --git a/assets/js/misuzu/csrf.js b/assets/js/misuzu/csrf.js new file mode 100644 index 00000000..e022059e --- /dev/null +++ b/assets/js/misuzu/csrf.js @@ -0,0 +1,17 @@ +Misuzu.CSRF = {}; +Misuzu.CSRF.tokenValue = undefined; +Misuzu.CSRF.tokenElement = undefined; +Misuzu.CSRF.init = function() { + Misuzu.CSRF.tokenElement = document.querySelector('[name="csrf-token"]'); + Misuzu.CSRF.tokenValue = Misuzu.CSRF.tokenElement.getAttribute('value'); +}; +Misuzu.CSRF.getToken = function() { return Misuzu.CSRF.tokenValue || ''; }; +Misuzu.CSRF.setToken = function(token) { + if(!token) + return; + Misuzu.CSRF.tokenElement.setAttribute('value', Misuzu.CSRF.tokenValue = token); + + var elems = document.getElementsByName('csrf'); + for(var i = 0; i < elems.length; ++i) + elems[i].value = token; +}; diff --git a/assets/js/misuzu/formutils.js b/assets/js/misuzu/formutils.js new file mode 100644 index 00000000..27e7f209 --- /dev/null +++ b/assets/js/misuzu/formutils.js @@ -0,0 +1,81 @@ +Misuzu.FormUtils = {}; +Misuzu.FormUtils.extractFormData = function(form, resetSource) { + var formData = new FormData; + + for(var i = 0; i < form.length; ++i) { + if(form[i].type.toLowerCase() === 'checkbox' && !form[i].checked) + continue; + formData.append(form[i].name, form[i].value || ''); + } + + if(resetSource) + Misuzu.FormUtils.resetFormData(form); + + return formData; +}; +Misuzu.FormUtils.resetFormData = function(form, defaults) { + defaults = defaults || []; + + for(var i = 0; i < form.length; ++i) { + var input = form[i]; + + switch(input.type.toLowerCase()) { + case 'checkbox': + input.checked = false; + break; + + case 'hidden': + var hiddenDefault = defaults.find(function(fhd) { return fhd.Name.toLowerCase() === input.name.toLowerCase(); }); + if(hiddenDefault) + input.value = hiddenDefault.Value; + break; + + default: + input.value = ''; + } + } +}; +Misuzu.FormUtils.initDataRequestMethod = function() { + var links = document.links; + + for(var i = 0; i < links.length; ++i) { + if(!links[i].href || !links[i].dataset || !links[i].dataset.mszMethod) + continue; + + links[i].addEventListener('click', function(ev) { + Misuzu.FormUtils.handleDataRequestMethod(this, this.dataset.mszMethod, this.href); + ev.preventDefault(); + }); + } +}; +Misuzu.FormUtils.handleDataRequestMethod = function(elem, method, url) { + var split = url.split('?', 2), + target = split[0], + query = split[1] || null; + + if(elem.getAttribute('disabled')) + return; + elem.setAttribute('disabled', 'disabled'); + + var xhr = new XMLHttpRequest; + xhr.onreadystatechange = function(ev) { + if(xhr.readyState !== 4) + return; + elem.removeAttribute('disabled'); + + if(xhr.status === 301 || xhr.status === 302 || xhr.status === 307 || xhr.status === 308) { + location.assign(xhr.getResponseHeader('X-Misuzu-Location')); + return; + } + + if(xhr.status >= 400 && xhr.status <= 599) { + alert(xhr.responseText); + return; + } + }; + xhr.open(method, target); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.setRequestHeader('X-Misuzu-CSRF', Misuzu.CSRF.getToken()); + xhr.setRequestHeader('X-Misuzu-XHR', '1'); + xhr.send(query); +}; diff --git a/assets/js/misuzu/forum/_forum.js b/assets/js/misuzu/forum/_forum.js new file mode 100644 index 00000000..52d6f67e --- /dev/null +++ b/assets/js/misuzu/forum/_forum.js @@ -0,0 +1 @@ +Misuzu.Forum = {}; diff --git a/assets/typescript/Forum/Posting.ts b/assets/js/misuzu/forum/editor.js similarity index 50% rename from assets/typescript/Forum/Posting.ts rename to assets/js/misuzu/forum/editor.js index 4862001d..ff1f3334 100644 --- a/assets/typescript/Forum/Posting.ts +++ b/assets/js/misuzu/forum/editor.js @@ -1,51 +1,52 @@ -/// - -let forumPostingCloseOK: boolean = false; - -function forumPostingInit(): void { - const postingForm: HTMLDivElement = document.querySelector('.js-forum-posting'); - +Misuzu.Forum.Editor = {}; +Misuzu.Forum.Editor.allowWindowClose = false; +Misuzu.Forum.Editor.init = function() { + var postingForm = document.querySelector('.js-forum-posting'); if(!postingForm) return; - const postingButtons: HTMLDivElement = postingForm.querySelector('.js-forum-posting-buttons'), - postingText: HTMLTextAreaElement = postingForm.querySelector('.js-forum-posting-text'), - postingParser: HTMLSelectElement = postingForm.querySelector('.js-forum-posting-parser'), - postingPreview: HTMLDivElement = postingForm.querySelector('.js-forum-posting-preview'), - postingMode: HTMLSpanElement = postingForm.querySelector('.js-forum-posting-mode'), - previewButton: HTMLButtonElement = document.createElement('button'), + var postingButtons = postingForm.querySelector('.js-forum-posting-buttons'), + postingText = postingForm.querySelector('.js-forum-posting-text'), + postingParser = postingForm.querySelector('.js-forum-posting-parser'), + postingPreview = postingForm.querySelector('.js-forum-posting-preview'), + postingMode = postingForm.querySelector('.js-forum-posting-mode'), + previewButton = document.createElement('button'), bbcodeButtons = document.querySelector('.forum__post__actions--bbcode'), markdownButtons = document.querySelector('.forum__post__actions--markdown'), markupButtons = document.querySelectorAll('.forum__post__action--tag'); - window.addEventListener("beforeunload", (e) => { - if(!forumPostingCloseOK && postingText.value.length > 0) { - const message: string = 'Are you sure you want to close the tab?'; - e.returnValue = message; - return message; + // hack: don't prompt user when hitting submit, really need to make this not stupid. + postingButtons.firstElementChild.addEventListener('click', function() { + Misuzu.Forum.Editor.allowWindowClose = true; + }); + + window.addEventListener('beforeunload', function(ev) { + if(!Misuzu.Forum.Editor.allowWindowClose && postingText.value.length > 0) { + ev.preventDefault(); + ev.returnValue = ''; } }); - for(let i = 0; i < markupButtons.length; i++) { - let currentBtn = markupButtons[i] as HTMLDivElement; - currentBtn.addEventListener('click', (ev) => - forumPostingInputMarkup(currentBtn.dataset.tagOpen, currentBtn.dataset.tagClose)); - } + for(var i = 0; i < markupButtons.length; ++i) + (function(currentBtn) { + currentBtn.addEventListener('click', function(ev) { + postingText.insertTags(currentBtn.dataset.tagOpen, currentBtn.dataset.tagClose); + }); + })(markupButtons[i]); - forumPostingSwitchButtons(parseInt(postingParser.value)); + Misuzu.Forum.Editor.switchButtons(parseInt(postingParser.value)); - let lastPostText: string = '', - lastPostParser: Parser = null; + var lastPostText = '', + lastPostParser = null; - postingParser.addEventListener('change', () => { - const postParser: Parser = parseInt(postingParser.value); - - forumPostingSwitchButtons(postParser); + postingParser.addEventListener('change', function() { + var postParser = parseInt(postingParser.value); + Misuzu.Forum.Editor.switchButtons(postParser); if(postingPreview.hasAttribute('hidden')) return; - // dunno if this is even possible, but ech + // dunno if this would even be possible, but ech if(postParser === lastPostParser) return; @@ -53,17 +54,16 @@ function forumPostingInit(): void { previewButton.setAttribute('disabled', 'disabled'); previewButton.classList.add('input__button--busy'); - forumPostingPreview(postParser, lastPostText, (success, text) => { + Misuzu.Forum.Editor.renderPreview(postParser, lastPostText, function(success, text) { if(!success) { - messageBox(text); + Misuzu.showMessageBox(text); return; } - if(postParser === Parser.Markdown) { + if(postParser === Misuzu.Parser.markdown) postingPreview.classList.add('markdown'); - } else { + else postingPreview.classList.remove('markdown'); - } lastPostParser = postParser; postingPreview.innerHTML = text; @@ -77,7 +77,7 @@ function forumPostingInit(): void { previewButton.textContent = 'Preview'; previewButton.type = 'button'; previewButton.value = 'preview'; - previewButton.addEventListener('click', () => { + previewButton.addEventListener('click', function() { if(previewButton.value === 'back') { postingPreview.setAttribute('hidden', 'hidden'); postingText.removeAttribute('hidden'); @@ -86,8 +86,8 @@ function forumPostingInit(): void { postingMode.textContent = postingMode.dataset.original; postingMode.dataset.original = null; } else { - const postText: string = postingText.value, - postParser: Parser = parseInt(postingParser.value); + var postText = postingText.value, + postParser = parseInt(postingParser.value); if(lastPostText === postText && lastPostParser === postParser) { postingPreview.removeAttribute('hidden'); @@ -103,17 +103,16 @@ function forumPostingInit(): void { previewButton.setAttribute('disabled', 'disabled'); previewButton.classList.add('input__button--busy'); - forumPostingPreview(postParser, postText, (success, text) => { + Misuzu.Forum.Editor.renderPreview(postParser, postText, function(success, text) { if(!success) { - messageBox(text); + Misuzu.showMessageBox(text); return; } - if(postParser === Parser.Markdown) { + if(postParser === Misuzu.Parser.markdown) postingPreview.classList.add('markdown'); - } else { + else postingPreview.classList.remove('markdown'); - } lastPostText = postText; lastPostParser = postParser; @@ -132,85 +131,48 @@ function forumPostingInit(): void { }); postingButtons.insertBefore(previewButton, postingButtons.firstChild); -} +}; +Misuzu.Forum.Editor.switchButtons = function(parser) { + var bbcodeButtons = document.querySelector('.forum__post__actions--bbcode'), + markdownButtons = document.querySelector('.forum__post__actions--markdown'); -function forumPostingPreview( - parser: Parser, - text: string, - callback: (success: boolean, htmlOrMessage: string) => void -): void { - const xhr: XMLHttpRequest = new XMLHttpRequest, - formData: FormData = new FormData; + switch(parser) { + default: + case Misuzu.Parser.plain: + bbcodeButtons.hidden = markdownButtons.hidden = true; + break; + case Misuzu.Parser.bbcode: + bbcodeButtons.hidden = false; + markdownButtons.hidden = true; + break; + case Misuzu.Parser.markdown: + bbcodeButtons.hidden = true; + markdownButtons.hidden = false; + break; + } +}; +Misuzu.Forum.Editor.renderPreview = function(parser, text, callback) { + if(!callback) + return; + parser = parseInt(parser); + text = text || ''; + + var xhr = new XMLHttpRequest, + formData = new FormData; formData.append('post[mode]', 'preview'); formData.append('post[text]', text); formData.append('post[parser]', parser.toString()); - xhr.addEventListener('readystatechange', () => { + xhr.addEventListener('readystatechange', function() { if(xhr.readyState !== XMLHttpRequest.DONE) return; - - if(xhr.status === 200) { + if(xhr.status === 200) callback(true, xhr.response); - } else { + else callback(false, 'Failed to render preview.'); - } }); - xhr.open('POST', urlFormat('forum-topic-new')); + xhr.open('POST', Misuzu.Urls.format('forum-topic-new')); xhr.withCredentials = true; xhr.send(formData); -} - -function forumPostingSwitchButtons(parser: Parser): void { - const bbcodeButtons = document.querySelector('.forum__post__actions--bbcode') as HTMLElement, - markdownButtons = document.querySelector('.forum__post__actions--markdown') as HTMLElement; - - switch(parser) { - default: - case Parser.PlainText: - bbcodeButtons.hidden = markdownButtons.hidden = true; - break; - case Parser.BBCode: - bbcodeButtons.hidden = false; - markdownButtons.hidden = true; - break; - case Parser.Markdown: - bbcodeButtons.hidden = true; - markdownButtons.hidden = false; - break; - } -} - -declare interface document { - selection: any; -} - -function forumPostingInputMarkup(tagOpen: string, tagClose: string): void { - const editor: HTMLTextAreaElement = document.querySelector('.js-forum-posting-text'), - doc = document as any; - - if(doc.selection) { - editor.focus(); - let selected = doc.selection.createRange(); - selected.text = tagOpen + selected.text + tagClose; - editor.focus(); - } else if(editor.selectionStart || editor.selectionStart === 0) { - let startPos = editor.selectionStart, - endPos = editor.selectionEnd, - scrollTop = editor.scrollTop; - - editor.value = editor.value.substring(0, startPos) - + tagOpen - + editor.value.substring(startPos, endPos) - + tagClose - + editor.value.substring(endPos, editor.value.length); - - editor.focus(); - editor.selectionStart = startPos + tagOpen.length; - editor.selectionEnd = endPos + tagOpen.length; - editor.scrollTop + scrollTop; - } else { - editor.value += tagOpen + tagClose; - editor.focus(); - } -} +}; diff --git a/assets/js/misuzu/forum/polls.js b/assets/js/misuzu/forum/polls.js new file mode 100644 index 00000000..3e4dd826 --- /dev/null +++ b/assets/js/misuzu/forum/polls.js @@ -0,0 +1,49 @@ +Misuzu.Forum.Polls = {}; +Misuzu.Forum.Polls.init = function() { + var polls = document.getElementsByClassName('js-forum-poll'); + if(!polls.length) + return; + for(var i = 0; i < polls.length; ++i) + Misuzu.Forum.Polls.initPoll(polls[i]); +}; +Misuzu.Forum.Polls.initPoll = function() { + var options = poll.getElementsByClassName('input__checkbox__input'), + votesRemaining = poll.querySelector('.js-forum-poll-remaining'), + votesRemainingCount = poll.querySelector('.js-forum-poll-remaining-count'), + votesRemainingPlural = poll.querySelector('.js-forum-poll-remaining-plural'), + maxVotes = parseInt(poll.dataset.pollMaxVotes); + + if(maxVotes < 2) + return; + + var votes = maxVotes; + + for(var i = 0; i < options.length; ++i) { + if(options[i].checked) { + if(votes < 1) + options[i].checked = false; + else + votes--; + } + + options[i].addEventListener('change', function(ev) { + if(this.checked) { + if(votes < 1) { + this.checked = false; + ev.preventDefault(); + return; + } + + votes--; + } else + votes++; + + votesRemainingCount.textContent = votes.toString(); + votesRemainingPlural.hidden = votes == 1; + }); + } + + votesRemaining.hidden = false; + votesRemainingCount.textContent = votes.toString(); + votesRemainingPlural.hidden = votes == 1; +}; diff --git a/assets/js/misuzu/urls.js b/assets/js/misuzu/urls.js new file mode 100644 index 00000000..65e80c9a --- /dev/null +++ b/assets/js/misuzu/urls.js @@ -0,0 +1,55 @@ +Misuzu.Urls = {}; +Misuzu.Urls.registry = []; +Misuzu.Urls.loadFromDocument = function() { + var elem = document.getElementById('js-urls-list'); + if(!elem) + return; + Misuzu.Urls.registry = JSON.parse(elem.textContent); +}; +Misuzu.Urls.handleVariable = function(value, vars) { + if(value[0] === '<' && value.slice(-1) === '>') + return (vars.find(function(x) { return x.name == value.slice(1, -1); }) || {}).value || ''; + if(value[0] === '[' && value.slice(-1) === ']') + return ''; // not sure if there's a proper substitute for this, should probably resolve these in url_list + if(value[0] === '{' && value.slice(-1) === '}') + return Misuzu.CSRF.getToken(); + return value; +}; +Misuzu.Urls.v = function(name, value) { + if(typeof value === 'undefined' || value === null) + value = ''; + return { name: name.toString(), value: value.toString() }; +}; +Misuzu.Urls.format = function(name, vars) { + vars = vars || []; + var entry = Misuzu.Urls.registry.find(function(x) { return x.name == name; }); + if(!entry || !entry.path) + return ''; + + var split = entry.path.split('/'); + for(var i = 0; i < split.length; ++i) + split[i] = Misuzu.Urls.handleVariable(split[i], vars); + + var url = split.join('/'); + + if(entry.query) { + url += '?'; + + for(var i = 0; i < entry.query.length; ++i) { + var query = entry.query[i], + value = Misuzu.Urls.handleVariable(query.value, vars); + + if(!value || (query.name === 'page' && parseInt(value) < 2)) + continue; + + url += query.name + '=' + value.toString() + '&'; + } + + url = url.replace(/^[\?\&]+|[\?\&]+$/g, ''); + } + + if(entry.fragment) + url += ('#' + Misuzu.Urls.handleVariable(entry.fragment, vars)).replace(/[\#]+$/g, ''); + + return url; +}; \ No newline at end of file diff --git a/assets/js/misuzu/userrels.js b/assets/js/misuzu/userrels.js new file mode 100644 index 00000000..791c4933 --- /dev/null +++ b/assets/js/misuzu/userrels.js @@ -0,0 +1,104 @@ +Misuzu.UserRelations = {}; +Misuzu.UserRelations.Type = DefineEnum({ none: 0, follow: 1, }); +Misuzu.UserRelations.init = function() { + var buttons = document.getElementsByClassName('js-user-relation-action'); + + for(var i = 0; i < buttons.length; ++i) { + switch(buttons[i].tagName.toLowerCase()) { + case 'a': + buttons[i].removeAttribute('href'); + buttons[i].removeAttribute('target'); + buttons[i].removeAttribute('rel'); + break; + } + + buttons[i].addEventListener('click', Misuzu.UserRelations.setRelationHandler); + } +}; +Misuzu.UserRelations.setRelation = function(user, type, onSuccess, onFailure) { + var xhr = new XMLHttpRequest; + xhr.addEventListener('readystatechange', function() { + if(xhr.readyState !== 4) + return; + + Misuzu.CSRF.setToken(xhr.getResponseHeader('X-Misuzu-CSRF')); + + var json = JSON.parse(xhr.responseText), + message = json.error || json.message; + + if(message && onFailure) + onFailure(message); + else if(!message && onSuccess) + onSuccess(json); + }); + xhr.open('GET', Misuzu.Urls.format('user-relation-create', [Misuzu.Urls.v('user', user), Misuzu.Urls.v('type', type)])); + xhr.setRequestHeader('X-Misuzu-XHR', 'user_relation'); + xhr.setRequestHeader('X-Misuzu-CSRF', Misuzu.CSRF.getToken()); + xhr.send(); +}; +Misuzu.UserRelations.ICO_ADD = 'fas fa-user-plus'; +Misuzu.UserRelations.ICO_REM = 'fas fa-user-minus'; +Misuzu.UserRelations.ICO_BUS = 'fas fa-spinner fa-pulse'; +Misuzu.UserRelations.BTN_BUS = 'input__button--busy'; +Misuzu.UserRelations.setRelationHandler = function(ev) { + var target = this, + userId = parseInt(target.dataset.relationUser), + relationType = parseInt(target.dataset.relationType), + isButton = target.classList.contains('input__button'), + icon = target.querySelector('[class^="fa"]'); + + if(isButton) { + if(target.classList.contains(Misuzu.UserRelations.BTN_BUS)) + return; + target.classList.add(Misuzu.UserRelations.BTN_BUS); + } + + if(icon) + icon.className = Misuzu.UserRelations.ICO_BUS; + + Misuzu.UserRelations.setRelation( + userId, + relationType, + function(info) { + target.classList.remove(Misuzu.UserRelations.BTN_BUS); + + switch(info.relation_type) { + case Misuzu.UserRelations.Type.none: + if(isButton) { + if(target.classList.contains('input__button--destroy')) + target.classList.remove('input__button--destroy'); + + target.textContent = 'Follow'; + } + + if(icon) { + icon.className = Misuzu.UserRelations.ICO_ADD; + target.title = 'Follow'; + } + + target.dataset.relationType = Misuzu.UserRelations.Type.follow.toString(); + break; + + case Misuzu.UserRelations.Type.follow: + if(isButton) { + if(!target.classList.contains('input__button--destroy')) + target.classList.add('input__button--destroy'); + + target.textContent = 'Unfollow'; + } + + if(icon) { + icon.className = Misuzu.UserRelations.ICO_REM; + target.title = 'Unfollow'; + } + + target.dataset.relationType = Misuzu.UserRelations.Type.none.toString(); + break; + } + }, + function(msg) { + target.classList.remove(Misuzu.UserRelations.BTN_BUS); + Misuzu.showMessageBox(msg); + } + ); +}; diff --git a/assets/typescript/Comments.tsx b/assets/typescript/Comments.tsx deleted file mode 100644 index 55968e33..00000000 --- a/assets/typescript/Comments.tsx +++ /dev/null @@ -1,439 +0,0 @@ -/// - -enum CommentVoteType { - Indifferent = 0, - Like = 1, - Dislike = -1, -} - -interface CommentNotice { - error: string; - message: string; -} - -interface CommentDeletionInfo extends CommentNotice { - id: number; // minor inconsistency, deal with it -} - -interface CommentPostInfo extends CommentNotice { - comment_id: number; - category_id: number; - comment_text: string; - comment_html: string; - comment_created: Date; - comment_edited: Date | null; - comment_deleted: Date | null; - comment_reply_to: number; - comment_pinned: Date | null; - user_id: number; - username: string; - user_colour: number; -} - -interface CommentVotesInfo extends CommentNotice { - comment_id: number; - likes: number; - dislikes: number; -} - -function commentDeleteEventHandler(ev: Event): void { - const target: HTMLAnchorElement = ev.target as HTMLAnchorElement, - commentId: number = parseInt(target.dataset.commentId); - - commentDelete( - commentId, - info => { - let elem = document.getElementById('comment-' + info.id); - - if(elem) - elem.parentNode.removeChild(elem); - }, - message => messageBox(message) - ); -} - -function commentDelete(commentId: number, onSuccess: (info: CommentDeletionInfo) => void = null, onFail: (message: string) => void = null): void -{ - if(!Misuzu.User.isLoggedIn() - || !Misuzu.User.localUser.perms.canDeleteOwnComment()) { - if(onFail) - onFail("You aren't allowed to delete comments."); - return; - } - - const xhr: XMLHttpRequest = new XMLHttpRequest; - - xhr.addEventListener('readystatechange', () => { - if(xhr.readyState !== 4) - return; - - updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF')); - - let json: CommentDeletionInfo = JSON.parse(xhr.responseText) as CommentDeletionInfo, - message = json.error || json.message; - - if(message && onFail) - onFail(message); - else if(!message && onSuccess) - onSuccess(json); - }); - xhr.open('GET', urlFormat('comments-delete', [{name:'comment',value:commentId}])); - xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); - xhr.send(); -} - -function commentPostEventHandler(ev: Event): void -{ - const form: HTMLFormElement = ev.target as HTMLFormElement; - - if(form.dataset.disabled) - return; - form.dataset.disabled = '1'; - form.style.opacity = '0.5'; - - commentPost( - extractFormData(form, true), - info => commentPostSuccess(form, info), - message => commentPostFail(form, message) - ); -} - -function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) => void = null, onFail: (message: string) => void = null): void -{ - if(!Misuzu.User.isLoggedIn() - || !Misuzu.User.localUser.perms.canCreateComment()) { - if(onFail) - onFail("You aren't allowed to post comments."); - return; - } - - const xhr = new XMLHttpRequest(); - - xhr.addEventListener('readystatechange', () => { - if(xhr.readyState !== 4) - return; - - updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF')); - - const json: CommentPostInfo = JSON.parse(xhr.responseText) as CommentPostInfo, - message: string = json.error || json.message; - - if(message && onFail) - onFail(message); - else if(!message && onSuccess) - onSuccess(json); - }); - - xhr.open('POST', urlFormat('comment-create')); - xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); - xhr.send(formData); -} - -function commentPostSuccess(form: HTMLFormElement, comment: CommentPostInfo): void { - if(form.classList.contains('comment--reply')) - (form.parentNode.parentNode.querySelector('label.comment__action') as HTMLLabelElement).click(); - - commentInsert(comment, form); - form.style.opacity = '1'; - form.dataset.disabled = ''; -} - -function commentPostFail(form: HTMLFormElement, message: string): void { - messageBox(message); - form.style.opacity = '1'; - form.dataset.disabled = ''; -} - -function commentsInit(): void { - const commentDeletes: HTMLCollectionOf = document.getElementsByClassName('comment__action--delete') as HTMLCollectionOf; - - for(let i = 0; i < commentDeletes.length; i++) { - commentDeletes[i].addEventListener('click', commentDeleteEventHandler); - commentDeletes[i].dataset.href = commentDeletes[i].href; - commentDeletes[i].href = 'javascript:void(0);'; - } - - const commentInputs: HTMLCollectionOf = document.getElementsByClassName('comment__text--input') as HTMLCollectionOf; - - for(let i = 0; i < commentInputs.length; i++) { - commentInputs[i].form.action = 'javascript:void(0);'; - commentInputs[i].form.addEventListener('submit', commentPostEventHandler); - commentInputs[i].addEventListener('keydown', commentInputEventHandler); - } - - const voteButtons: HTMLCollectionOf = document.getElementsByClassName('comment__action--vote') as HTMLCollectionOf; - - for(let i = 0; i < voteButtons.length; i++) - { - voteButtons[i].href = 'javascript:void(0);'; - voteButtons[i].addEventListener('click', commentVoteEventHandler); - } - - const pinButtons: HTMLCollectionOf = document.getElementsByClassName('comment__action--pin') as HTMLCollectionOf; - - for(let i = 0; i < pinButtons.length; i++) { - pinButtons[i].href = 'javascript:void(0);'; - pinButtons[i].addEventListener('click', commentPinEventHandler); - } -} - -function commentInputEventHandler(ev: KeyboardEvent): void { - if(ev.code === 'Enter' && ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) { - const form: HTMLFormElement = (ev.target as HTMLTextAreaElement).form; - commentPost( - extractFormData(form, true), - info => commentPostSuccess(form, info), - message => commentPostFail - ); - } -} - -function commentConstruct(comment: CommentPostInfo, layer: number = 0): HTMLElement { - const commentDate = new Date(comment.comment_created + 'Z'), - commentTime: HTMLElement = ; - let actions: HTMLElement[] = []; - - if(Misuzu.User.isLoggedIn() && Misuzu.User.localUser.perms.canVoteOnComment()) { - actions.push(); - actions.push(Dislike); - } - - const commentText: HTMLDivElement =
, - commentColour = new Misuzu.Colour(comment.user_colour); - - if(comment.comment_html) - commentText.innerHTML = comment.comment_html; - else - commentText.textContent = comment.comment_text; - - const commentElement: HTMLDivElement =
-
- - {comment.username} - -
- - {commentText} -
- {actions} - -
-
-
-
- -
- - - -
-
- -
-
-
- -
- -
-
-
-
-
-
; - - timeago.render(commentTime); - - return commentElement; -} - -function commentInsert(comment: CommentPostInfo, form: HTMLFormElement): void -{ - const isReply: boolean = form.classList.contains('comment--reply'), - parent: Element = isReply - ? form.parentElement - : form.parentElement.parentElement.getElementsByClassName('comments__listing')[0], - repliesIndent: number = isReply - ? (parseInt(parent.classList[1].substr(25)) + 1) - : 1, - commentElement: HTMLElement = commentConstruct(comment, repliesIndent); - - if(isReply) - parent.appendChild(commentElement); - else - parent.insertBefore(commentElement, parent.firstElementChild); - - const placeholder: HTMLElement = document.getElementById('_no_comments_notice_' + comment.category_id); - - if(placeholder) - placeholder.parentNode.removeChild(placeholder); -} - -function commentVoteEventHandler(ev: Event): void { - const target: HTMLAnchorElement = this as HTMLAnchorElement, - commentId: number = parseInt(target.dataset.commentId), - voteType: CommentVoteType = parseInt(target.dataset.commentVote), - buttons: NodeListOf = document.querySelectorAll(`.comment__action--vote[data-comment-id="${commentId}"]`), - likeButton: HTMLAnchorElement = document.querySelector(`.comment__action--like[data-comment-id="${commentId}"]`), - dislikeButton: HTMLAnchorElement = document.querySelector(`.comment__action--dislike[data-comment-id="${commentId}"]`), - classVoted: string = 'comment__action--voted'; - - for(let i = 0; i < buttons.length; i++) { - let button: HTMLAnchorElement = buttons[i]; - - button.textContent = button === target ? '...' : ''; - button.classList.remove(classVoted); - - if(button === likeButton) { - button.dataset.commentVote = (voteType === CommentVoteType.Like ? CommentVoteType.Indifferent : CommentVoteType.Like).toString(); - } else if(button === dislikeButton) { - button.dataset.commentVote = (voteType === CommentVoteType.Dislike ? CommentVoteType.Indifferent : CommentVoteType.Dislike).toString(); - } - } - - commentVote( - commentId, - voteType, - info => { - switch(voteType) { - case CommentVoteType.Like: - likeButton.classList.add(classVoted); - break; - - case CommentVoteType.Dislike: - dislikeButton.classList.add(classVoted); - break; - } - - likeButton.textContent = info.likes > 0 ? `Like (${info.likes.toLocaleString()})` : 'Like'; - dislikeButton.textContent = info.dislikes > 0 ? `Dislike (${info.dislikes.toLocaleString()})` : 'Dislike'; - }, - message => { - likeButton.textContent = 'Like'; - dislikeButton.textContent = 'Dislike'; - messageBox(message); - } - ); -} - -function commentVote( - commentId: number, - vote: CommentVoteType, - onSuccess: (voteInfo: CommentVotesInfo) => void = null, - onFail: (message: string) => void = null -): void { - if(!Misuzu.User.isLoggedIn() - || !Misuzu.User.localUser.perms.canVoteOnComment()) { - if(onFail) - onFail("You aren't allowed to vote on comments."); - return; - } - - const xhr: XMLHttpRequest = new XMLHttpRequest; - xhr.onreadystatechange = () => { - if(xhr.readyState !== 4) - return; - - updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF')); - - const json: CommentVotesInfo = JSON.parse(xhr.responseText), - message: string = json.error || json.message; - - if(message && onFail) - onFail(message); - else if(!message && onSuccess) - onSuccess(json); - }; - xhr.open('GET', urlFormat('comment-vote', [{name: 'comment', value: commentId}, {name: 'vote', value: vote}])); - xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); - xhr.send(); -} - -function commentPinEventHandler(ev: Event): void { - const target: HTMLAnchorElement = this as HTMLAnchorElement, - commentId: number = parseInt(target.dataset.commentId), - isPinned: boolean = target.dataset.commentPinned !== '0'; - - target.textContent = '...'; - - commentPin( - commentId, - !isPinned, - info => { - if(info.comment_pinned === null) { - target.textContent = 'Pin'; - target.dataset.commentPinned = '0'; - const pinElement: HTMLDivElement = document.querySelector(`#comment-${info.comment_id} .comment__pin`); - pinElement.parentElement.removeChild(pinElement); - } else { - target.textContent = 'Unpin'; - target.dataset.commentPinned = '1'; - - const pinInfo: HTMLDivElement = document.querySelector(`#comment-${info.comment_id} .comment__info`), - pinElement: HTMLDivElement = document.createElement('div'), - pinTime: HTMLTimeElement = document.createElement('time'), - pinDateTime = new Date(info.comment_pinned + 'Z'); - - pinTime.title = pinDateTime.toLocaleString(); - pinTime.dateTime = pinDateTime.toISOString(); - pinTime.textContent = timeago().format(pinDateTime); - timeago().render(pinTime); - - pinElement.className = 'comment__pin'; - pinElement.appendChild(document.createTextNode('Pinned ')); - pinElement.appendChild(pinTime); - pinInfo.appendChild(pinElement); - } - }, - message => { - target.textContent = isPinned ? 'Unpin' : 'Pin'; - messageBox(message); - } - ); -} - -function commentPin( - commentId: number, - pin: boolean, - onSuccess: (commentInfo: CommentPostInfo) => void = null, - onFail: (message: string) => void = null -): void { - if(!Misuzu.User.isLoggedIn() - || !Misuzu.User.localUser.perms.canPinComment()) { - if(onFail) - onFail("You aren't allowed to pin comments."); - return; - } - - const mode: string = pin ? 'pin' : 'unpin'; - const xhr: XMLHttpRequest = new XMLHttpRequest; - xhr.onreadystatechange = () => { - if(xhr.readyState !== 4) - return; - - updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF')); - - const json: CommentPostInfo = JSON.parse(xhr.responseText), - message: string = json.error || json.message; - - if(message && onFail) - onFail(message); - else if(!message && onSuccess) - onSuccess(json); - }; - xhr.open('GET', urlFormat(`comment-${mode}`, [{name: 'comment', value: commentId}])); - xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); - xhr.send(); -} diff --git a/assets/typescript/FormUtilities.ts b/assets/typescript/FormUtilities.ts deleted file mode 100644 index 66557c5a..00000000 --- a/assets/typescript/FormUtilities.ts +++ /dev/null @@ -1,118 +0,0 @@ -function extractFormData(form: HTMLFormElement, resetSource: boolean = false): FormData { - const formData: FormData = new FormData; - - for(let i = 0; i < form.length; i++) { - let input: HTMLInputElement = form[i] as HTMLInputElement, - type = input.type.toLowerCase(), - isCheckbox = type === 'checkbox'; - - if(isCheckbox && !input.checked) - continue; - - formData.append(input.name, input.value || ''); - } - - if(resetSource) - resetForm(form); - - return formData; -} - -interface FormHiddenDefault { - Name: string; - Value: string; -} - -function resetForm(form: HTMLFormElement, defaults: FormHiddenDefault[] = []): void { - for(let i = 0; i < form.length; i++) { - let input: HTMLInputElement = form[i] as HTMLInputElement; - - switch(input.type.toLowerCase()) { - case 'checkbox': - input.checked = false; - break; - - case 'hidden': - let hiddenDefault: FormHiddenDefault = defaults.find(fhd => fhd.Name.toLowerCase() === input.name.toLowerCase()); - - if(hiddenDefault) - input.value = hiddenDefault.Value; - break; - - default: - input.value = ''; - } - } -} - -let CSRFToken: string; - -function initCSRF(): void { - CSRFToken = document.querySelector('[name="csrf-token"]').getAttribute('value'); -} - -function getCSRFToken(): string { - return CSRFToken; -} - -function updateCSRF(token: string): void { - if(token === null) { - return; - } - - document.querySelector('[name="csrf-token"]').setAttribute('value', CSRFToken = token); - - const elements: NodeListOf = document.getElementsByName('csrf') as NodeListOf; - - for(let i = 0; i < elements.length; i++) { - elements[i].value = token; - } -} - -function handleDataRequestMethod(elem: HTMLAnchorElement, method: string, url: string): void { - const split: string[] = url.split('?', 2), - target: string = split[0], - query: string = split[1] || null; - - if(elem.getAttribute('disabled')) - return; - elem.setAttribute('disabled', 'disabled'); - - const xhr: XMLHttpRequest = new XMLHttpRequest; - xhr.onreadystatechange = function(ev) { - if(xhr.readyState !== 4) - return; - elem.removeAttribute('disabled'); - - if(xhr.status === 301 || xhr.status === 302 || xhr.status === 307 || xhr.status === 308) { - location.assign(xhr.getResponseHeader('X-Misuzu-Location')); - return; - } - - if(xhr.status >= 400 && xhr.status <= 599) { - alert(xhr.responseText); - return; - } - }; - xhr.open(method, target); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.setRequestHeader('X-Misuzu-CSRF', getCSRFToken()); - xhr.setRequestHeader('X-Misuzu-XHR', '1'); - xhr.send(query); -} - -function initDataRequestMethod(): void { - const links: HTMLCollection = document.links; - - for(let i = 0; i < links.length; i++) { - let elem: HTMLAnchorElement = links[i] as HTMLAnchorElement; - - if(!elem.href || !elem.dataset || !elem.dataset.mszMethod) - continue; - - elem.onclick = function(ev) { - ev.preventDefault(); - handleDataRequestMethod(elem, elem.dataset.mszMethod, elem.href); - }; - } -} diff --git a/assets/typescript/Forum/Polls.ts b/assets/typescript/Forum/Polls.ts deleted file mode 100644 index fbda0a29..00000000 --- a/assets/typescript/Forum/Polls.ts +++ /dev/null @@ -1,56 +0,0 @@ -function forumPollsInit(): void { - const polls: HTMLCollectionOf = document.getElementsByClassName('js-forum-poll'); - - if(polls.length < 1) { - return; - } - - for(let i = 0; i < polls.length; i++) { - forumPollInit(polls[i] as HTMLFormElement); - } -} - -function forumPollInit(poll: HTMLFormElement): void { - const options: HTMLCollectionOf = poll.getElementsByClassName('input__checkbox__input') as HTMLCollectionOf, - votesRemaining: HTMLDivElement = poll.querySelector('.js-forum-poll-remaining'), - votesRemainingCount: HTMLSpanElement = poll.querySelector('.js-forum-poll-remaining-count'), - votesRemainingPlural: HTMLSpanElement = poll.querySelector('.js-forum-poll-remaining-plural'), - maxVotes: number = parseInt(poll.dataset.pollMaxVotes); - - if(maxVotes > 1) { - let votes: number = maxVotes; - - for(let i = 0; i < options.length; i++) { - if(options[i].checked) { - if(votes < 1) { - options[i].checked = false; - } else { - votes--; - } - } - - options[i].addEventListener('change', ev => { - const elem: HTMLInputElement = ev.target as HTMLInputElement; - - if(elem.checked) { - if(votes < 1) { - elem.checked = false; - ev.preventDefault(); - return; - } - - votes--; - } else { - votes++; - } - - votesRemainingCount.textContent = votes.toString(); - votesRemainingPlural.hidden = votes == 1; - }); - } - - votesRemaining.hidden = false; - votesRemainingCount.textContent = votes.toString(); - votesRemainingPlural.hidden = votes == 1; - } -} diff --git a/assets/typescript/Parser.ts b/assets/typescript/Parser.ts deleted file mode 100644 index 1c0ac515..00000000 --- a/assets/typescript/Parser.ts +++ /dev/null @@ -1,5 +0,0 @@ -enum Parser { - PlainText = 0, - BBCode = 1, - Markdown = 2, -} diff --git a/assets/typescript/UrlRegistry.ts b/assets/typescript/UrlRegistry.ts deleted file mode 100644 index cec1436c..00000000 --- a/assets/typescript/UrlRegistry.ts +++ /dev/null @@ -1,87 +0,0 @@ -interface UrlRegistryVariable { - name: string; - value: string | number; -} - -interface UrlRegistryEntryQuery { - name: string; - value: string; -} - -interface UrlRegistryEntry { - name: string; - path: string; - query: UrlRegistryEntryQuery[]; - fragment: string; -} - -let urlRegistryTable: UrlRegistryEntry[] = []; - -function getRawUrlRegistry(): UrlRegistryEntry[] { - const urlListElement: HTMLElement = document.getElementById('js-urls-list') as HTMLElement; - - if(!urlListElement) - return null; - - return JSON.parse(urlListElement.textContent) as UrlRegistryEntry[]; -} - -function urlRegistryInit(): void { - urlRegistryTable = getRawUrlRegistry(); -} - -function urlFormat(name: string, vars: UrlRegistryVariable[] = []): string { - const entry: UrlRegistryEntry = urlRegistryTable.find(x => x.name == name); - - if(!entry || !entry.path) { - return ''; - } - - const splitUrl: string[] = entry.path.split('/'); - - for(let i = 0; i < splitUrl.length; i++) { - splitUrl[i] = urlVariable(splitUrl[i], vars); - } - - let url: string = splitUrl.join('/'); - - if(entry.query) { - url += '?'; - - for(let i = 0; i < entry.query.length; i++) { - const query: UrlRegistryEntryQuery = entry.query[i], - value: string = urlVariable(query.value, vars); - - if(!value || (query.name === 'page' && parseInt(value) < 2)) { - continue; - } - - url += `${query.name}=${value}&`; - } - - url = url.replace(/^[\?\&]+|[\?\&]+$/g, ''); - } - - if(entry.fragment) { - url += ('#' + urlVariable(entry.fragment, vars)).replace(/[\#]+$/g, ''); - } - - return url; -} - -function urlVariable(value: string, vars: UrlRegistryVariable[]): string { - if(value[0] === '<' && value.slice(-1) === '>') { - const urvar: UrlRegistryVariable = vars.find(x => x.name == value.slice(1, -1)); - return urvar ? urvar.value.toString() : ''; - } - - if(value[0] === '[' && value.slice(-1) === ']') { - return ''; // not sure if there's a proper substitute for this, should probably resolve these in url_list - } - - if(value[0] === '{' && value.slice(-1) === '}') { - return getCSRFToken(); - } - - return value; -} diff --git a/assets/typescript/UserRelations.ts b/assets/typescript/UserRelations.ts deleted file mode 100644 index 9efd29cb..00000000 --- a/assets/typescript/UserRelations.ts +++ /dev/null @@ -1,127 +0,0 @@ -enum UserRelationType { - None = 0, - Follow = 1, -} - -interface UserRelationInfo { - user_id: number; - subject_id: number; - relation_type: UserRelationType; - - error: string; - message: string; -} - -function userRelationsInit(): void { - const relationButtons: HTMLCollectionOf = document.getElementsByClassName('js-user-relation-action') as HTMLCollectionOf; - - for(let i = 0; i < relationButtons.length; i++) { - switch(relationButtons[i].tagName.toLowerCase()) { - case 'a': - const anchor: HTMLAnchorElement = relationButtons[i] as HTMLAnchorElement; - anchor.removeAttribute('href'); - anchor.removeAttribute('target'); - anchor.removeAttribute('rel'); - break; - } - - relationButtons[i].addEventListener('click', userRelationSetEventHandler); - } -} - -function userRelationSetEventHandler(ev: Event): void { - const target: HTMLElement = this as HTMLElement, - userId: number = parseInt(target.dataset.relationUser), - relationType: UserRelationType = parseInt(target.dataset.relationType), - isButton: boolean = target.classList.contains('input__button'), - icon: HTMLElement = target.querySelector('[class^="fa"]'), - buttonBusy: string = 'input__button--busy', - iconAdd: string = 'fas fa-user-plus', - iconRemove: string = 'fas fa-user-minus', - iconBusy: string = 'fas fa-spinner fa-pulse'; - - if(isButton) { - if(target.classList.contains(buttonBusy)) - return; - - target.classList.add(buttonBusy); - } - - if(icon) { - icon.className = iconBusy; - } - - userRelationSet( - userId, - relationType, - info => { - target.classList.remove(buttonBusy); - - switch(info.relation_type) { - case UserRelationType.None: - if(isButton) { - if(target.classList.contains('input__button--destroy')) - target.classList.remove('input__button--destroy'); - - target.textContent = 'Follow'; - } - - if(icon) { - icon.className = iconAdd; - target.title = 'Follow'; - } - - target.dataset.relationType = UserRelationType.Follow.toString(); - break; - - case UserRelationType.Follow: - if(isButton) { - if(!target.classList.contains('input__button--destroy')) - target.classList.add('input__button--destroy'); - - target.textContent = 'Unfollow'; - } - - if(icon) { - icon.className = iconRemove; - target.title = 'Unfollow'; - } - - target.dataset.relationType = UserRelationType.None.toString(); - break; - } - }, - msg => { - target.classList.remove(buttonBusy); - messageBox(msg); - } - ); -} - -function userRelationSet( - userId: number, - relationType: UserRelationType, - onSuccess: (info: UserRelationInfo) => void = null, - onFail: (message: string) => void = null -): void { - const xhr: XMLHttpRequest = new XMLHttpRequest; - - xhr.addEventListener('readystatechange', () => { - if(xhr.readyState !== 4) - return; - - updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF')); - - let json: UserRelationInfo = JSON.parse(xhr.responseText) as UserRelationInfo, - message = json.error || json.message; - - if(message && onFail) - onFail(message); - else if(!message && onSuccess) - onSuccess(json); - }); - xhr.open('GET', urlFormat('user-relation-create', [{name: 'user', value: userId}, {name: 'type', value: relationType.toString()}])); - xhr.setRequestHeader('X-Misuzu-XHR', 'user_relation'); - xhr.setRequestHeader('X-Misuzu-CSRF', getCSRFToken()); - xhr.send(); -} diff --git a/assets/typescript/misuzu.tsx b/assets/typescript/misuzu.tsx deleted file mode 100644 index 2da6914c..00000000 --- a/assets/typescript/misuzu.tsx +++ /dev/null @@ -1,224 +0,0 @@ -/// -/// -/// -/// -/// -/// -/// - -let loginFormAvatarTimeout: number = 0; - -function mszCreateElement(type: string, properties: {} = {}, children: any[] = []): HTMLElement { - const element: HTMLElement = document.createElement(type); - - if(!Array.isArray(children)) - children = [children]; - - if(arguments.length > 3) - for(let i = 3; i < arguments.length; i++) - children.push(arguments[i]); - - if(properties) - for(let prop in properties) { - switch(typeof properties[prop]) { - case 'function': - element.addEventListener( - prop.substring(0, 2) === 'on' - ? prop.substring(2).toLowerCase() - : prop, - properties[prop] - ); - break; - default: - element.setAttribute(prop, properties[prop]); - break; - } - } - - if(children) - for(let child in children as []) { - switch(typeof children[child]) { - case 'string': - element.appendChild(document.createTextNode(children[child])); - break; - default: - if(children[child] instanceof Element) - element.appendChild(children[child]); - break; - } - } - - return element; -} - -// Initialisation process. -document.addEventListener('DOMContentLoaded', () => { - console.log("%c __ ____\n / |/ (_)______ ______ __ __\n / /|_/ / / ___/ / / /_ / / / / /\n / / / / (__ ) /_/ / / /_/ /_/ /\n/_/ /_/_/____/\\__,_/ /___/\\__,_/\nhttps://github.com/flashwave/misuzu", 'color: #8559a5'); - - initCSRF(); - urlRegistryInit(); - //userInit(); - userRelationsInit(); - initDataRequestMethod(); - - const changelogChangeAction: HTMLDivElement = document.querySelector('.changelog__log__action') as HTMLDivElement; - - if(changelogChangeAction && !Misuzu.supportsSidewaysText()) - changelogChangeAction.title = "This is supposed to be sideways, but your browser doesn't support that."; - - const loginForms: HTMLCollectionOf = document.getElementsByClassName('js-login-form') as HTMLCollectionOf; - - if(loginForms.length > 0) { - for(let i = 0; i < loginForms.length; i++) { - const loginForm: HTMLFormElement = loginForms[i], - loginAvatar: HTMLImageElement = loginForm.getElementsByClassName('js-login-avatar')[0] as HTMLImageElement, - loginUsername: HTMLInputElement = loginForm.getElementsByClassName('js-login-username')[0] as HTMLInputElement; - - // Initial bump, in case anything is prefilled. - loginFormUpdateAvatar(loginAvatar, loginUsername, true); - - loginUsername.addEventListener('keyup', () => loginFormUpdateAvatar(loginAvatar, loginUsername)); - } - } - - const ctrlSubmit: HTMLCollectionOf = document.getElementsByClassName('js-ctrl-enter-submit') as HTMLCollectionOf; - - if(ctrlSubmit.length > 0) { - for(let i = 0; i < ctrlSubmit.length; i++) { - ctrlSubmit[i].addEventListener('keydown', ev => { - if((ev.code === 'Enter' || ev.code === 'NumpadEnter') /* i hate this fucking language so much */ - && ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) { - // for a hackjob - forumPostingCloseOK = true; - - ctrlSubmit[i].form.submit(); - ev.preventDefault(); - } - }); - } - } - - commentsInit(); - forumPostingInit(); - forumPollsInit(); - - var d: Date = new Date; - - if(d.getMonth() === 11 && d.getDate() > 5 && d.getDate() < 27) - mszEventChristmas(); -}); - -function mszEventChristmas(): void { - var headerBg: HTMLDivElement = document.querySelector('.header__background'), - menuBgs: NodeListOf = document.querySelectorAll('.header__desktop__submenu__background'), - propName: string = 'msz-christmas-' + (new Date).getFullYear().toString(); - - if(!localStorage.getItem(propName)) - localStorage.setItem(propName, '0'); - - var changeColour = function() { - var count = parseInt(localStorage.getItem(propName)); - document.body.style.setProperty('--header-accent-colour', (count++ % 2) ? 'green' : 'red'); - localStorage.setItem(propName, count.toString()); - }; - - if(headerBg) - headerBg.style.transition = 'background-color .4s'; - - setTimeout(function() { - if(headerBg) - headerBg.style.transition = 'background-color 1s'; - - for(var i = 0; i < menuBgs.length; i++) - menuBgs[i].style.transition = 'background-color 1s'; - }, 1000); - - changeColour(); - setInterval(changeColour, 10000); -} - -function loginFormUpdateAvatar(avatarElement: HTMLImageElement, usernameElement: HTMLInputElement, force: boolean = false): void { - if(!force) { - if(loginFormAvatarTimeout) - return; - - loginFormAvatarTimeout = setTimeout(() => { - loginFormUpdateAvatar(avatarElement, usernameElement, true); - clearTimeout(loginFormAvatarTimeout); - loginFormAvatarTimeout = 0; - }, 750); - return; - } - - const xhr: XMLHttpRequest = new XMLHttpRequest; - xhr.addEventListener('readystatechange', () => { - if(xhr.readyState !== 4) - return; - - avatarElement.src = urlFormat('user-avatar', [ - { name: 'user', value: xhr.responseText.indexOf('<') !== -1 ? '0' : xhr.responseText }, - { name: 'res', value: 100 }, - ]); - }); - xhr.open('GET', urlFormat('auth-resolve-user', [{name: 'username', value: encodeURIComponent(usernameElement.value)}])); - xhr.send(); -} - -interface MessageBoxButton { - text: string; - callback: Function; -} - -function messageBox(text: string, title: string = null, buttons: MessageBoxButton[] = []): boolean { - if(document.querySelector('.messagebox')) { - return false; - } - - const element = document.createElement('div'); - element.className = 'messagebox'; - - const container = element.appendChild(document.createElement('div')); - container.className = 'container messagebox__container'; - - const titleElement = container.appendChild(document.createElement('div')), - titleBackground = titleElement.appendChild(document.createElement('div')), - titleText = titleElement.appendChild(document.createElement('div')); - - titleElement.className = 'container__title'; - titleBackground.className = 'container__title__background'; - titleText.className = 'container__title__text'; - titleText.textContent = title || 'Information'; - - const textElement = container.appendChild(document.createElement('div')); - textElement.className = 'container__content'; - textElement.textContent = text; - - const buttonsContainer = container.appendChild(document.createElement('div')); - buttonsContainer.className = 'messagebox__buttons'; - - let firstButton = null; - - if(buttons.length < 1) { - firstButton = buttonsContainer.appendChild(document.createElement('button')); - firstButton.className = 'input__button'; - firstButton.textContent = 'OK'; - firstButton.addEventListener('click', () => element.remove()); - } else { - for(let i = 0; i < buttons.length; i++) { - let button = buttonsContainer.appendChild(document.createElement('button')); - button.className = 'input__button'; - button.textContent = buttons[i].text; - button.addEventListener('click', () => { - element.remove(); - buttons[i].callback(); - }); - - if(firstButton === null) - firstButton = button; - } - } - - document.body.appendChild(element); - firstButton.focus(); - return true; -} diff --git a/build.php b/build.php deleted file mode 100644 index 96279fb9..00000000 --- a/build.php +++ /dev/null @@ -1,76 +0,0 @@ - 0 ? date(' ') . $text . PHP_EOL : PHP_EOL; -} - -function create_dir(string $dir): void { - if(!file_exists($dir) || !is_dir($dir)) { - mkdir($dir); - build_log("Created '{$dir}'!"); - } -} - -function glob_dir(string $dir, string $pattern, int $flags = 0): array { - return glob($dir . '/' . $pattern, $flags); -} - -function purge_dir(string $dir, string $pattern): void { - $files = glob_dir($dir, $pattern); - - foreach($files as $file) { - if(is_dir($file)) { - build_log("'{$file}' is a directory, entering..."); - purge_dir($file, $pattern); - rmdir($file); - } else { - unlink($file); - build_log("Deleted '{$file}'"); - } - } -} - -$doAll = empty($argv[1]) || $argv[1] === 'all'; -$doJs = $doAll || $argv[1] === 'js'; - -// Make sure we're running from the misuzu root directory. -chdir(__DIR__); - -build_log('Cleanup'); - -if($doJs) { - create_dir(JS_DIR); - purge_dir(JS_DIR, '*.js'); - purge_dir(TS_DIR, '*.d.ts'); - - build_log(); - build_log('Compiling TypeScript'); - build_log(shell_exec('tsc --extendedDiagnostics -p tsconfig.json')); -} - -// no need to do this in debug mode, auto reload is enabled and cache is disabled -if($doAll && !file_exists(__DIR__ . '/.debug')) { - // Clear Twig cache - build_log(); - build_log('Deleting template cache'); - purge_dir(TWIG_DIRECTORY, '*'); -} diff --git a/src/TwigMisuzu.php b/src/TwigMisuzu.php index 23d96291..6e509473 100644 --- a/src/TwigMisuzu.php +++ b/src/TwigMisuzu.php @@ -16,7 +16,6 @@ final class TwigMisuzu extends Twig_Extension { new Twig_Filter('html_link', 'html_link'), // deprecate this call, convert to html in php new Twig_Filter('parse_text', fn(string $text, int $parser): string => Parser::instance($parser)->parseText($text)), - new Twig_Filter('asset_url', [static::class, 'assetUrl']), new Twig_Filter('perms_check', 'perms_check'), new Twig_Filter('bg_settings', 'user_background_settings_strings'), new Twig_Filter('clamp', 'clamp'), @@ -45,14 +44,4 @@ final class TwigMisuzu extends Twig_Extension { new Twig_Function('sql_query_count', fn() => DB::queries()), ]; } - - public static function assetUrl(string $path): string { - $realPath = realpath(MSZ_ROOT . '/public/' . $path); - - if($realPath === false || !file_exists($realPath)) { - return $path; - } - - return $path . '?' . filemtime($realPath); - } } diff --git a/templates/master.twig b/templates/master.twig index 836535c4..17476a55 100644 --- a/templates/master.twig +++ b/templates/master.twig @@ -63,6 +63,5 @@ -