655 lines
20 KiB
TypeScript
655 lines
20 KiB
TypeScript
/*
|
|
* Sakura Yuuno
|
|
*/
|
|
|
|
// Notification class
|
|
interface Notification {
|
|
read: boolean;
|
|
title: string;
|
|
text: string;
|
|
link: string;
|
|
img: string;
|
|
timeout: number;
|
|
sound: boolean;
|
|
}
|
|
|
|
// Spawns a notification
|
|
function notifyUI(content: Notification): void {
|
|
// Grab the container and create an ID
|
|
var cont: HTMLElement = document.getElementById('notifications');
|
|
var id: string = 'sakura-notification-' + Date.now();
|
|
|
|
// Create the elements
|
|
var alert: HTMLDivElement = document.createElement('div');
|
|
var aIcon: HTMLDivElement = document.createElement('div');
|
|
var aCont: HTMLDivElement = document.createElement('div');
|
|
var aTitle: HTMLDivElement = document.createElement('div');
|
|
var aText: HTMLDivElement = document.createElement('div');
|
|
var aClose: HTMLDivElement = document.createElement('div');
|
|
var aCIcon: HTMLDivElement = document.createElement('div');
|
|
var aClear: HTMLDivElement = document.createElement('div');
|
|
var aIconCont: any;
|
|
|
|
// Add attributes to the main element
|
|
alert.className = 'notification-enter';
|
|
alert.id = id;
|
|
|
|
// Add the icon
|
|
if ((typeof content.img).toLowerCase() === 'undefined' || content.img == null || content.img.length < 2) {
|
|
aIconCont = document.createElement('div');
|
|
aIconCont.className = 'font-icon fa fa-info fa-4x';
|
|
} else if (content.img.substr(0, 5) == 'FONT:') {
|
|
aIconCont = document.createElement('div');
|
|
aIconCont.className = 'font-icon fa ' + content.img.replace('FONT:', '') + ' fa-4x';
|
|
} else {
|
|
aIconCont = document.createElement('img');
|
|
aIconCont.alt = id;
|
|
aIconCont.src = content.img;
|
|
}
|
|
|
|
aIcon.appendChild(aIconCont);
|
|
aIcon.className = 'notification-icon';
|
|
alert.appendChild(aIcon);
|
|
|
|
// Add the content
|
|
aCont.className = 'notification-content';
|
|
aTitle.className = 'notification-title';
|
|
aText.className = 'notifcation-text';
|
|
aTitle.textContent = content.title;
|
|
aText.textContent = content.text;
|
|
|
|
// Check if a link exists and add if it does
|
|
if ((typeof content.link).toLowerCase() !== 'undefined' && content.link !== null && content.link.length > 1) {
|
|
alert.setAttribute('sakurahref', content.link);
|
|
aCont.setAttribute('onclick', content.link.substr(0, 11) == 'javascript:' ? content.link.substring(11) : 'notifyOpen(this.parentNode.id);');
|
|
}
|
|
|
|
// Append stuff
|
|
aCont.appendChild(aTitle);
|
|
aCont.appendChild(aText);
|
|
alert.appendChild(aCont);
|
|
|
|
// Add the close button
|
|
aClose.className = 'notification-close';
|
|
aClose.setAttribute('onclick', 'notifyClose(this.parentNode.id);');
|
|
aClose.appendChild(aCIcon);
|
|
alert.appendChild(aClose);
|
|
|
|
// Append the notification to the document
|
|
cont.appendChild(alert);
|
|
|
|
// Play sound if request
|
|
if (content.sound) {
|
|
// Create the elements
|
|
var sound: HTMLAudioElement = document.createElement('audio');
|
|
var mp3: HTMLSourceElement = document.createElement('source');
|
|
var ogg: HTMLSourceElement = document.createElement('source');
|
|
|
|
// Assign attribs
|
|
mp3.type = 'audio/mp3';
|
|
ogg.type = 'audio/ogg';
|
|
mp3.src = sakuraVars.content + '/sounds/notify.mp3';
|
|
ogg.src = sakuraVars.content + '/sounds/notify.ogg';
|
|
|
|
// Append
|
|
sound.appendChild(mp3);
|
|
sound.appendChild(ogg);
|
|
|
|
// Less loud
|
|
sound.volume = 0.5;
|
|
|
|
// And play
|
|
sound.play();
|
|
}
|
|
|
|
// If keepalive is 0 keep the notification open forever
|
|
if (content.timeout > 0) {
|
|
// Set a timeout and close after an amount
|
|
setTimeout(() => {
|
|
notifyClose(id);
|
|
}, content.timeout);
|
|
}
|
|
}
|
|
|
|
// Closing a notification
|
|
function notifyClose(id: string): void {
|
|
// Get the element
|
|
var e: HTMLElement = document.getElementById(id);
|
|
|
|
// Add the animation
|
|
e.className = 'notification-exit';
|
|
|
|
// Remove after 410 ms
|
|
setTimeout(() => {
|
|
Sakura.removeById(id);
|
|
}, 410);
|
|
}
|
|
|
|
// Opening an alerted link
|
|
function notifyOpen(id: string): void {
|
|
var sakuraHref: string = document.getElementById(id).getAttribute('sakurahref');
|
|
|
|
if ((typeof sakuraHref).toLowerCase() !== 'undefined') {
|
|
window.location.assign(sakuraHref);
|
|
}
|
|
}
|
|
|
|
// Request notifications
|
|
function notifyRequest(session: string): void {
|
|
// Check if the document isn't hidden
|
|
if (document.hidden) {
|
|
return;
|
|
}
|
|
|
|
// Create AJAX object
|
|
var get: AJAX = new AJAX();
|
|
get.setUrl('/settings.php?request-notifications=true&time=' + Sakura.epoch() + '&session=' + session);
|
|
|
|
// Add callbacks
|
|
get.addCallback(200, () => {
|
|
// Assign the parsed JSON
|
|
var data: Notification = JSON.parse(get.response());
|
|
|
|
// Check if nothing went wrong
|
|
if ((typeof data).toLowerCase() === 'undefined') {
|
|
// Inform the user
|
|
throw "No or invalid data was returned";
|
|
|
|
// Stop
|
|
return;
|
|
}
|
|
|
|
// Create an object for every notification
|
|
for (var id in data) {
|
|
notifyUI(data[id]);
|
|
}
|
|
});
|
|
|
|
get.start(HTTPMethods.GET);
|
|
}
|
|
|
|
// Show the full page busy window
|
|
function ajaxBusyView(show: boolean, message: string = null, type: string = null): void {
|
|
// Get elements
|
|
var cont: HTMLElement = document.getElementById('ajaxBusy');
|
|
var stat: HTMLElement = document.getElementById('ajaxStatus');
|
|
var anim: HTMLElement = document.getElementById('ajaxAnimate');
|
|
var body: HTMLElement = document.getElementById('contentwrapper');
|
|
var icon: string = 'fa fa-4x ';
|
|
|
|
// Select the proper icon
|
|
switch (type) {
|
|
case 'ok':
|
|
icon += 'fa-check';
|
|
break;
|
|
case 'fail':
|
|
icon += 'fa-remove';
|
|
break;
|
|
case 'busy':
|
|
default:
|
|
icon += 'fa-refresh fa-spin';
|
|
break;
|
|
}
|
|
|
|
// If request to show the window, build it
|
|
if (show) {
|
|
if ((typeof cont).toLowerCase() === 'undefined' || cont === null) {
|
|
// Container
|
|
var cCont = document.createElement('div');
|
|
cCont.className = 'ajax-busy';
|
|
cCont.id = 'ajaxBusy';
|
|
|
|
// Inner
|
|
var cInner = document.createElement('div');
|
|
cInner.className = 'ajax-inner';
|
|
cCont.appendChild(cInner);
|
|
|
|
// Desc
|
|
var cMsg = document.createElement('h2');
|
|
cMsg.id = 'ajaxStatus';
|
|
cInner.appendChild(cMsg);
|
|
|
|
// Icon
|
|
var cIco = document.createElement('div');
|
|
cIco.id = 'ajaxAnimate';
|
|
cInner.appendChild(cIco);
|
|
|
|
// Append to document
|
|
body.appendChild(cCont);
|
|
|
|
// Reassign
|
|
cont = document.getElementById('ajaxBusy');
|
|
stat = document.getElementById('ajaxStatus');
|
|
anim = document.getElementById('ajaxAnimate');
|
|
}
|
|
|
|
// Update the icon
|
|
anim.className = icon;
|
|
|
|
// Update the message
|
|
stat.textContent = (message === null ? '' : message);
|
|
} else {
|
|
if (cont !== null) {
|
|
var out: any = setInterval(() => {
|
|
if (cont.style.opacity === null || cont.style.opacity === "") {
|
|
cont.style.opacity = "1";
|
|
}
|
|
|
|
// If the value isn't 0 yet subtract by .1
|
|
if (parseInt(cont.style.opacity) > 0) {
|
|
cont.style.opacity = (parseInt(cont.style.opacity) - 0.1).toString();
|
|
} else {
|
|
Sakura.removeById('ajaxBusy');
|
|
clearInterval(out);
|
|
}
|
|
}, 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Making a post request using AJAX
|
|
function ajaxPost(url: string, data: Object, callback: Function): AJAX {
|
|
// Create AJAX
|
|
var request = new AJAX();
|
|
|
|
// Set url
|
|
request.setUrl(url);
|
|
|
|
// Add callbacks
|
|
request.addCallback(200, function () {
|
|
callback.call(request.response())
|
|
});
|
|
request.addCallback(0, function () {
|
|
ajaxBusyView(false);
|
|
|
|
throw "POST Request failed";
|
|
});
|
|
|
|
// Add header
|
|
request.addHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
|
|
// Set the post data
|
|
request.setSend(data);
|
|
|
|
// Make the request
|
|
request.start(HTTPMethods.POST);
|
|
|
|
// Return the AJAX object
|
|
return request;
|
|
}
|
|
|
|
// Convert a href attr to an object
|
|
function prepareAjaxLink(linkId: any, callback: Function, attrs: string = null): void {
|
|
// Get element
|
|
var link: HTMLElement = (typeof linkId).toLowerCase() === 'object' ? linkId : document.getElementById(linkId);
|
|
|
|
// Catch null
|
|
if (link === null) {
|
|
return;
|
|
}
|
|
|
|
// Get the raw HREF value
|
|
var href: string = link.getAttribute('href');
|
|
|
|
// Get the action
|
|
var action: string = href.split('?')[0];
|
|
|
|
// Split the request variables
|
|
var varEarly: string[] = href.split('?')[1].split('&');
|
|
|
|
// Create storage thing
|
|
var variables: Object = new Object();
|
|
|
|
// Split them
|
|
for (var k in varEarly) {
|
|
// Split
|
|
var newVar: string[] = varEarly[k].split('=');
|
|
|
|
// Push
|
|
variables[newVar[0]] = newVar[1];
|
|
}
|
|
|
|
// Add ajax=true
|
|
variables['ajax'] = true;
|
|
|
|
// Update link attributes
|
|
link.setAttribute('href', 'javascript:void(0);');
|
|
link.setAttribute('onclick', callback + '(\'' + action + '\', JSON.parse(\'' + JSON.stringify(variables) + '\')' + (typeof attrs != 'undefined' ? attrs : '') + ');');
|
|
}
|
|
|
|
// Prepare a form for an AJAX request
|
|
function prepareAjaxForm(formId: string, message: string, resetCaptcha: boolean = false): void {
|
|
// Get the form
|
|
var form: HTMLElement = document.getElementById(formId);
|
|
|
|
// Create hidden ajax input
|
|
var hide: HTMLInputElement = document.createElement('input');
|
|
|
|
// Set the attributes
|
|
hide.name = 'ajax';
|
|
hide.value = 'true';
|
|
hide.type = 'hidden';
|
|
form.appendChild(hide);
|
|
|
|
// Update form
|
|
form.setAttribute('onsubmit', 'submitPost(\'' + form.getAttribute('action') + '\', formToObject(\'' + formId + '\'), true, \'' + (message ? message : 'Please wait...') + '\', ' + (resetCaptcha ? 'true' : 'false') + ');');
|
|
form.setAttribute('action', 'javascript:void(0);');
|
|
}
|
|
|
|
// Convert form to an object
|
|
function formToObject(formId: string): Object {
|
|
// Get the form
|
|
var form: any = document.getElementById(formId);
|
|
|
|
// Make an object for the request parts
|
|
var requestParts: Object = new Object();
|
|
|
|
// Get all the children with a name attr
|
|
var children = form.querySelectorAll('[name]');
|
|
|
|
// Sort the children and make them ready for submission
|
|
for (var i in children) {
|
|
if ((typeof children[i]).toLowerCase() === 'object') {
|
|
requestParts[children[i].name] = ((typeof children[i].type !== "undefined" && children[i].type.toLowerCase() == "checkbox") ? (children[i].checked ? 1 : 0) : children[i].value);
|
|
}
|
|
}
|
|
|
|
// Return the request parts
|
|
return requestParts;
|
|
}
|
|
|
|
// Quickly building a form
|
|
function generateForm(formId: string, formAttr: Object, formData: Object, appendTo: string = null): HTMLFormElement {
|
|
// Create form element
|
|
var form: HTMLFormElement = document.createElement('form');
|
|
form.id = formId;
|
|
|
|
// Set additional attrs
|
|
for (var c in formAttr) {
|
|
form.setAttribute(c, formAttr[c]);
|
|
}
|
|
|
|
// Set data
|
|
for (var a in formData) {
|
|
var b: HTMLInputElement = document.createElement('input');
|
|
b.type = 'hidden';
|
|
b.name = a;
|
|
b.value = formData[a];
|
|
form.appendChild(b);
|
|
}
|
|
|
|
// Append to something if requested
|
|
if (appendTo !== null) {
|
|
document.getElementById(appendTo).appendChild(form);
|
|
}
|
|
|
|
return form;
|
|
}
|
|
|
|
// Submitting a post using AJAX
|
|
function submitPost(action: string, requestParts: Object, busyView: boolean, msg: string, resetCaptcha: boolean): void {
|
|
// If requested display the busy thing
|
|
if (busyView) {
|
|
ajaxBusyView(true, msg, 'busy');
|
|
}
|
|
|
|
// Submit the AJAX
|
|
var request = ajaxPost(action, requestParts, () => {
|
|
submitPostHandler(request.response(), busyView, resetCaptcha);
|
|
});
|
|
}
|
|
|
|
// Handling a submitted form using AJAX
|
|
function submitPostHandler(data: string, busyView: boolean, resetCaptcha: boolean): void {
|
|
// Split the result
|
|
var result: string[] = data.split('|');
|
|
|
|
// If using the bust view thing update the text displayed to the return of the request
|
|
if (busyView) {
|
|
ajaxBusyView(true, result[0], (result[1] == '1' ? 'ok' : 'fail'));
|
|
}
|
|
|
|
// Reset captcha
|
|
if (resetCaptcha && result[1] != '1' && sakuraVars.recaptchaEnabled != '0') {
|
|
grecaptcha.reset();
|
|
}
|
|
|
|
setTimeout(() => {
|
|
if (busyView) {
|
|
ajaxBusyView(false);
|
|
}
|
|
|
|
if (result[1] == '1') {
|
|
window.location.assign(result[2]);
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
// Check if a password is within the minimum entropy value
|
|
function checkPwdEntropy(pwd: string): boolean {
|
|
return (Sakura.entropy(pwd) >= sakuraVars.minPwdEntropy);
|
|
}
|
|
|
|
// Check registration variables
|
|
function registerVarCheck(id: string, mode: string, option: any = null): void {
|
|
// Get the element we're working with
|
|
var input: any = document.getElementById(id);
|
|
var check: boolean = null;
|
|
|
|
// Use the proper mode
|
|
switch (mode) {
|
|
case 'confirmpw':
|
|
option = document.getElementById(option);
|
|
check = input.value === option.value;
|
|
break;
|
|
|
|
case 'password':
|
|
check = checkPwdEntropy(input.value);
|
|
break;
|
|
|
|
case 'email':
|
|
check = Sakura.validateEmail(input.value);
|
|
break;
|
|
|
|
case 'username':
|
|
default:
|
|
check = Sakura.stringLength(input.value, sakuraVars.minUserLen, sakuraVars.maxUserLen);
|
|
break;
|
|
}
|
|
|
|
if (input.className.indexOf(check ? 'green' : 'red') < 0) {
|
|
input.className = input.className + ' ' + (check ? 'green' : 'red');
|
|
}
|
|
|
|
if (input.className.indexOf(check ? 'red' : 'green') > 0) {
|
|
input.className = input.className.replace(check ? 'red' : 'green', '');
|
|
}
|
|
}
|
|
|
|
// Replace some special tags
|
|
function replaceTag(tag: string): string {
|
|
return { '&': '&', '<': '<', '>': '>' }[tag] || tag;
|
|
}
|
|
|
|
// ^
|
|
function safeTagsReplace(str: string): string {
|
|
return str.replace(/[&<>]/g, replaceTag);
|
|
}
|
|
|
|
// Open a comment reply field
|
|
function commentReply(id: number, session: string, category: string, action: string, avatar: string): void {
|
|
// Find subject post
|
|
var replyingTo: HTMLElement = document.getElementById('comment-' + id);
|
|
|
|
// Check if it actually exists
|
|
if ((typeof replyingTo).toLowerCase() === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
// Attempt to get previously created box
|
|
var replyBox: HTMLElement = document.getElementById('comment-reply-container-' + id);
|
|
|
|
// Remove it if it already exists
|
|
if (replyBox) {
|
|
Sakura.removeById('comment-reply-container-' + id);
|
|
return;
|
|
}
|
|
|
|
// Container
|
|
var replyContainer: HTMLLIElement = document.createElement('li');
|
|
replyContainer.id = 'comment-reply-container-' + id;
|
|
|
|
// Form
|
|
var replyForm: HTMLFormElement = document.createElement('form');
|
|
replyForm.id = 'comment-reply-' + id;
|
|
replyForm.action = action;
|
|
replyForm.method = 'post';
|
|
|
|
// Session
|
|
var replyInput: HTMLInputElement = document.createElement('input');
|
|
replyInput.type = 'hidden';
|
|
replyInput.name = 'session';
|
|
replyInput.value = session;
|
|
replyForm.appendChild(replyInput);
|
|
|
|
// Category
|
|
var replyInput: HTMLInputElement = document.createElement('input');
|
|
replyInput.type = 'hidden';
|
|
replyInput.name = 'category';
|
|
replyInput.value = category;
|
|
replyForm.appendChild(replyInput);
|
|
|
|
// Reply ID
|
|
var replyInput: HTMLInputElement = document.createElement('input');
|
|
replyInput.type = 'hidden';
|
|
replyInput.name = 'replyto';
|
|
replyInput.value = id.toString();
|
|
replyForm.appendChild(replyInput);
|
|
|
|
// Mode
|
|
var replyInput: HTMLInputElement = document.createElement('input');
|
|
replyInput.type = 'hidden';
|
|
replyInput.name = 'mode';
|
|
replyInput.value = 'comment';
|
|
replyForm.appendChild(replyInput);
|
|
|
|
// Comment container
|
|
var replyDiv: HTMLDivElement = document.createElement('div');
|
|
replyDiv.className = 'comment';
|
|
|
|
// Avatar
|
|
var replyAvatar: HTMLDivElement = document.createElement('div');
|
|
replyAvatar.className = 'comment-avatar';
|
|
replyAvatar.style.backgroundImage = 'url(' + avatar + ')';
|
|
replyDiv.appendChild(replyAvatar);
|
|
|
|
// Pointer
|
|
var replyPoint: HTMLDivElement = document.createElement('div');
|
|
replyPoint.className = 'comment-pointer';
|
|
replyDiv.appendChild(replyPoint);
|
|
|
|
// Textarea
|
|
var replyText: HTMLTextAreaElement = document.createElement('textarea');
|
|
replyText.className = 'comment-content';
|
|
replyText.name = 'comment';
|
|
replyDiv.appendChild(replyText);
|
|
|
|
// Submit
|
|
var replySubmit: HTMLInputElement = document.createElement('input');
|
|
replySubmit.className = 'comment-submit';
|
|
replySubmit.type = 'submit';
|
|
replySubmit.name = 'submit';
|
|
replySubmit.value = "\uf1d8";
|
|
replyDiv.appendChild(replySubmit);
|
|
|
|
// Append to form
|
|
replyForm.appendChild(replyDiv);
|
|
|
|
// Append form to container
|
|
replyContainer.appendChild(replyForm);
|
|
|
|
// Insert the HTML
|
|
if (replyingTo.children[1].children.length > 0) {
|
|
replyingTo.children[1].insertBefore(replyContainer, replyingTo.children[1].firstChild);
|
|
} else {
|
|
replyingTo.children[1].appendChild(replyContainer);
|
|
}
|
|
|
|
// Prepare AJAX submission
|
|
prepareAjaxForm(replyForm.id, 'Replying...');
|
|
}
|
|
|
|
// Inserting text into text box
|
|
// Borrowed from http://stackoverflow.com/questions/1064089/inserting-a-text-where-cursor-is-using-javascript-jquery (therefore not in Typescript format, fix this later)
|
|
function insertText(areaId, text) {
|
|
var txtarea = document.getElementById(areaId);
|
|
var scrollPos = txtarea.scrollTop;
|
|
var strPos = 0;
|
|
var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ?
|
|
"ff" : (document.selection ? "ie" : false));
|
|
if (br == "ie") {
|
|
txtarea.focus();
|
|
var range = document.selection.createRange();
|
|
range.moveStart('character', -txtarea.value.length);
|
|
strPos = range.text.length;
|
|
}
|
|
else if (br == "ff") strPos = txtarea.selectionStart;
|
|
|
|
var front = (txtarea.value).substring(0, strPos);
|
|
var back = (txtarea.value).substring(strPos, txtarea.value.length);
|
|
txtarea.value = front + text + back;
|
|
strPos = strPos + text.length;
|
|
if (br == "ie") {
|
|
txtarea.focus();
|
|
var range = document.selection.createRange();
|
|
range.moveStart('character', -txtarea.value.length);
|
|
range.moveStart('character', strPos);
|
|
range.moveEnd('character', 0);
|
|
range.select();
|
|
}
|
|
else if (br == "ff") {
|
|
txtarea.selectionStart = strPos;
|
|
txtarea.selectionEnd = strPos;
|
|
txtarea.focus();
|
|
}
|
|
txtarea.scrollTop = scrollPos;
|
|
}
|
|
|
|
// Inserting a bbcode
|
|
function insertBBcode(textarea: string, tag: string, arg: boolean = false): void {
|
|
var element = document.getElementById(textarea);
|
|
var before = "[" + tag + (arg ? "=" : "") + "]";
|
|
var after = "[/" + tag + "]";
|
|
|
|
if (document.selection) {
|
|
element.focus();
|
|
var sel = document.selection.createRange();
|
|
sel.text = before + sel.text + after;
|
|
element.focus();
|
|
} else if (element.selectionStart || element.selectionStart === 0) {
|
|
var startPos = element.selectionStart;
|
|
var endPos = element.selectionEnd;
|
|
var scrollTop = element.scrollTop;
|
|
element.value = element.value.substring(0, startPos) + before + element.value.substring(startPos, endPos) + after + element.value.substring(endPos, element.value.length);
|
|
element.focus();
|
|
element.selectionStart = startPos + before.length;
|
|
element.selectionEnd = endPos + before.length;
|
|
element.scrollTop = scrollTop;
|
|
} else {
|
|
element.value += before + after;
|
|
element.focus();
|
|
}
|
|
}
|
|
|
|
// Formatting money
|
|
Number.prototype.formatMoney = function (u, c, k) {
|
|
var f = this,
|
|
u = isNaN(u = Math.abs(u)) ? 2 : u,
|
|
c = c == undefined ? "." : c,
|
|
k = k == undefined ? "," : k,
|
|
i = f < 0 ? "-" : "",
|
|
n = parseInt(f = Math.abs(+f || 0).toFixed(u)) + "",
|
|
g = (g = n.length) > 3 ? g % 3 : 0;
|
|
|
|
return i + (g ? n.substr(0, g) + k : "") + n.substr(g).replace(/(\c{3})(?=\c)/g, "$1" + k) + (u ? c + Math.abs(f - n).toFixed(u).slice(2) : "");
|
|
};
|