Moving away from LESS and TypeScript part 4 of 4!
This commit is contained in:
parent
67884c3dcc
commit
69620fe461
20 changed files with 1033 additions and 1262 deletions
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
491
assets/js/misuzu/comments.js
Normal file
491
assets/js/misuzu/comments.js
Normal file
|
@ -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;
|
||||
};
|
17
assets/js/misuzu/csrf.js
Normal file
17
assets/js/misuzu/csrf.js
Normal file
|
@ -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;
|
||||
};
|
81
assets/js/misuzu/formutils.js
Normal file
81
assets/js/misuzu/formutils.js
Normal file
|
@ -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);
|
||||
};
|
1
assets/js/misuzu/forum/_forum.js
Normal file
1
assets/js/misuzu/forum/_forum.js
Normal file
|
@ -0,0 +1 @@
|
|||
Misuzu.Forum = {};
|
|
@ -1,51 +1,52 @@
|
|||
/// <reference path="../Parser.ts" />
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
49
assets/js/misuzu/forum/polls.js
Normal file
49
assets/js/misuzu/forum/polls.js
Normal file
|
@ -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;
|
||||
};
|
55
assets/js/misuzu/urls.js
Normal file
55
assets/js/misuzu/urls.js
Normal file
|
@ -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;
|
||||
};
|
104
assets/js/misuzu/userrels.js
Normal file
104
assets/js/misuzu/userrels.js
Normal file
|
@ -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);
|
||||
}
|
||||
);
|
||||
};
|
|
@ -1,439 +0,0 @@
|
|||
/// <reference path="FormUtilities.ts" />
|
||||
|
||||
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<HTMLAnchorElement> = document.getElementsByClassName('comment__action--delete') as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
|
||||
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<HTMLTextAreaElement> = document.getElementsByClassName('comment__text--input') as HTMLCollectionOf<HTMLTextAreaElement>;
|
||||
|
||||
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<HTMLAnchorElement> = document.getElementsByClassName('comment__action--vote') as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
|
||||
for(let i = 0; i < voteButtons.length; i++)
|
||||
{
|
||||
voteButtons[i].href = 'javascript:void(0);';
|
||||
voteButtons[i].addEventListener('click', commentVoteEventHandler);
|
||||
}
|
||||
|
||||
const pinButtons: HTMLCollectionOf<HTMLAnchorElement> = document.getElementsByClassName('comment__action--pin') as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
|
||||
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 = <time class="comment__date" title={commentDate.toLocaleString()} dateTime={commentDate.toISOString()}>{timeago.format(commentDate)}</time>;
|
||||
let actions: HTMLElement[] = [];
|
||||
|
||||
if(Misuzu.User.isLoggedIn() && Misuzu.User.localUser.perms.canVoteOnComment()) {
|
||||
actions.push(<a class="comment__action comment__action--link comment__action--vote comment__action--like"
|
||||
data-comment-id={comment.comment_id} data-comment-vote={CommentVoteType.Like}
|
||||
href="javascript:void(0);" onClick={commentVoteEventHandler}>Like</a>);
|
||||
actions.push(<a class="comment__action comment__action--link comment__action--vote comment__action--dislike"
|
||||
data-comment-id={comment.comment_id} data-comment-vote={CommentVoteType.Dislike}
|
||||
href="javascript:void(0);" onClick={commentVoteEventHandler}>Dislike</a>);
|
||||
}
|
||||
|
||||
const commentText: HTMLDivElement = <div class="comment__text"></div>,
|
||||
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 = <div class="comment" id={"comment-" + comment.comment_id}>
|
||||
<div class="comment__container">
|
||||
<a class="comment__avatar" href={urlFormat('user-profile', [{name:'user',value:comment.user_id}])}>
|
||||
<img class="avatar" alt={comment.username} width={layer <= 1 ? 50 : 40} height={layer <= 1 ? 50 : 40} src={urlFormat('user-avatar', [
|
||||
{ name: 'user', value: comment.user_id },
|
||||
{ name: 'res', value: layer <= 1 ? 100 : 80 }
|
||||
])}/>
|
||||
</a>
|
||||
<div class="comment__content">
|
||||
<div class="comment__info">
|
||||
<a class="comment__user comment__user--link" href={urlFormat('user-profile', [{name:'user',value:comment.user_id}])}
|
||||
style={"--user-colour: " + commentColour.getCSS()}>{comment.username}</a>
|
||||
<a class="comment__link" href={"#comment-" + comment.comment_id}>{commentTime}</a>
|
||||
</div>
|
||||
{commentText}
|
||||
<div class="comment__actions">
|
||||
{actions}
|
||||
<label class="comment__action comment__action--link" for={"comment-reply-toggle-" + comment.comment_id}>Reply</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class={"comment__replies comment__replies--indent-" + layer} id={"comment-" + comment.comment_id + "-replies"}>
|
||||
<input type="checkbox" id={"comment-reply-toggle-" + comment.comment_id} class="comment__reply-toggle" />
|
||||
<form id={"comment-reply-" + comment.comment_id} class="comment comment--input comment--reply" method="post"
|
||||
action="javascript:void(0);" onSubmit={commentPostEventHandler}>
|
||||
<input type="hidden" name="csrf" value={getCSRFToken()} />
|
||||
<input type="hidden" name="comment[category]" value={comment.category_id} />
|
||||
<input type="hidden" name="comment[reply]" value={comment.comment_id} />
|
||||
<div class="comment__container">
|
||||
<div class="avatar comment__avatar">
|
||||
<img class="avatar" width="40" height="40" src={urlFormat('user-avatar', [{name:'user',value:comment.user_id},{name:'res',value:80}])}/>
|
||||
</div>
|
||||
<div class="comment__content">
|
||||
<div class="comment__info"></div>
|
||||
<textarea class="comment__text input__textarea comment__text--input" name="comment[text]" placeholder="Share your extensive insights..." onKeyDown={commentInputEventHandler}></textarea>
|
||||
<div class="comment__actions">
|
||||
<button class="input__button comment__action comment__action--button comment__action--post">Reply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
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<HTMLAnchorElement> = 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();
|
||||
}
|
|
@ -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<HTMLInputElement> = document.getElementsByName('csrf') as NodeListOf<HTMLInputElement>;
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
function forumPollsInit(): void {
|
||||
const polls: HTMLCollectionOf<Element> = 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<HTMLInputElement> = poll.getElementsByClassName('input__checkbox__input') as HTMLCollectionOf<HTMLInputElement>,
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
enum Parser {
|
||||
PlainText = 0,
|
||||
BBCode = 1,
|
||||
Markdown = 2,
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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<HTMLElement> = document.getElementsByClassName('js-user-relation-action') as HTMLCollectionOf<HTMLElement>;
|
||||
|
||||
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();
|
||||
}
|
|
@ -1,224 +0,0 @@
|
|||
/// <reference path="Support.ts" />
|
||||
/// <reference path="Comments.tsx" />
|
||||
/// <reference path="FormUtilities.ts" />
|
||||
/// <reference path="UserRelations.ts" />
|
||||
/// <reference path="Forum/Posting.ts" />
|
||||
/// <reference path="UrlRegistry.ts" />
|
||||
/// <reference path="Forum/Polls.ts" />
|
||||
|
||||
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<HTMLFormElement> = document.getElementsByClassName('js-login-form') as HTMLCollectionOf<HTMLFormElement>;
|
||||
|
||||
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<HTMLInputElement> = document.getElementsByClassName('js-ctrl-enter-submit') as HTMLCollectionOf<HTMLInputElement>;
|
||||
|
||||
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<HTMLDivElement> = 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;
|
||||
}
|
76
build.php
76
build.php
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Misuzu Asset Build Script.
|
||||
*/
|
||||
|
||||
/**
|
||||
* BEYOND THIS POINT YOU WON'T HAVE TO EDIT THE CONFIG PRETTY MUCH EVER
|
||||
*/
|
||||
|
||||
define('ASSETS_DIR', __DIR__ . '/assets');
|
||||
define('TS_DIR', ASSETS_DIR . '/typescript');
|
||||
|
||||
define('PUBLIC_DIR', __DIR__ . '/public');
|
||||
define('JS_DIR', PUBLIC_DIR . '/js');
|
||||
|
||||
define('TWIG_DIRECTORY', sys_get_temp_dir() . '/msz-tpl-cache-' . md5(__DIR__));
|
||||
|
||||
/**
|
||||
* FUNCTIONS
|
||||
*/
|
||||
|
||||
function build_log(string $text = ''): void {
|
||||
echo strlen($text) > 0 ? date('<H:i:s> ') . $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, '*');
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,5 @@
|
|||
<script src="/vendor/timeago/timeago.locales.min.js" type="text/javascript"></script>
|
||||
<script src="/vendor/highlightjs/highlight.pack.js" type="text/javascript"></script>
|
||||
<script src="/assets/misuzu.js" type="text/javascript"></script>
|
||||
<script src="{{ '/js/misuzu.js'|asset_url }}" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Add table
Reference in a new issue