diff --git a/app/config/minify_js_groups.php b/app/config/minify_js_groups.php index 72ddfd75..5d5528c9 100644 --- a/app/config/minify_js_groups.php +++ b/app/config/minify_js_groups.php @@ -17,19 +17,11 @@ * This is the config array for javascript files to concatenate and minify */ return [ - /* - For each group create an array like so - - 'my_group' => array( - 'path/to/file1.js', - 'path/to/file2.js' - ), - */ 'base' => [ - //'base/AnimeClient.js', - 'base/base.js', - 'base/event.js', - 'base/ajax.js', + 'base/AnimeClient.js', + //'base/base.js', + //'base/event.js', + //'base/ajax.js', ], 'event' => [ 'base/events.js', diff --git a/public/js/anime_edit.js b/public/js/anime_edit.js index fc67cbe6..c35e930e 100755 --- a/public/js/anime_edit.js +++ b/public/js/anime_edit.js @@ -8,7 +8,7 @@ // Action to increment episode count _.on('body.anime.list', 'click', '.plus_one', function(e) { let this_sel = this; - let parent_sel = this.parentElement; + let parent_sel = _.closestParent(this, 'article'); let watched_count = parseInt(_.$('.completed_number', parent_sel)[0].textContent, 10); let total_count = parseInt(_.$('.total_number', parent_sel)[0].textContent, 10); diff --git a/public/js/base/AnimeClient.js b/public/js/base/AnimeClient.js new file mode 100644 index 00000000..9c23f4e9 --- /dev/null +++ b/public/js/base/AnimeClient.js @@ -0,0 +1,274 @@ +var AnimeClient = (function(w) { + + 'use strict'; + + const slice = Array.prototype.slice; + + // ------------------------------------------------------------------------- + // ! Base + // ------------------------------------------------------------------------- + + function matches(elm, selector) { + let matches = (elm.document || elm.ownerDocument).querySelectorAll(selector), + i = matches.length; + while (--i >= 0 && matches.item(i) !== elm); + return i > -1; + } + + const _ = { + /** + * Placeholder function + */ + noop: () => {}, + /** + * DOM selector + * + * @param {string} selector - The dom selector string + * @param {object} context + * @return {array} - array of dom elements + */ + $(selector, context) { + if (typeof selector != "string" || selector === undefined) { + return selector; + } + + context = (context != null && context.nodeType === 1) + ? context + : document; + + let elements = []; + if (selector.match(/^#([\w]+$)/)) { + elements.push(document.getElementById(selector.split('#')[1])); + } else { + elements = slice.apply(context.querySelectorAll(selector)); + } + + return elements; + }, + /** + * Scroll to the top of the Page + * + * @return {void} + */ + scrollToTop() { + w.scroll(0,0); + }, + /** + * Display a message box + * + * @param {String} type - message type: info, error, success + * @param {String} message - the message itself + * @return {void} + */ + showMessage(type, message) { + let template = ` +
`; + + let sel = AnimeClient.$('.message')[0]; + if (sel !== undefined) { + sel.innerHTML = template; + sel.removeAttribute('hidden'); + } else { + _.$('header')[0].insertAdjacentHTML('beforeend', template); + } + }, + /** + * Finds the closest parent element matching the passed selector + * + * @param {DOMElement} current - the current DOMElement + * @param {string} parentSelector - selector for the parent element + * @return {DOMElement|null} - the parent element + */ + closestParent(current, parentSelector) { + if (Element.prototype.closest !== undefined) { + return current.closest(parentSelector); + } + + while (current !== document.documentElement) { + if (matches(current, parentSelector)) { + return current; + } + + current = current.parentElement; + } + + return null; + }, + /** + * Generate a full url from a relative path + * + * @param {String} path - url path + * @return {String} - full url + */ + url(path) { + let uri = `//${document.location.host}`; + uri += (path.charAt(0) === '/') ? path : `/${path}`; + + return uri; + }, + /** + * Throttle execution of a function + * + * @see https://remysharp.com/2010/07/21/throttling-function-calls + * @see https://jsfiddle.net/jonathansampson/m7G64/ + * @param {Number} interval - the minimum throttle time in ms + * @param {Function} fn - the function to throttle + * @param {Object} scope - the 'this' object for the function + * @return {void} + */ + throttle(interval, fn, scope) { + var wait = false; + return function () { + var context = scope || this; + var args = arguments; + + if ( ! wait) { + fn.apply(context, args); + wait = true; + setTimeout(function() { + wait = false; + }, interval); + } + }; + }, + }; + + // ------------------------------------------------------------------------- + // ! Events + // ------------------------------------------------------------------------- + + function addEvent(sel, event, listener) { + // Recurse! + if (! event.match(/^([\w\-]+)$/)) { + event.split(' ').forEach((evt) => { + addEvent(sel, evt, listener); + }); + } + + sel.addEventListener(event, listener, false); + } + + function delegateEvent(sel, target, event, listener) { + // Attach the listener to the parent + addEvent(sel, event, (e) => { + // Get live version of the target selector + _.$(target, sel).forEach((element) => { + if(e.target == element) { + listener.call(element, e); + e.stopPropagation(); + } + }); + }); + } + + _.on = function (sel, event, target, listener) { + if (arguments.length === 3) { + listener = target; + _.$(sel).forEach((el) => { + addEvent(el, event, listener); + }); + } else { + _.$(sel).forEach((el) => { + delegateEvent(el, target, event, listener); + }); + } + } + + // ------------------------------------------------------------------------- + // ! Ajax + // ------------------------------------------------------------------------- + + /** + * Url encoding for non-get requests + * + * @param data + * @returns {string} + * @private + */ + function ajaxSerialize(data) { + let pairs = []; + + Object.keys(data).forEach((name) => { + let value = data[name].toString(); + + name = encodeURIComponent(name); + value = encodeURIComponent(value); + + pairs.push(`${name}=${value}`); + }); + + return pairs.join("&"); + }; + + _.ajax = function(url, config) { + // Set some sane defaults + config = config || {}; + config.data = config.data || {}; + config.type = config.type || 'GET'; + config.dataType = config.dataType || ''; + config.success = config.success || _.noop; + config.error = config.error || _.noop; + + let request = new XMLHttpRequest(); + let method = String(config.type).toUpperCase(); + + if (method === "GET") { + url += (url.match(/\?/)) + ? ajaxSerialize(config.data) + : `?${ajaxSerialize(config.data)}`; + } + + request.open(method, url); + + request.onreadystatechange = () => { + if (request.readyState === 4) { + let responseText = ''; + + if (request.responseType == 'json') { + responseText = JSON.parse(request.responseText); + } else { + responseText = request.responseText; + } + + if (request.status > 400) { + config.error.call(null, request.statusText, request.response); + } else { + config.success.call(null, responseText, request.status); + } + } + }; + + switch (method) { + case "GET": + request.send(null); + break; + + default: + request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + request.send(ajaxSerialize(config.data)); + break; + } + }; + + _.get = function(url, data, callback) { + if (arguments.length === 2) { + callback = data; + data = {}; + } + + return _.ajax(url, { + data: data, + success: callback + }); + }; + + // ------------------------------------------------------------------------- + // Export + // ------------------------------------------------------------------------- + + return _; +})(window); \ No newline at end of file diff --git a/public/js/base/ajax.js b/public/js/base/ajax.js deleted file mode 100644 index e91bcbfa..00000000 --- a/public/js/base/ajax.js +++ /dev/null @@ -1,89 +0,0 @@ -AnimeClient = (function (ac) { - - /** - * Url encoding for non-get requests - * - * @param data - * @returns {string} - * @private - */ - function serialize(data) { - let pairs = []; - - Object.keys(data).forEach((name) => { - let value = data[name].toString(); - - name = encodeURIComponent(name); - value = encodeURIComponent(value); - - pairs.push(`${name}=${value}`); - }); - - return pairs.join("&"); - }; - - ac.ajax = function(url, config) { - // Set some sane defaults - config = config || {}; - config.data = config.data || {}; - config.type = config.type || 'GET'; - config.dataType = config.dataType || ''; - config.success = config.success || ac.noop; - config.error = config.error || ac.noop; - - let request = new XMLHttpRequest(); - let method = String(config.type).toUpperCase(); - - if (method === "GET") { - url += (url.match(/\?/)) - ? serialize(config.data) - : `?${serialize(config.data)}`; - } - - request.open(method, url); - - request.onreadystatechange = () => { - if (request.readyState === 4) { - let responseText = ''; - - if (request.responseType == 'json') { - responseText = JSON.parse(request.responseText); - } else { - responseText = request.responseText; - } - - if (request.status > 400) { - config.error.call(null, request.statusText, request.response); - } else { - config.success.call(null, responseText, request.status); - } - } - }; - - switch (method) { - case "GET": - request.send(null); - break; - - default: - request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - request.send(serialize(config.data)); - break; - } - }; - - ac.get = function(url, data, callback) { - if (arguments.length === 2) { - callback = data; - data = {}; - } - - return ac.ajax(url, { - data: data, - success: callback - }); - }; - - return ac; - -})(AnimeClient); \ No newline at end of file diff --git a/public/js/base/base.js b/public/js/base/base.js deleted file mode 100644 index 4c46b479..00000000 --- a/public/js/base/base.js +++ /dev/null @@ -1,107 +0,0 @@ -var AnimeClient = (function(w) { - - 'use strict'; - - const slice = Array.prototype.slice; - - return { - /** - * Placeholder function - */ - noop: () => {}, - /** - * DOM selector - * - * @param {string} selector - The dom selector string - * @param {object} context - * @return {array} - arrau of dom elements - */ - $(selector, context) { - if (typeof selector != "string" || selector === undefined) { - return selector; - } - - context = (context != null && context.nodeType === 1) - ? context - : document; - - let elements = []; - if (selector.match(/^#([\w]+$)/)) { - elements.push(document.getElementById(selector.split('#')[1])); - } else { - elements = slice.apply(context.querySelectorAll(selector)); - } - - return elements; - }, - /** - * Scroll to the top of the Page - * - * @return {void} - */ - scrollToTop() { - w.scroll(0,0); - }, - /** - * Display a message box - * - * @param {String} type - message type: info, error, success - * @param {String} message - the message itself - * @return {void} - */ - showMessage(type, message) { - - let template = ` - `; - - let sel = AnimeClient.$('.message')[0]; - if (sel !== undefined) { - sel.innerHTML = template; - sel.removeAttribute('hidden'); - } else { - AnimeClient.$('header')[0].insertAdjacentHTML('beforeend', template); - } - }, - /** - * Generate a full url from a relative path - * - * @param {String} path - url path - * @return {String} - full url - */ - url(path) { - let uri = `//${document.location.host}`; - uri += (path.charAt(0) === '/') ? path : `/${path}`; - - return uri; - }, - /** - * Throttle execution of a function - * - * @see https://remysharp.com/2010/07/21/throttling-function-calls - * @see https://jsfiddle.net/jonathansampson/m7G64/ - * @param {Number} interval - the minimum throttle time in ms - * @param {Function} fn - the function to throttle - * @param {Object} scope - the 'this' object for the function - * @return {void} - */ - throttle(interval, fn, scope) { - var wait = false; - return function () { - var context = scope || this; - var args = arguments; - - if ( ! wait) { - fn.apply(context, args); - wait = true; - setTimeout(function() { - wait = false; - }, interval); - } - }; - }, - }; -})(window); \ No newline at end of file diff --git a/public/js/base/event.js b/public/js/base/event.js deleted file mode 100644 index 4307f20d..00000000 --- a/public/js/base/event.js +++ /dev/null @@ -1,41 +0,0 @@ -AnimeClient = (function (ac) { - 'use strict'; - - function add(sel, event, listener) { - // Recurse! - if (! event.match(/^([\w\-]+)$/)) { - event.split(' ').forEach((evt) => { - add(sel, evt, listener); - }); - } - - sel.addEventListener(event, listener, false); - } - function delegate(sel, target, event, listener) { - // Attach the listener to the parent - add(sel, event, (e) => { - // Get live version of the target selector - ac.$(target, sel).forEach((element) => { - if(e.target == element) { - listener.call(element, e); - e.stopPropagation(); - } - }); - }); - } - - ac.on = function (sel, event, target, listener) { - if (arguments.length === 3) { - listener = target; - ac.$(sel).forEach((el) => { - add(el, event, listener); - }); - } else { - ac.$(sel).forEach((el) => { - delegate(el, target, event, listener); - }); - } - } - - return ac; -})(AnimeClient); \ No newline at end of file diff --git a/public/js/manga_edit.js b/public/js/manga_edit.js index 80393b37..d59a9587 100755 --- a/public/js/manga_edit.js +++ b/public/js/manga_edit.js @@ -7,7 +7,7 @@ _.on('.manga.list', 'click', '.edit_buttons button', function(e) { let this_sel = this; - let parent_sel = this.parentElement.parentElement; + let parent_sel = _.closestParent(this, 'article'); let manga_id = parent_sel.id.replace("manga-", ""); let type = this_sel.classList.contains("plus_one_chapter") ? 'chapter' : 'volume'; let completed = parseInt(_.$(`.${type}s_read`, parent_sel)[0].textContent, 10); diff --git a/public/test/index.html b/public/test/index.html index a4281818..9203f8b3 100644 --- a/public/test/index.html +++ b/public/test/index.html @@ -13,9 +13,7 @@ - - - +