diff --git a/app/Controllers/StatusController.php b/app/Controllers/StatusController.php new file mode 100644 index 0000000..871ae51 --- /dev/null +++ b/app/Controllers/StatusController.php @@ -0,0 +1,22 @@ + + */ +class StatusController extends Controller +{ + public function index() + { + return view('status/index'); + } +} diff --git a/app/Template.php b/app/Template.php index 68a383b..8e2b023 100644 --- a/app/Template.php +++ b/app/Template.php @@ -90,10 +90,20 @@ class Template $views_dir = ROOT . 'resources/views/'; // Initialise Twig Filesystem Loader - $twigLoader = new Twig_Loader_Filesystem([$views_dir . self::$name, $views_dir . 'shared/']); + $loader = new Twig_Loader_Filesystem(); + + foreach (glob("{$views_dir}*") as $dir) { + $key = basename($dir); + + if ($key === self::$name) { + $key = '__main__'; + } + + $loader->addPath($dir, $key); + } // Environment variable - $twigEnv = [ + $env = [ 'cache' => config("performance.template_cache") ? realpath(ROOT . config("performance.cache_dir") . 'views') : false, @@ -102,7 +112,7 @@ class Template ]; // And now actually initialise the templating engine - self::$engine = new Twig_Environment($twigLoader, $twigEnv); + self::$engine = new Twig_Environment($loader, $env); // Load String template loader self::$engine->addExtension(new Twig_Extension_StringLoader()); diff --git a/public/images/status-banner.png b/public/images/status-banner.png new file mode 100644 index 0000000..3064da6 Binary files /dev/null and b/public/images/status-banner.png differ diff --git a/resources/assets/typescript/Sakura.ts b/resources/assets/typescript/Sakura.ts new file mode 100644 index 0000000..c9ceeed --- /dev/null +++ b/resources/assets/typescript/Sakura.ts @@ -0,0 +1,17 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// diff --git a/resources/assets/typescript/Sakura/Changelog.ts b/resources/assets/typescript/Sakura/Changelog.ts index c60978d..c8a90ee 100644 --- a/resources/assets/typescript/Sakura/Changelog.ts +++ b/resources/assets/typescript/Sakura/Changelog.ts @@ -3,7 +3,7 @@ namespace Sakura export class Changelog { private static Client: AJAX; - private static Element: DOM; + private static Element: HTMLDivElement; private static Fetch: number = 0; private static Colours: string[] = [ 'inherit', // Unknown @@ -15,25 +15,25 @@ namespace Sakura '#C44', // Revert ]; - public static Build(target: DOM) + public static Build(target: HTMLElement) { this.Client = new AJAX; - this.Element = DOM.Create('table', 'changelog panelTable'); + this.Element = DOM.Create('table', 'changelog panelTable'); - this.Element.Element.style.borderSpacing = '0 1px'; + this.Element.style.borderSpacing = '0 1px'; - var title: DOM = DOM.Create('div', 'head'), - link: DOM = DOM.Create('a', 'underline'); + var title: HTMLDivElement = DOM.Create('div', 'head'), + link: HTMLLinkElement = DOM.Create('a', 'underline'); - title.Element.style.marginBottom = '1px'; + title.style.marginBottom = '1px'; - link.Text('Changelog'); - (link.Element).href = Config.ChangelogUrl + '#r' + Config.Revision; - (link.Element).target = '_blank'; + link.innerText = 'Changelog'; + link.href = Config.ChangelogUrl + '#r' + Config.Revision; + link.target = '_blank'; - title.Append(link); - target.Append(title); - target.Append(this.Element); + DOM.Append(title, link); + DOM.Append(target, title); + DOM.Append(target, this.Element); this.Client.SetUrl(Config.ChangelogUrl + Config.ChangelogApi); @@ -53,34 +53,33 @@ namespace Sakura private static Add(changelog: IChangelogDate) { - var header: DOM = DOM.Create('tr', 'changelog__row changelog__row--header'), - headerInner: DOM = DOM.Create('th', 'changelog__header'); + var header: HTMLTableRowElement = DOM.Create('tr', 'changelog__row changelog__row--header'), + headerInner: HTMLTableHeaderCellElement = DOM.Create('th', 'changelog__header'); - headerInner.Text(changelog.date); - headerInner.Element.style.fontSize = '1.2em'; - (headerInner.Element).colSpan = 2; + headerInner.innerText = changelog.date; + headerInner.style.fontSize = '1.2em'; + headerInner.colSpan = 2; - header.Append(headerInner); - this.Element.Append(header); + DOM.Append(header, headerInner); + DOM.Append(this.Element, header); for (var _i in changelog.changes) { var change: IChangelogChange = changelog.changes[_i], - row: DOM = DOM.Create('tr', 'changelog__row'), - action: DOM = DOM.Create('td', 'changelog__column'), - message: DOM = DOM.Create('td', 'changelog__column'); + row: HTMLTableRowElement = DOM.Create('tr', 'changelog__row'), + action: HTMLTableCellElement = DOM.Create('td', 'changelog__column'), + message: HTMLTableCellElement = DOM.Create('td', 'changelog__column'); - action.Text(change.action.name); - action.Element.style.backgroundColor = this.Colours[change.action.id]; - action.Element.style.borderBottom = '1px solid ' + this.Colours[change.action.id]; + action.innerText = change.action.name; + action.style.backgroundColor = this.Colours[change.action.id]; + action.style.borderBottom = '1px solid ' + this.Colours[change.action.id]; - message.Text(change.message); - message.Element.style.borderBottom = '1px solid ' + this.Colours[change.action.id]; + message.innerText = change.message; + message.style.borderBottom = '1px solid ' + this.Colours[change.action.id]; - row.Append(action); - row.Append(message); - - this.Element.Append(row); + DOM.Append(row, action); + DOM.Append(row, message); + DOM.Append(this.Element, row); } } } diff --git a/resources/assets/typescript/Sakura/Config.ts b/resources/assets/typescript/Sakura/Config.ts index c46f610..93396fe 100644 --- a/resources/assets/typescript/Sakura/Config.ts +++ b/resources/assets/typescript/Sakura/Config.ts @@ -5,9 +5,6 @@ namespace Sakura public static Revision: number = 0; public static UserId: number = 0; public static SessionId: string = ""; - public static UserNameMinLength: number = 3; - public static UserNameMaxLength: number = 16; - public static PasswordMinEntropy: number = 48; public static LoggedIn: boolean = false; public static ChangelogUrl: string = "https://sakura.flash.moe/"; public static ChangelogApi: string = "api.php/"; diff --git a/resources/assets/typescript/Sakura/DOM.ts b/resources/assets/typescript/Sakura/DOM.ts index 4f1c645..bab1980 100644 --- a/resources/assets/typescript/Sakura/DOM.ts +++ b/resources/assets/typescript/Sakura/DOM.ts @@ -2,73 +2,186 @@ namespace Sakura { export class DOM { - public Element: HTMLElement; + public static BEM(block: string, element: string = null, modifiers: string[] = [], firstModifierOnly: boolean = false): string + { + var className: string = ""; - constructor(object: any, mode: DOMSelector) { - switch (mode) { - case DOMSelector.ID: - this.Element = document.getElementById(object); - break; - - case DOMSelector.CLASS: - this.Element = document.getElementsByClassName(object)[0]; - break; - - case DOMSelector.ELEMENT: - this.Element = object; - break; - - case DOMSelector.QUERY: - this.Element = document.querySelector(object); - break; + if (firstModifierOnly && modifiers.length === 0) { + return null; } + + className += block; + + if (element !== null) { + className += "__" + element; + } + + var baseName: string = className; + + for (var _i in modifiers) { + if (firstModifierOnly) { + return baseName + "--" + modifiers[_i]; + } + + className += " " + baseName + "--" + modifiers[_i]; + } + + return className; } - public static Create(element: string, className: string = null, id: string = null): DOM { - var elem: HTMLElement = document.createElement(element), - cont: DOM = new DOM(elem, DOMSelector.ELEMENT); + public static Create(name: string, className: string = null, id: string = null): HTMLElement { + var element = document.createElement(name); if (className !== null) { - cont.SetClass(className); + element.className = className; } if (id !== null) { - cont.SetId(id); + element.id = id; } - return cont; + return element; } - public Text(text: string): void { - this.Element.appendChild(document.createTextNode(text)); + public static Text(text: string): Text { + return document.createTextNode(text); } - public Append(element: DOM): void { - this.Element.appendChild(element.Element); + public static ID(id: string): HTMLElement { + return document.getElementById(id); } - public Prepend(element: DOM, before: HTMLElement | Node = null): void { + public static Remove(element: HTMLElement): void { + element.parentNode.removeChild(element); + } + + public static Class(className: string): NodeListOf { + return >document.getElementsByClassName(className); + } + + public static Prepend(target: HTMLElement, element: HTMLElement | Text, before: HTMLElement | Node = null): void { if (before === null) { - before = this.Element.firstChild; + before = target.firstChild; } - if (this.Element.children.length) { - this.Element.insertBefore(element.Element, before); + if (target.children.length) { + target.insertBefore(element, before); } else { - this.Append(element); + this.Append(target, element); } } - public SetId(id: string): void { - this.Element.id = id; + public static Append(target: HTMLElement, element: HTMLElement | Text): void { + target.appendChild(element); } - public SetClass(name: string): void { - this.Element.className = name; + public static ClassNames(target: HTMLElement): string[] { + var className: string = target.className, + classes: string[] = []; + + if (className.length > 1) { + classes = className.split(' '); + } + + return classes; } - public Remove(): void { - this.Element.parentNode.removeChild(this.Element); + public static AddClass(target: HTMLElement, classes: string[]): void { + for (var _i in classes) { + var current: string[] = this.ClassNames(target), + index: number = current.indexOf(classes[_i]); + + if (index >= 0) { + continue; + } + + current.push(classes[_i]); + + target.className = current.join(' '); + } + } + + public static RemoveClass(target: HTMLElement, classes: string[]): void { + for (var _i in classes) { + var current: string[] = this.ClassNames(target), + index: number = current.indexOf(classes[_i]); + + if (index < 0) { + continue; + } + + current.splice(index, 1); + + target.className = current.join(' '); + } + } + + public static Clone(subject: HTMLElement): HTMLElement { + return (subject.cloneNode(true)); + } + + public static Query(query: string): NodeListOf { + return document.querySelectorAll(query); + } + + public static SetPosition(element: HTMLTextAreaElement, pos: number, end: boolean = false): void { + if (end) { + element.selectionEnd = pos; + return; + } + + element.selectionStart = pos; + } + + public static GetPosition(element: HTMLTextAreaElement, end: boolean = false): number { + if (end) { + return element.selectionEnd; + } + + return element.selectionStart; + } + + public static GoToStart(element: HTMLTextAreaElement): void { + this.SetPosition(element, 0); + } + + public static GoToEnd(element: HTMLTextAreaElement): void { + this.SetPosition(element, element.value.length); + } + + public static GetSelectionLength(element: HTMLTextAreaElement): number { + var length: number = this.GetPosition(element, true) - this.GetPosition(element); + + if (length < 0) { + length = this.GetPosition(element) - this.GetPosition(element, true); + } + + return length; + } + + public static EnterAtCursor(element: HTMLTextAreaElement, text: string, overwrite: boolean = false): void { + var value: string = this.GetText(element), + final: string = "", + current: number = this.GetPosition(element); + + final += value.slice(0, current); + final += text; + final += value.slice(current + (overwrite ? text.length : 0)); + + this.SetText(element, final); + this.SetPosition(element, current); + } + + public static GetSelectedText(element: HTMLTextAreaElement): string { + return this.GetText(element).slice(this.GetPosition(element), this.GetPosition(element, true)); + } + + public static GetText(element: HTMLTextAreaElement): string { + return element.value; + } + + public static SetText(element: HTMLTextAreaElement, text: string): void { + element.value = text; } } } diff --git a/resources/assets/typescript/Sakura/DOMSelector.ts b/resources/assets/typescript/Sakura/DOMSelector.ts deleted file mode 100644 index 6829598..0000000 --- a/resources/assets/typescript/Sakura/DOMSelector.ts +++ /dev/null @@ -1,10 +0,0 @@ -namespace Sakura -{ - export enum DOMSelector - { - ID, - CLASS, - ELEMENT, - QUERY - } -} diff --git a/resources/assets/typescript/Sakura/Friend.ts b/resources/assets/typescript/Sakura/Friend.ts index 3ed7651..a0671df 100644 --- a/resources/assets/typescript/Sakura/Friend.ts +++ b/resources/assets/typescript/Sakura/Friend.ts @@ -1,5 +1,3 @@ -declare var ajaxBusyView: any; - namespace Sakura { export class Friend @@ -28,18 +26,22 @@ namespace Sakura this.Client.SetSend({ "session": Config.SessionId }); this.Client.AddCallback(200, (client: AJAX) => { var response: IFriendResponse = client.JSON(), - error: string = response.error || null; + alert: INotification = { + id: -(Date.now()), + user: Config.UserId, + time: Math.round(Date.now() / 1000), + read: false, + title: response.error || response.message, + text: "", + link: null, + image: "FONT:fa-user-plus", + timeout: 60000 + }; - if (error !== null) { - ajaxBusyView(true, error, 'fail'); + Notifications.DisplayMethod.call(this, alert); - setTimeout(() => { - ajaxBusyView(false); - }, 1500); - } else { - ajaxBusyView(true, response.message, 'ok'); - location.reload(); - } + // replace this with a thing that just updates the dom + window.location.reload(); }); this.Client.Start(HTTPMethod.POST); } diff --git a/resources/assets/typescript/Sakura/Legacy.ts b/resources/assets/typescript/Sakura/Legacy.ts deleted file mode 100644 index 3ee288f..0000000 --- a/resources/assets/typescript/Sakura/Legacy.ts +++ /dev/null @@ -1,76 +0,0 @@ -declare function escape(a); - -namespace Sakura -{ - export class Legacy - { - // Alternative for Math.log2() since it's still experimental - public static log2(num: number): number { - return Math.log(num) / Math.log(2); - } - - // Get the number of unique characters in a string - public static unique(string: string): number { - // Store the already found character - var used: string[] = []; - - // The amount of characters we've already found - var count: number = 0; - - // Count the amount of unique characters - for (var i = 0; i < string.length; i++) { - // Check if we already counted this character - if (used.indexOf(string[i]) == -1) { - // Push the character into the used array - used.push(string[i]); - - // Up the count - count++; - } - } - - // Return the count - return count; - } - - // Calculate password entropy - public static entropy(string: string): number { - // Decode utf-8 encoded characters - string = this.utf8_decode(string); - - // Count the unique characters in the string - var unique: number = this.unique(string); - - // Do the entropy calculation - return unique * this.log2(256); - } - - // Validate string lengths - public static stringLength(string: string, minimum: number, maximum: number): boolean { - // Get length of string - var length = string.length; - - // Check if it meets the minimum/maximum - if (length < minimum || length > maximum) { - return false; - } - - // If it passes both return true - return true; - } - - // Validate email address formats - public static validateEmail(email: string): boolean { - // RFC compliant e-mail address regex - var re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,48})+$/; - - // Test it on the email var which'll return a boolean - return re.test(email); - } - - // Decode a utf-8 string - public static utf8_decode(string): string { - return decodeURIComponent(escape(string)); - } - } -} diff --git a/resources/assets/typescript/Sakura/Main.ts b/resources/assets/typescript/Sakura/Main.ts deleted file mode 100644 index bc3fb17..0000000 --- a/resources/assets/typescript/Sakura/Main.ts +++ /dev/null @@ -1,16 +0,0 @@ -namespace Sakura -{ - export class Main - { - public static Startup(): void { - console.log(this.Supported()); - TimeAgo.Init(); - Friend.Init(); - Notifications.Init(); - } - - public static Supported(): boolean { - return true; - } - } -} diff --git a/resources/assets/typescript/Sakura/Notifications.ts b/resources/assets/typescript/Sakura/Notifications.ts index bf8bd34..13548f1 100644 --- a/resources/assets/typescript/Sakura/Notifications.ts +++ b/resources/assets/typescript/Sakura/Notifications.ts @@ -4,19 +4,17 @@ namespace Sakura { private static Client: AJAX; private static IntervalContainer: number; - public static DisplayMethod: Function = (alert: INotification) => { - console.log(alert); - }; + public static DisplayMethod: Function = Notifications.Display; public static Init(): void { - this.Client = new AJAX; - this.Client.SetUrl("/notifications"); - this.Client.AddCallback(200, (client: AJAX) => { + Notifications.Client = new AJAX; + Notifications.Client.SetUrl("/notifications"); + Notifications.Client.AddCallback(200, (client: AJAX) => { Notifications.Load(client.JSON()); }); - this.Poll(); - this.Start(); + Notifications.Poll(); + Notifications.Start(); } public static Poll(): void @@ -32,7 +30,7 @@ namespace Sakura } Notifications.Poll(); - }, 5000); + }, 60000); } public static Stop(): void @@ -47,5 +45,10 @@ namespace Sakura this.DisplayMethod(alerts[i]); } } + + public static Display(alert: INotification): void + { + console.log(alert); + } } } diff --git a/resources/assets/typescript/Sakura/TimeAgo.ts b/resources/assets/typescript/Sakura/TimeAgo.ts index 1441e90..6e0e685 100644 --- a/resources/assets/typescript/Sakura/TimeAgo.ts +++ b/resources/assets/typescript/Sakura/TimeAgo.ts @@ -6,7 +6,7 @@ namespace Sakura public static Init(): void { - var watchElements: NodeListOf = document.getElementsByClassName(this.WatchClass); + var watchElements: NodeListOf = DOM.Class(this.WatchClass); for (var _i in watchElements) { if ((typeof watchElements[_i]).toLowerCase() !== 'object') { diff --git a/resources/assets/typescript/Yuuno.ts b/resources/assets/typescript/Yuuno.ts new file mode 100644 index 0000000..e693123 --- /dev/null +++ b/resources/assets/typescript/Yuuno.ts @@ -0,0 +1,6 @@ +/// +/// +/// +/// +/// +/// diff --git a/resources/assets/typescript/Yuuno/Busy.ts b/resources/assets/typescript/Yuuno/Busy.ts new file mode 100644 index 0000000..0f71c9c --- /dev/null +++ b/resources/assets/typescript/Yuuno/Busy.ts @@ -0,0 +1,47 @@ +namespace Yuuno +{ + export class Busy + { + private static Container: HTMLElement; + private static Text: HTMLElement; + private static Icon: HTMLElement; + + public static Init(): void + { + this.Container = Sakura.DOM.ID('busy-window'); + this.Text = Sakura.DOM.ID('busy-status'); + this.Icon = Sakura.DOM.ID('busy-icon'); + } + + public static Hide(): void + { + Sakura.DOM.AddClass(this.Container, ['hidden']); + } + + public static Show(mode: BusyMode = BusyMode.BUSY, text: string = null, hideAfter: number = 0): void + { + var icon: string = "fa fa-4x "; + + switch (mode) { + case BusyMode.OK: + icon += 'fa-check'; + break; + case BusyMode.FAIL: + icon += 'fa-remove'; + break; + case BusyMode.BUSY: + default: + icon += 'fa-refresh fa-spin'; + } + + Sakura.DOM.RemoveClass(this.Icon, Sakura.DOM.ClassNames(this.Icon)); + Sakura.DOM.AddClass(this.Icon, icon.split(' ')); + this.Text.innerText = text || ''; + Sakura.DOM.RemoveClass(this.Container, ['hidden']); + + if (hideAfter > 0) { + setTimeout(Busy.Hide, hideAfter); + } + } + } +} diff --git a/resources/assets/typescript/Yuuno/BusyMode.ts b/resources/assets/typescript/Yuuno/BusyMode.ts new file mode 100644 index 0000000..bf28d17 --- /dev/null +++ b/resources/assets/typescript/Yuuno/BusyMode.ts @@ -0,0 +1,9 @@ +namespace Yuuno +{ + export enum BusyMode + { + OK, + FAIL, + BUSY + } +} diff --git a/resources/assets/typescript/Yuuno/Editor.ts b/resources/assets/typescript/Yuuno/Editor.ts new file mode 100644 index 0000000..4e71c2e --- /dev/null +++ b/resources/assets/typescript/Yuuno/Editor.ts @@ -0,0 +1,20 @@ +/// + +namespace Yuuno +{ + export class Editor + { + public static InsertBBCode(target: HTMLTextAreaElement, code: string, param: boolean): void + { + var start: string = "[" + code + (param ? "=" : "") + "]", + end: string = "[/" + code + "]", + selectionLength = Sakura.DOM.GetSelectionLength(target); + + Sakura.DOM.EnterAtCursor(target, start); + Sakura.DOM.SetPosition(target, Sakura.DOM.GetPosition(target) + selectionLength + start.length); + Sakura.DOM.EnterAtCursor(target, end); + Sakura.DOM.SetPosition(target, Sakura.DOM.GetPosition(target) - selectionLength); + Sakura.DOM.SetPosition(target, Sakura.DOM.GetPosition(target) + selectionLength, true); + } + } +} diff --git a/resources/assets/typescript/Yuuno/Notifications.ts b/resources/assets/typescript/Yuuno/Notifications.ts index 98f8e00..07027b9 100644 --- a/resources/assets/typescript/Yuuno/Notifications.ts +++ b/resources/assets/typescript/Yuuno/Notifications.ts @@ -1,28 +1,75 @@ -/// +/// namespace Yuuno { - export class Notifications + export class Notifications extends Sakura.Notifications { - private static Container; + private static Container: HTMLElement; - public static RegisterDisplay(): void + public static Init(): void { - this.Container = new Sakura.DOM('notifications', Sakura.DOMSelector.ID); Sakura.Notifications.DisplayMethod = this.Display; + super.Init(); + Notifications.Container = Sakura.DOM.ID('notifications'); } public static Display(alert: Sakura.INotification): void { var id = 'yuuno-alert-' + Date.now(), - container = Sakura.DOM.Create('div', 'notification-enter', id), - icon = Sakura.DOM.Create('div', 'notification-icon'), - inner = Sakura.DOM.Create('div', 'notification-content'), - title = Sakura.DOM.Create('div', 'notification-title'), - text = Sakura.DOM.Create('div', 'notification-text'), - close = Sakura.DOM.Create('div', 'notification-close'), - closeIcon = Sakura.DOM.Create('div'), - clear = Sakura.DOM.Create('div'); + container: HTMLDivElement = Sakura.DOM.Create('div', 'notification-enter', id), + iconContent: HTMLDivElement = Sakura.DOM.Create('div'), + icon: HTMLDivElement = Sakura.DOM.Create('div', 'notification-icon'), + inner: HTMLDivElement = Sakura.DOM.Create('div', 'notification-content'), + title: HTMLDivElement = Sakura.DOM.Create('div', 'notification-title'), + text: HTMLDivElement = Sakura.DOM.Create('div', 'notification-text'), + close: HTMLDivElement = Sakura.DOM.Create('div', 'notification-close'), + closeIcon: HTMLDivElement = Sakura.DOM.Create('div'); + + if (alert.image === null) { + Sakura.DOM.AddClass(iconContent, ['font-icon', 'fa', 'fa-info', 'fa-4x']); + } else if (alert.image.substring(0, 5) == 'FONT:') { + Sakura.DOM.AddClass(iconContent, ['font-icon', 'fa', alert.image.replace('FONT:', ''), 'fa-4x']); + } else { + iconContent.style.background = "url(0) no-repeat center center / cover transparent".replace('0', alert.image); + iconContent.style.width = "100%"; + iconContent.style.height = "100%"; + } + + Sakura.DOM.Append(icon, iconContent); + Sakura.DOM.Append(container, icon); + + title.innerText = alert.title; + text.innerText = alert.text; + + if (alert.link !== null) { + inner.setAttribute('onclick', alert.link.substr(0, 11) == 'javascript:' ? alert.link.substring(11) : 'window.location.assign("' + alert.link + '");'); + } + + Sakura.DOM.Append(inner, title); + Sakura.DOM.Append(inner, text); + Sakura.DOM.Append(container, inner); + + close.setAttribute('onclick', 'Yuuno.Notifications.CloseAlert(this.parentNode.id);'); + + Sakura.DOM.Append(close, closeIcon); + Sakura.DOM.Append(container, close); + + Sakura.DOM.Append(Notifications.Container, container); + + if (alert.timeout > 0) { + setTimeout(() => { + Notifications.CloseAlert(id); + }, alert.timeout); + } + } + + private static CloseAlert(id: string): void + { + var element: HTMLElement = Sakura.DOM.ID(id); + Sakura.DOM.AddClass(element, ['notification-exit']); + setTimeout(() => { + Sakura.DOM.Remove(element); + }, 410); } } } diff --git a/resources/assets/typescript/yuuno/Main.ts b/resources/assets/typescript/yuuno/Main.ts index fadd7b5..fa9629c 100644 --- a/resources/assets/typescript/yuuno/Main.ts +++ b/resources/assets/typescript/yuuno/Main.ts @@ -1,11 +1,19 @@ +/// + namespace Yuuno { export class Main { public static Startup() { - Sakura.Main.Startup(); - Notifications.RegisterDisplay(); + Sakura.TimeAgo.Init(); + Sakura.Friend.Init(); + Notifications.Init(); + Busy.Init(); + + if (window.location.pathname === '/' || window.location.pathname === '/forum' || window.location.pathname === '/forum/') { + Ybabstat.Initiate(); + } } } } diff --git a/resources/assets/typescript/yuuno/ybabstat.ts b/resources/assets/typescript/yuuno/ybabstat.ts index a9c36c7..503d860 100644 --- a/resources/assets/typescript/yuuno/ybabstat.ts +++ b/resources/assets/typescript/yuuno/ybabstat.ts @@ -1,68 +1,82 @@ -var illuminati: Array = new Array(); -var startTime: number = (new Date()).getTime(); +/// -function hideYourMind(conflictions: KeyboardEvent): void { - var twoThousandTwelveIsTheYearWeAscendToSpaceRobots: number = conflictions.keyCode; +namespace Yuuno +{ + export class Ybabstat + { + private static Illuminati: number[] = []; + private static FreeMason: number = Date.now(); - illuminati.push(twoThousandTwelveIsTheYearWeAscendToSpaceRobots); + public static Initiate(): void + { + document.addEventListener('keydown', Ybabstat.HideYourMind); + } - if (illuminati[0] == 68 && illuminati[1] == 73 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 83) { - var dicksAre: HTMLAudioElement = document.createElement('audio'); - var forMyFriends: HTMLSourceElement = document.createElement('source'); - var whenTheyCome: HTMLSourceElement = document.createElement('source'); + private static TwoThousandSixteenIsTheYearWePhysicallyMergeWithCats(): void + { + var diff: number = Date.now() - Ybabstat.FreeMason, + vals: number[] = [ + -7 / Math.cos((diff / 500) * (.85 * Math.PI)), + -7 * Math.tan((diff / 250) * (.85 * Math.PI)) + ]; - forMyFriends.type = 'audio/mp3'; - whenTheyCome.type = 'audio/ogg'; + document.body.style.position = 'absolute'; + document.body.style.left = vals[0] + 'px'; + document.body.style.top = vals[1] + 'px'; + document.body.style.fontSize = vals[0] + 'px'; + } - forMyFriends.src = 'https://data.flashii.net/sounds/dicks.mp3'; - whenTheyCome.src = 'https://data.flashii.net/sounds/dicks.ogg'; + private static HideYourMind(conflictions: KeyboardEvent): void + { + var twoThousandTwelveIsTheYearWeAscendToSpaceRobots: number = conflictions.keyCode; - dicksAre.appendChild(forMyFriends); - dicksAre.appendChild(whenTheyCome); + Ybabstat.Illuminati.push(twoThousandTwelveIsTheYearWeAscendToSpaceRobots); - var toMyHouse: HTMLAudioElement = dicksAre; + if (Ybabstat.Illuminati[0] === 68 && Ybabstat.Illuminati[1] === 73 && Ybabstat.Illuminati[2] === 67 && Ybabstat.Illuminati[3] === 75 && Ybabstat.Illuminati[4] === 83) { + var dicksAreForMy: HTMLAudioElement = Sakura.DOM.Create('audio'), + friendsWhenThey: HTMLSourceElement = Sakura.DOM.Create('source'), + comeToMyHouse: HTMLSourceElement = Sakura.DOM.Create('source'); - toMyHouse.play(); + friendsWhenThey.type = 'audio/mp3'; + comeToMyHouse.type = 'audio/ogg'; - illuminati = new Array(); - } + friendsWhenThey.src = 'https://data.flashii.net/assets/sounds/dicks.mp3'; + comeToMyHouse.src = 'https://data.flashii.net/assets/sounds/dicks.ogg'; - if (illuminati[0] == 77 && illuminati[1] == 69 && illuminati[2] == 87 && illuminati[3] == 79 && illuminati[4] == 87) { - var noklz: HTMLAudioElement = document.createElement('audio'); - var von: HTMLSourceElement = document.createElement('source'); - var schnitzel: HTMLSourceElement = document.createElement('source'); + Sakura.DOM.Append(dicksAreForMy, friendsWhenThey); + Sakura.DOM.Append(dicksAreForMy, comeToMyHouse); - von.type = 'audio/mp3'; - schnitzel.type = 'audio/ogg'; + dicksAreForMy.play(); - von.src = 'https://data.flashii.net/sounds/mewow.mp3'; - schnitzel.src = 'https://data.flashii.net/sounds/mewow.ogg'; + Ybabstat.Illuminati = []; + } - noklz.appendChild(von); - noklz.appendChild(schnitzel); + if (Ybabstat.Illuminati[0] === 77 && Ybabstat.Illuminati[1] === 69 && Ybabstat.Illuminati[2] === 87 && Ybabstat.Illuminati[3] === 79 && Ybabstat.Illuminati[4] === 87) { + var noklz: HTMLAudioElement = Sakura.DOM.Create('audio'), + von: HTMLSourceElement = Sakura.DOM.Create('source'), + schnitzel: HTMLSourceElement = Sakura.DOM.Create('source'); - noklz.play(); + von.type = 'audio/mp3'; + schnitzel.type = 'audio/ogg'; - document.body.style.animation = 'spin 5s infinite linear'; + von.src = 'https://data.flashii.net/assets/sounds/mewow.mp3'; + schnitzel.src = 'https://data.flashii.net/assets/sounds/mewow.ogg'; - illuminati = new Array(); - } + Sakura.DOM.Append(noklz, von); + Sakura.DOM.Append(noklz, schnitzel); - if (illuminati[0] == 83 && illuminati[1] == 79 && illuminati[2] == 67 && illuminati[3] == 75 && illuminati[4] == 67 && illuminati[5] == 72 && illuminati[6] == 65 && illuminati[7] == 84) { - setInterval(twoThousandSixteenIsTheYearWePhysicallyMergeWithCats, 20); + noklz.play(); - illuminati = new Array(); + document.body.style.animation = 'spin 5s infinite linear'; + + Ybabstat.Illuminati = []; + } + + if (Ybabstat.Illuminati[0] == 83 && Ybabstat.Illuminati[1] == 79 && Ybabstat.Illuminati[2] == 67 && Ybabstat.Illuminati[3] == 75 && Ybabstat.Illuminati[4] == 67 && Ybabstat.Illuminati[5] == 72 && Ybabstat.Illuminati[6] == 65 && Ybabstat.Illuminati[7] == 84) { + setInterval(Ybabstat.TwoThousandSixteenIsTheYearWePhysicallyMergeWithCats, 20); + + Ybabstat.Illuminati = []; + } + } } } - -function twoThousandSixteenIsTheYearWePhysicallyMergeWithCats() { - var diff: number = (new Date()).getTime() - startTime; - var vals: Array = [-7 / Math.cos((diff / 500) * (.85 * Math.PI)), -7 * Math.tan((diff / 250) * (.85 * Math.PI))]; - - document.body.style.position = 'absolute'; - document.body.style.left = vals[0] + 'px'; - document.body.style.top = vals[1] + 'px'; - document.body.style.fontSize = vals[0] + 'px'; -} - -document.addEventListener('keydown', hideYourMind, false); diff --git a/resources/assets/typescript/yuuno/yuuno.ts b/resources/assets/typescript/yuuno/yuuno.ts deleted file mode 100644 index 0d35999..0000000 --- a/resources/assets/typescript/yuuno/yuuno.ts +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Sakura Yuuno - */ - - declare var Sakura: any; - declare var AJAX: any; - declare var HTTPMethods: any; - declare var sakuraVars: any; - -// Notification class -interface Notification { - read: boolean; - title: string; - text: string; - link: string; - image: string; - timeout: number; -} - -// 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.image).toLowerCase() === 'undefined' || content.image == null || content.image.length < 2) { - aIconCont = document.createElement('div'); - aIconCont.className = 'font-icon fa fa-info fa-4x'; - } else if (content.image.substr(0, 5) == 'FONT:') { - aIconCont = document.createElement('div'); - aIconCont.className = 'font-icon fa ' + content.image.replace('FONT:', '') + ' fa-4x'; - } else { - aIconCont = document.createElement('img'); - aIconCont.alt = id; - aIconCont.src = content.image; - } - - 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); - - // 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(() => { - (new Sakura.DOM(id, Sakura.DOMSelector.ID)).Remove(); - }, 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); - } -} - -// 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 { - (new Sakura.DOM('ajaxBusy', Sakura.DOMSelector.ID)).Remove(); - clearInterval(out); - } - }, 10); - } - } -} - -// Making a post request using AJAX -function ajaxPost(url: string, data: Object, callback: Function) { - // Create AJAX - var request = new Sakura.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(Sakura.HTTPMethod.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')); - } - - 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.Legacy.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.Legacy.validateEmail(input.value); - break; - - case 'username': - default: - check = Sakura.Legacy.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); -} - -// 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: any = document.getElementById(areaId); - var scrollPos: any = 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: any = 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(); - } -} - -interface Number { - formatMoney(u, c, k); -} - -// 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: any = 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) : ""); -}; diff --git a/resources/views/aitemu/master.twig b/resources/views/aitemu/master.twig index f539aac..fec031a 100644 --- a/resources/views/aitemu/master.twig +++ b/resources/views/aitemu/master.twig @@ -18,7 +18,7 @@ {##} - + {##} {{ block('js') }} @@ -29,7 +29,7 @@