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();
- }
-
- 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 = ;
-
- 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 @@
-