diff --git a/classes/Templating/Template.class.php b/classes/Templating/Template.class.php index f43e4d0..9da270f 100644 --- a/classes/Templating/Template.class.php +++ b/classes/Templating/Template.class.php @@ -51,11 +51,53 @@ public function __construct(Core $core) { $this->core->getHooks()->addAction('html_head', [ $this, 'favicon' ], 10, false); $this->core->getHooks()->addAction('html_foot', [ $this, 'foot_modals' ], 10, false); $this->core->getHooks()->addAction('html_foot', [ $this, 'foot_scripts' ], 10, false); - - $this->files->addStylesheet('bootstrap', $this->url('css/bootstrap/bootstrap.min.css'), '5.3.2'); - $this->files->addStylesheet('bootstrap-icons', $this->url('fonts/bootstrap-icons/bootstrap-icons.css'), '1.11.1', [ 'bootstrap' ]); - $this->files->addStylesheet('cascadia-mono', $this->url('fonts/cascadia-mono/cascadia-mono.css'), '2111.01', [ 'bootstrap' ]); - $this->files->addJavascript('bootstrap', $this->url('js/bootstrap/bootstrap.bundle.min.js'), '5.3.2', [], TemplateFiles::FOOTER); + + $this->getFiles()->addStylesheet('global', $this->url('css/global.css'), '1.0.0', [ 'bootstrap' ]); + $this->getFiles()->addStylesheet('bootstrap', $this->url('css/bootstrap/bootstrap.min.css'), '5.3.2'); + $this->getFiles()->addStylesheet('bootstrap-icons', $this->url('fonts/bootstrap-icons/bootstrap-icons.css'), '1.11.1', [ 'bootstrap' ]); + $this->getFiles()->addStylesheet('cascadia-mono', $this->url('fonts/cascadia-mono/cascadia-mono.css'), '2111.01', [ 'bootstrap' ]); + $this->getFiles()->addJavascript('ajax', $this->url('js/ajax.js'), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); + $this->getFiles()->addJavascript('bootstrap', $this->url('js/bootstrap/bootstrap.bundle.min.js'), '5.3.2', [], TemplateFiles::FOOTER); + + if(defined('DEBUG' && DEBUG)) { + $this->getFiles()->addStylesheet('debug', $this->url('css/components/debug.css'), '1.0.0', [ 'bootstrap' ]); + } + + foreach([ + 'themes', + 'tooltip', + 'popover', + 'ajax', + 'modal' + ] AS $component) { + if(file_exists(sprintf('default/css/components/%s.css', $component))) { + $this->getFiles()->addStylesheet(sprintf('component-%s', $component), $this->url(sprintf('css/components/%s.css', $component)), '1.0.0', [ 'bootstrap' ]); + } + + if(file_exists(sprintf('default/js/components/%s.js', $component))) { + $this->getFiles()->addJavascript(sprintf('component-%s', $component), $this->url(sprintf('js/components/%s.js', $component)), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); + } + } + + if(Auth::isLoggedIn()) { + $this->getFiles()->addJavascript('codemirror', $this->url('js/codemirror/build/bundle.min.js'), '6.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); + + foreach([ + 'confirmation', + 'loading', + 'module-info', + 'console', + 'file-tree' + ] AS $component) { + if(file_exists(sprintf('default/css/components/%s.css', $component))) { + $this->getFiles()->addStylesheet(sprintf('component-%s', $component), $this->url(sprintf('css/components/%s.css', $component)), '1.0.0', [ 'bootstrap' ]); + } + + if(file_exists(sprintf('default/js/components/%s.js', $component))) { + $this->getFiles()->addJavascript(sprintf('component-%s', $component), $this->url(sprintf('js/components/%s.js', $component)), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); + } + } + } $this->navigation->addCategory('account', I18N::get('Account')); $this->navigation->addCategory('database', I18N::get('Databases')); diff --git a/classes/UI/Modal.class.php b/classes/UI/Modal.class.php index 1826e4b..eead6a7 100644 --- a/classes/UI/Modal.class.php +++ b/classes/UI/Modal.class.php @@ -72,7 +72,7 @@ public function getContent(Template $template) : mixed { // @ToDo check if is template file if(!empty($this->content)) { - $template->display($this->content, $this->variables, false, false); + $template->display($this->content, $this->variables, true, false); return null; } diff --git a/default/css/components/console.css b/default/css/components/console.css new file mode 100644 index 0000000..1a6bd4b --- /dev/null +++ b/default/css/components/console.css @@ -0,0 +1,42 @@ +/* + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT +*/ + +div.terminal { + position: relative; + margin: 0 -1.5rem !important; + width: calc(100% + 3rem); + background: #282c34; + color: #dcdfe4; + box-shadow: inset 0px 2px 10px rgba(0, 0, 0, .25); +} + +div.terminal div.output { + height: calc(100% - 35px); + overflow: auto; + padding: 15px; + line-height: 14px; + font-size: 12px; + font-family: 'Cascadia Mono'; +} + +div.terminal div.output span[data-color="0;32"] { + color: #9bc67c; +} + +div.terminal div.output span[data-color="38;5;202"] { + color: #e5c07b; +} + +div.terminal input[name="command"] { + height: 35px; + width: 100%; + background: rgba(255, 255, 255, 0.2); + border: 0; + padding: 10px; + color: #FFFFFF; +} \ No newline at end of file diff --git a/default/css/components/debug.css b/default/css/components/debug.css new file mode 100644 index 0000000..f0786af --- /dev/null +++ b/default/css/components/debug.css @@ -0,0 +1,19 @@ +/* + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT +*/ + +.xdebug-error, .xdebug-var-dump { + z-index: 9999999999; + position: relative; + overflow: visible !important; + background: #FEFEFE; + color: #000000; +} + +[data-bs-theme="dark"] .xdebug-error, [data-bs-theme="dark"] .xdebug-var-dump { + background: #444444; +} diff --git a/default/css/components/file-tree.css b/default/css/components/file-tree.css new file mode 100644 index 0000000..a35b762 --- /dev/null +++ b/default/css/components/file-tree.css @@ -0,0 +1,47 @@ +/* + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT +*/ +.tree { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + padding: 0; + overflow: hidden; +} + +.tree .list-group { + margin-bottom: 0; +} + +.tree .list-group-item { + border-radius: 0; + border-width: 1px 0 0 0; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + cursor: pointer; +} + +.tree [data-type="directory"]::before { + content: "\F3D9"; + position: relative; + display: inline-block; + font-family: 'bootstrap-icons'; + font-size: 20px; + color: #f3c36b; +} + +.tree .list-group-item:hover { + background-color: #dee2e6; +} + +.tree > .list-group-item:first-child { + border-top-width: 0; +} \ No newline at end of file diff --git a/default/css/components/module-info.css b/default/css/components/module-info.css new file mode 100644 index 0000000..72de00f --- /dev/null +++ b/default/css/components/module-info.css @@ -0,0 +1,48 @@ +/* + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT +*/ +div#module_info .module-icon { + font-size: 100px; +} + +div#module_info .carousel-caption { + right: 0; + bottom: 0; + left: 0; + color: #fff !important; + background: rgba(0, 0, 0, 0.5); +} + +div#module_info div.markdown { + background: var(--bs-modal-footer-border-color); + padding: 20px; + font-size: 16px; +} + +div#module_info div.markdown h1 { + font-size: 1.4rem; +} + +div#module_info div.markdown h2 { + font-size: 1.3rem; +} + +div#module_info div.markdown h3 { + font-size: 1.2rem; +} + +div#module_info div.markdown h4 { + font-size: 1.1rem; +} + +div#module_info div.markdown h5 { + font-size: 1.0rem; +} + +div#module_info div.markdown h6 { + font-size: 0.9rem; +} \ No newline at end of file diff --git a/default/css/components/themes.css b/default/css/components/themes.css new file mode 100644 index 0000000..8080939 --- /dev/null +++ b/default/css/components/themes.css @@ -0,0 +1,32 @@ +/* + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT +*/ + +.theme-switch { + z-index: 99999; + border-radius: 35%; + color: var(--bs-emphasis-color); + background-color: var(--bs-secondary-bg-subtle); +} + +.theme-switch .bi { + vertical-align: -.125em; + fill: currentColor; +} + +.theme-switch .btn-bd-primary:active, +.theme-switch .btn-bd-primary:focus { + --bs-btn-active-border-color: transparent; +} + +.theme-switch .bd-mode-toggle { + z-index: 1500; +} + +.theme-switch .bd-mode-toggle .dropdown-menu .active .bi { + display: block !important; +} \ No newline at end of file diff --git a/default/css/global.css b/default/css/global.css index 1f14599..03b2a57 100644 --- a/default/css/global.css +++ b/default/css/global.css @@ -17,14 +17,6 @@ input:focus { box-shadow: none !important; } -/* Theme Switch */ -.theme-switch { - z-index: 99999; - border-radius: 35%; - color: var(--bs-emphasis-color); - background-color: var(--bs-secondary-bg-subtle); -} - /* Dropdown */ .dropdown-scroll { max-height: 145px; @@ -77,9 +69,4 @@ span[data-dots="true"]::after { content: ""; width: 5px; animation: dots 3s linear infinite; -} - -/* XDebug */ -.xdebug-error { - color: #000000; } \ No newline at end of file diff --git a/default/css/login.css b/default/css/login.css index 6b8dd47..df257f5 100644 --- a/default/css/login.css +++ b/default/css/login.css @@ -27,23 +27,4 @@ margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; -} - -/* Theme Switch */ -.bi { - vertical-align: -.125em; - fill: currentColor; -} - -.btn-bd-primary:active, -.btn-bd-primary:focus { - --bs-btn-active-border-color: transparent; -} - -.bd-mode-toggle { - z-index: 1500; -} - -.bd-mode-toggle .dropdown-menu .active .bi { - display: block !important; } \ No newline at end of file diff --git a/default/css/style.css b/default/css/style.css index 29f9d0c..f5804dd 100644 --- a/default/css/style.css +++ b/default/css/style.css @@ -6,18 +6,6 @@ * @license MIT */ -/* DEBUG */ -.xdebug-error, .xdebug-var-dump { - z-index: 9999999999; - position: relative; - overflow: visible !important; - background: #FEFEFE; -} - -[data-bs-theme="dark"] .xdebug-error, [data-bs-theme="dark"] .xdebug-var-dump { - background: #444444; -} - /* Animations & Effects */ @keyframes blink { 50% { @@ -26,10 +14,6 @@ } /* Globals */ -:root { - -} - * { outline: none !important; } @@ -39,10 +23,6 @@ body { max-height: 100vh; } -main { - /* height: calc(100vh - 50px);*/ -} - .h-100-head { height: calc(100% - 50px); } @@ -131,11 +111,6 @@ main { cursor: pointer; } -/* -.sidebar .wrapper { - padding-bottom: 65px !important; -}*/ - /* Topbar */ .navbar-brand { padding-top: .75rem; @@ -248,98 +223,6 @@ main iframe { overflow-y: auto; } -/* -body.sortable { - overflow: hidden; - - -.offcanvas-body { - height: 100vh; - - - -header.page-header h1 { - font-size: 22px; - display: flex; -} - -header.page-header a { - color: #444444; -} - -header.page-header a.active { - color: var(--primary); -} - -header.page-header i.bi { - color: #DDDDDD; -} - - -.form-control-dark { - color: #fff; - background-color: rgba(255, 255, 255, .1); - border-color: rgba(255, 255, 255, .1); -} - -.form-control-dark:focus { - border-color: transparent; - box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); -} - -.alert.welcome li .bi { - font-size: 20px; - vertical-align: text-bottom; -} - -.sortable-placeholder .card { - border: 4px dotted var(--gray); - text-align: center; - font-weight: bold; - color: var(--gray); - padding: 20px; -} - -ul.sortable li .card-header .actions .bi { - font-size: 20px; - height: 26px; - line-height: 22px; - width: 26px; - background: var(--white); - margin: 0 5px 0 0; - padding: 2px; - color: var(--secondary); - border: 1px solid var(--secondary); -} - -ul.sortable li .card-body { - padding: 0; -} - -ul.sortable li ul.list-unstyled { - overflow-x: auto; - padding: 5px; -} - -ul.sortable li ul.list-unstyled li a { - color: #333; -} - -ul.sortable li ul.list-unstyled li a:hover { - color: var(--primary); -} - -ul.sortable li ul.list-unstyled li a div.icon { - font-size: 22px; -} - -ul.sortable li ul.list-unstyled li a div.label { - font-size: 12px; -} - - -*/ - /* Module Icons */ img.module-icon { max-width: 20px; @@ -538,42 +421,7 @@ tr:hover .module-badge { opacity: 1; } -/* Bash Colors */ -div.terminal { - position: relative; - margin: 0 -1.5rem !important; - width: calc(100% + 3rem); - background: #282c34; - color: #dcdfe4; - box-shadow: inset 0px 2px 10px rgba(0, 0, 0, .25); -} -div.terminal div.output { - height: calc(100% - 35px); - overflow: auto; - padding: 15px; - line-height: 14px; - font-size: 12px; - font-family: 'Cascadia Mono'; -} - -div.terminal div.output span[data-color="0;32"] { - color: #9bc67c; -} - - -div.terminal div.output span[data-color="38;5;202"] { - color: #e5c07b; -} - -div.terminal input[name="command"] { - height: 35px; - width: 100%; - background: rgba(255, 255, 255, 0.2); - border: 0; - padding: 10px; - color: #FFFFFF; -} /* Logfile Viewer */ .contentbar { @@ -747,89 +595,4 @@ li.empty::before { flex: 1 1 auto; width: 1%; min-width: 0; -} - -/* Tree */ -.tree { - position: relative; - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - min-width: 0; - word-wrap: break-word; - padding: 0; - overflow: hidden; -} - -.tree .list-group { - margin-bottom: 0; -} - -.tree .list-group-item { - border-radius: 0; - border-width: 1px 0 0 0; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - cursor: pointer; -} - -.tree [data-type="directory"]::before { - content: "\F3D9"; - position: relative; - display: inline-block; - font-family: 'bootstrap-icons'; - font-size: 20px; - color: #f3c36b; -} - -.tree .list-group-item:hover { - background-color: #dee2e6; -} - -.tree > .list-group-item:first-child { - border-top-width: 0; -} - -/* Module Info */ -div#module_info .module-icon { - font-size: 100px; -} - -div#module_info .carousel-caption { - right: 0; - bottom: 0; - left: 0; - color: #fff !important; - background: rgba(0, 0, 0, 0.5); -} - -div#module_info div.markdown { - background: var(--bs-modal-footer-border-color); - padding: 20px; - font-size: 16px; -} - -div#module_info div.markdown h1 { - font-size: 1.4rem; -} - -div#module_info div.markdown h2 { - font-size: 1.3rem; -} - -div#module_info div.markdown h3 { - font-size: 1.2rem; -} - -div#module_info div.markdown h4 { - font-size: 1.1rem; -} - -div#module_info div.markdown h5 { - font-size: 1.0rem; -} - -div#module_info div.markdown h6 { - font-size: 0.9rem; } \ No newline at end of file diff --git a/default/footer.php b/default/footer.php index 7557e98..1bba463 100644 --- a/default/footer.php +++ b/default/footer.php @@ -8,85 +8,17 @@ */ use fruithost\Accounting\Auth; - use fruithost\Localization\I18N; - use fruithost\UI\Icon; - if(Auth::isLoggedIn()) { - ?> - - - - - - - - - - + + + + + + foot(); ?> diff --git a/default/functions.php b/default/functions.php index 05fd49f..ab9cb92 100644 --- a/default/functions.php +++ b/default/functions.php @@ -2,16 +2,10 @@ use fruithost\Accounting\Auth; use fruithost\Templating\TemplateFiles; - $this->getFiles()->addStylesheet('global', $this->url('css/global.css'), '1.0.0', [ 'bootstrap' ]); - $this->getFiles()->addJavascript('global', $this->url('js/global.js'), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); - $this->getFiles()->addJavascript('ajax', $this->url('js/ajax.js'), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); - if(!Auth::isLoggedIn()) { $this->getFiles()->addStylesheet('login', $this->url('css/login.css'), '2.0.0', [ 'bootstrap' ]); - $this->getFiles()->addJavascript('login', $this->url('js/login.js'), '1.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); } else { $this->getFiles()->addStylesheet('style', $this->url('css/style.css'), '2.0.0', [ 'bootstrap' ]); - $this->getFiles()->addJavascript('ui', $this->url('js/ui.js'), '2.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); - $this->getFiles()->addJavascript('codemirror', $this->url('js/codemirror/build/bundle.min.js'), '6.0.0', [ 'ui' ], TemplateFiles::FOOTER); + $this->getFiles()->addJavascript('navigation', $this->url('js/navigation.js'), '2.0.0', [ 'bootstrap' ], TemplateFiles::FOOTER); } ?> \ No newline at end of file diff --git a/default/js/components/ajax.js b/default/js/components/ajax.js new file mode 100644 index 0000000..14fcaf5 --- /dev/null +++ b/default/js/components/ajax.js @@ -0,0 +1,83 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ +'use strict'; + +class AjaxComponent { + constructor() { + [].map.call(document.querySelectorAll('.ajax'), (form) => { + /* + form.addEventListener('click', (event) => { + this.submit.apply(form, [ event ]); + }); + */ + + form.addEventListener('submit', (event) => { + this.submit.apply(form, [ event ]); + }); + }); + } + serialize(form) { + let result = {}; + let data = new FormData(form); + + for(let key of data.keys()) { + result[key] = data.get(key); + } + + return result; + } + + submit(event) { + event.preventDefault(); + + try { + let form = this; + // @ToDo add onError? + + new Ajax(form.action).onSuccess(function (response) { + if(response.toLowerCase() === 'true' || response.toLowerCase() === '1') { + window.location.reload(); + return; + + } else if(response.toLowerCase() == 'close') { + let node = form.parentNode.closest('.modal'); + node.classList.remove('fade'); + let modal = bootstrap.Modal.getInstance(node); + modal.hide(); + event.stopPropagation(); + return; + + } else if(response.toLowerCase() === 'false') { + response = 'An unknown error has occurred.'; // @ToDo Language + } + + let content = form.querySelector('.modal-body'); + let alert = content.querySelector('.alert'); + + if (alert) { + content.removeChild(alert); + } + + alert = document.createElement('div'); + alert.classList.add('alert'); + alert.classList.add('alert-danger'); + alert.setAttribute('role', 'alert'); + alert.innerHTML = response; + content.prepend(alert); + }).post(serialize(form)); + } catch (e) { + /* Do Nothing */ + } + + return false; + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.AjaxComponent = new AjaxComponent(); +}); \ No newline at end of file diff --git a/default/js/components/confirmation.js b/default/js/components/confirmation.js new file mode 100644 index 0000000..fe2d9fd --- /dev/null +++ b/default/js/components/confirmation.js @@ -0,0 +1,72 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ + +(() => { + window.Confirm = function Confirm(element) { + element.addEventListener('mousedown', function (event) { + let target = event.target; + let el = document.querySelector('#confirmation'); + const popup = new bootstrap.Modal(el); + let prevent= true; + let header = popup._element.querySelector('.modal-header'); + let a = document.createElement('p'); + + a.classList.add('m-0'); + a.classList.add('text-center'); + a.classList.add('p-3'); + a.innerHTML = target.dataset.confirm; + + let b = document.createElement('p'); + b.classList.add('modal-body'); + b.classList.add('d-none'); + b.innerHTML = target.dataset.confirm; + + [].map.call(header.parentNode.querySelectorAll('p'), function (e) { + e.parentNode.removeChild(e); + }); + + popup._element.querySelector('.modal-footer').classList.add('text-center'); + header.parentNode.insertBefore(a, header.nextSibling); + header.parentNode.insertBefore(b, header.nextSibling); + + popup.show(); + + let _watcher = setInterval(function () { + var res = el.querySelector('.alert'); + + if(res == null || typeof (res) === 'undefined') { + return; + } + + var state = res.innerText; + + if(state === 'CONFIRMED') { + clearInterval(_watcher); + + if(target.tagName === 'A') { + window.location.href = target.href; + } else { + target.click(); + } + } + + }, 500); + + el.addEventListener('hide.bs.modal', function (event) { + clearInterval(_watcher); + }); + + if(prevent) { + event.preventDefault(); + return false; + } + }); + }; + + [].map.call(document.querySelectorAll('[data-confirm]'), Confirm); +})(); \ No newline at end of file diff --git a/default/js/console.js b/default/js/components/console.js similarity index 96% rename from default/js/console.js rename to default/js/components/console.js index 8d61226..b179712 100644 --- a/default/js/console.js +++ b/default/js/components/console.js @@ -1,87 +1,87 @@ -/** - * fruithost | OpenSource Hosting - * - * @author Adrian Preuß - * @version 1.0.0 - * @license MIT - */ - -(() => { - 'use strict' - window.addEventListener('DOMContentLoaded', () => { - document.body.style.overflow = 'hidden'; - let destination = document.querySelector('input[name="destination"]'); - let output = document.querySelector('div.output'); - let command = document.querySelector('input[name="command"]'); - let parser = new Terminal(); - - function send(cmd) { - new Ajax(destination.value).onError(function (event, error, response) { - console.warn(event, error, response); - }).onSuccess(function (response) { - if (response === '\u001B[H\u001B[2J\u001B[3J' || response === 'clear') { - output.innerHTML = ''; - return; - } - - output.innerHTML += parser.parse(response); - command.focus(); - output.scrollTo(0, output.scrollHeight); - }).post({ - action: 'command', - command: cmd - }); - } - - let history = []; - let position = 0; - - command.addEventListener('keydown', (event) => { - switch (event.keyCode) { - /* Enter */ - case 13: - let text = command.value; - history.push(text); - position = history.length; - send(text); - command.value = ''; - break; - - /* Up */ - case 38: - if (position > 0) { - --position; - - if (position < 0) { - position = history.length - 1; - } - - command.blur(); - command.value = history[position]; - command.focus(); - } - break; - - /* Down */ - case 40: - if (position >= history.length) { - break; - } - - position++; - - if (position === history.length) { - command.value = ''; - } else { - command.blur(); - command.focus(); - command.value = history[position]; - } - break; - } - }); - - send('motd'); - command.focus(); - }); +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ + +(() => { + 'use strict' + window.addEventListener('DOMContentLoaded', () => { + document.body.style.overflow = 'hidden'; + let destination = document.querySelector('input[name="destination"]'); + let output = document.querySelector('div.output'); + let command = document.querySelector('input[name="command"]'); + let parser = new Terminal(); + + function send(cmd) { + new Ajax(destination.value).onError(function (event, error, response) { + console.warn(event, error, response); + }).onSuccess(function (response) { + if (response === '\u001B[H\u001B[2J\u001B[3J' || response === 'clear') { + output.innerHTML = ''; + return; + } + + output.innerHTML += parser.parse(response); + command.focus(); + output.scrollTo(0, output.scrollHeight); + }).post({ + action: 'command', + command: cmd + }); + } + + let history = []; + let position = 0; + + command.addEventListener('keydown', (event) => { + switch (event.keyCode) { + /* Enter */ + case 13: + let text = command.value; + history.push(text); + position = history.length; + send(text); + command.value = ''; + break; + + /* Up */ + case 38: + if (position > 0) { + --position; + + if (position < 0) { + position = history.length - 1; + } + + command.blur(); + command.value = history[position]; + command.focus(); + } + break; + + /* Down */ + case 40: + if (position >= history.length) { + break; + } + + position++; + + if (position === history.length) { + command.value = ''; + } else { + command.blur(); + command.focus(); + command.value = history[position]; + } + break; + } + }); + + send('motd'); + command.focus(); + }); })(); \ No newline at end of file diff --git a/default/js/components/loading.js b/default/js/components/loading.js new file mode 100644 index 0000000..98cc9fc --- /dev/null +++ b/default/js/components/loading.js @@ -0,0 +1,32 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ + +(() => { + window.Loading = function Loading(element) { + element.addEventListener('click', function (event) { + let element = event.target; + + element.innerHTML = ''; + + let spinner = document.createElement('span'); + spinner.classList.add('spinner-border'); + spinner.classList.add('spinner-border-sm'); + element.append(spinner); + + let text = document.createElement('span'); + text.innerText = ' ' + element.dataset.loading; + element.append(text); + + let dots = document.createElement('span'); + dots.dataset.dots = 'true'; + element.append(dots); + }, false); + }; + + [].map.call(document.querySelectorAll('[data-loading]'), Loading); +})(); \ No newline at end of file diff --git a/default/js/components/modal.js b/default/js/components/modal.js new file mode 100644 index 0000000..a5b6c90 --- /dev/null +++ b/default/js/components/modal.js @@ -0,0 +1,29 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ +'use strict'; + +class Modal { + constructor() { + if(typeof(window.showing) === 'undefined') { + console.warn('[WARNING]', 'window.showing', 'is Empty!'); + return; + } + + window.showing.forEach((modal) => { + let instance = new bootstrap.Modal('#' + modal, {}); + + if(instance) { + instance.show(); + } + }); + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.Modal = new Modal(); +}); \ No newline at end of file diff --git a/default/js/components/module-info.js b/default/js/components/module-info.js new file mode 100644 index 0000000..f1e0abd --- /dev/null +++ b/default/js/components/module-info.js @@ -0,0 +1,213 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ +'use strict'; + +class ModuleInfo { + constructor() { + let modal = document.querySelector('div#module_info'); + + if(modal) { + modal.addEventListener('show.bs.modal', this.onShow.bind(this)); + } + } + + error(modal, text) { + // @ToDo Create Error + console.warn('TPL-Error', text); + } + + build(modal, response) { + let tabs = []; + let module = response.info; + + if(typeof(response.info) == 'undefined') { + return; + } + + /* Header */ + try { + if(module.name) { + modal.querySelector('.modal-title').innerText = module.name; + } + + if(module.icon) { + modal.querySelector('.module-icon').innerHTML = module.icon; + } + + if(module.name) { + modal.querySelector('.module-name').innerText = module.name; + } + + if(module.version) { + modal.querySelector('.module-version').innerText = module.version; + } + + if(module.description) { + modal.querySelector('.module-description').innerText = module.description; + } + + if(module.author) { + if(module.author.email) { + if(module.author.email) { + modal.querySelector('.module-author').innerHTML = '' + module.author.name + ''; + } else if(module.author.name) { + modal.querySelector('.module-author').innerHTML = module.author.name; + } else { + modal.querySelector('.module-author').innerHTML = 'Unknown'; // @ToDo Language + } + } + + if(module.author.url) { + modal.querySelector('.module-url').innerHTML = '' + module.author.url + ''; + } + } + } catch(e) { + console.error('[ERROR]', 'Can\'t handle Module-Infos:', e); + } + + /* Readme */ + try { + if(response.readme.length > 0) { + tabs.push('details'); + modal.querySelector('.nav-item.details-tab').classList.remove('d-none'); + modal.querySelector('div#details-pane').innerHTML = response.readme; + } else { + modal.querySelector('div#details-pane').innerHTML = ''; + } + } catch (e) { + console.error('[ERROR]', 'Can\'t handle Module-Readme:', e); + } + + /* Screenshots */ + try { + if(response.screenshots.length > 0) { + tabs.push('screenshots'); + let screenshots = modal.querySelector('div#screenshotHolder div.carousel-inner'); + modal.querySelector('.nav-item.screenshots-tab').classList.remove('d-none'); + screenshots.innerHTML = ''; + + response.screenshots.forEach((entry, index) => { + let container = document.createElement('div'); + container.classList.add('carousel-item'); + + if(index === 0) { + container.classList.add('active'); + } + + /* Image */ + let image = new Image(); + image.src = entry; + image.classList.add('d-block'); + image.classList.add('w-100'); + container.appendChild(image); + + /* Description */ + if (typeof(module.screenshots[index].description) !== 'undefined') { + let descripton = document.createElement('div'); + + descripton.classList.add('carousel-caption'); + descripton.classList.add('d-none'); + descripton.classList.add('d-md-block'); + descripton.innerHTML = '

' + module.screenshots[index].description + '

'; + + container.appendChild(descripton); + } + + screenshots.appendChild(container); + }); + } else { + screenshots.innerHTML = ''; + } + } catch (e) { + console.error('[ERROR]', 'Can\'t handle Module-Screenshots:', e); + } + + /* Changelog */ + try { + if(response.changelog.length > 0) { + tabs.push('changelog'); + modal.querySelector('.nav-item.changelog-tab').classList.remove('d-none'); + modal.querySelector('div#changelog-pane').innerHTML = response.changelog; + } else { + modal.querySelector('div#changelog-pane').innerHTML = ''; + } + } catch (e) { + console.error('[ERROR]', 'Can\'t handle Module-Changelog:', e); + } + + /* Find the first Tab */ + if(tabs.length > 0) { + let active = tabs[0]; + + modal.querySelector('.nav.nav-tabs').classList.remove('d-none'); + modal.querySelector('.nav-item.' + active + '-tab').classList.remove('d-none'); + modal.querySelector('.nav-item.' + active + '-tab .nav-link').classList.add('active'); + modal.querySelector('.nav-item.' + active + '-tab .nav-link').ariaSelected = true; + modal.querySelector('#' + active + '-pane').classList.add('active'); + modal.querySelector('#' + active + '-pane').classList.add('show'); + } else { + modal.querySelector('.nav.nav-tabs').classList.add('d-none'); + } + + this.loading(modal, false); + } + + loading(modal, state) { + let loading = modal.querySelector('.modal-loading'); + + if(loading) { + loading.dataset.fetching = (state ? 'true' : 'false'); + } + } + + onShow(event) { + let modal = event.target; + modal.classList.add('modal-xl'); + this.loading(modal, true); + + /* Default hide all Tabs */ + [ + 'details', + 'screenshots', + 'changelog', + 'features' + ].forEach((tab) => { + modal.querySelector('.nav-item.' + tab + '-tab').classList.add('d-none'); + modal.querySelector('#' + tab + '-pane').classList.remove('show'); + modal.querySelector('#' + tab + '-pane').classList.remove('active'); + modal.querySelector('.nav-item.' + tab + '-tab .nav-link').classList.remove('active'); + modal.querySelector('.nav-item.' + tab + '-tab .nav-link').ariaSelected = false; + }); + + new Ajax('/ajax').onSuccess( (response) => { + switch(response) { + case 'NO_PERMISSIONS': + this.error(modal, 'You have no permissions for this action.'); // @ToDo Language + break; + case 'NO_REPOSITORYS': + this.error(modal, 'No repositorys.'); // @ToDo Language + break; + case 'MODULE_NOT_FOUND': + this.error(modal, 'Module not found.'); // @ToDo Language + break; + case 'MODULE_EMPTY': + this.error(modal, 'Module is empty.'); // @ToDo Language + break; + default: + this.build(modal, response); + break; + } + }).post({ + 'module': event.relatedTarget.dataset.module + }); + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.ModuleInfo = new ModuleInfo(); +}); \ No newline at end of file diff --git a/default/js/components/popover.js b/default/js/components/popover.js new file mode 100644 index 0000000..30fff5f --- /dev/null +++ b/default/js/components/popover.js @@ -0,0 +1,23 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ +'use strict'; + +class Popover { + constructor() { + document.querySelectorAll('[data-bs-toggle="popover"], [data-bs-toggle="hover"]').forEach((element) => { + new bootstrap.Popover(element, { + trigger: 'hover', + html: true + }); + }); + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.Popover = new Popover(); +}); \ No newline at end of file diff --git a/default/js/components/themes.js b/default/js/components/themes.js new file mode 100644 index 0000000..454e1a8 --- /dev/null +++ b/default/js/components/themes.js @@ -0,0 +1,204 @@ +/* + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT +*/ +'use strict'; + +class Theme { + constructor() { + this.icons = { + light: 'sun-fill', + dark: 'moon-stars-fill', + auto: 'circle-half' + }; + + this.createElement(); + this.setTheme(this.getPreferredTheme()); + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const storedTheme = this.getStoredTheme(); + + if(storedTheme !== 'light' && storedTheme !== 'dark') { + this.setTheme(this.getPreferredTheme()); + } + }); + + this.showActiveTheme(this.getPreferredTheme()); + + document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => { + toggle.addEventListener('click', (event) => { + this.getChange(event.target); + }); + }); + } + + createElement() { + let container = document.createElement('div'); + let active = document.createElement('button'); + let list = document.createElement('ul'); + + [ + 'theme-switch', + 'dropdown', + 'position-fixed', + 'bottom-0', + 'end-0', + 'mb-3', + 'me-3', + 'bd-mode-toggle' + ].forEach(className => { + container.classList.add(className); + }); + + /* Active Button */ + [ + 'btn', + 'border-0', + 'py-2', + 'dropdown-toggle', + 'd-flex', + 'align-items-center' + ].forEach(className => { + active.classList.add(className); + }); + + active.id = 'theme-active'; + active.type = 'button'; + active.ariaExpanded = 'false'; + active.ariaLabel = 'Toggle theme (Auto)'; // @ToDo Language + active.dataset.bsToggle = 'dropdown'; + + // Icon + let icon = document.createElement('i'); + icon.classList.add('bi-' + this.icons.auto); + icon.classList.add('me-2'); + icon.classList.add('opacity-50'); + icon.classList.add('theme-icon'); + icon.dataset.current = 'auto'; + active.appendChild(icon); + + // Span + let span = document.createElement('span'); + span.classList.add('visually-hidden'); + span.id = 'theme-text'; + span.innerText = 'Toggle theme'; // @ToDo Language + active.appendChild(span); + + container.appendChild(active); + + // List + list.classList.add('dropdown-menu'); + list.classList.add('dropdown-menu-end'); + list.classList.add('shadow'); + list.ariaLabelledby = 'theme-text'; + container.appendChild(list); + + let check = document.createElement('i'); + check.classList.add('ms-auto'); + check.classList.add('d-none'); + + Object.keys(this.icons).forEach(type => { + let li = document.createElement('li'); + let button = document.createElement('button'); + let image = document.createElement('i'); + + button.type = 'button'; + button.dataset.bsThemeValue = type; + button.dataset.ariaPressed = 'false'; + button.classList.add('dropdown-item'); + button.classList.add('d-flex'); + button.classList.add('align-items-center'); + + image.classList.add('me-2'); + image.classList.add('opacity-50'); + image.classList.add('theme-icon'); + image.classList.add('bi-' + this.icons[type]); + image.dataset.name = type; + + button.appendChild(image); + button.appendChild(document.createTextNode(type)); + button.appendChild(check); + li.appendChild(button); + list.appendChild(li); + }); + + document.body.appendChild(container); + } + + getChange(element) { + const theme = element.getAttribute('data-bs-theme-value'); + this.setStoredTheme(theme); + this.setTheme(theme); + this.showActiveTheme(theme, true); + } + + getPreferredTheme() { + const storedTheme = this.getStoredTheme(); + + if(storedTheme) { + return storedTheme; + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + + getStoredTheme() { + return localStorage.getItem('theme'); + } + + setStoredTheme(theme) { + localStorage.setItem('theme', theme); + } + + setTheme(theme) { + let type = theme; + + if(theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) { + type = 'dark'; + } + + document.documentElement.setAttribute('data-bs-theme', theme); + } + + showActiveTheme(theme, focus) { + const switcher = document.querySelector('.theme-switch'); + + if(!switcher) { + return; + } + + const text = switcher.querySelector('#theme-text'); + const icon = switcher.querySelector('#theme-active i'); + const active = switcher.querySelector(`[data-bs-theme-value="${theme}"]`); + const svg = active.querySelector('i'); + + switcher.querySelectorAll('[data-bs-theme-value]').forEach(element => { + element.classList.remove('active'); + element.setAttribute('aria-pressed', 'false'); + }); + + active.classList.add('active'); + active.setAttribute('aria-pressed', 'true'); + + if(icon) { + Object.keys(this.icons).forEach(type => { + icon.classList.remove('bi-' + this.icons[type]); + }); + + icon.classList.add('bi-' + this.icons[svg.dataset.name]); + } + + switcher.setAttribute('aria-label', `${text.textContent} (${active.dataset.bsThemeValue})`); + + if(focus) { + switcher.focus(); + } + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.Theme = new Theme(); +}); \ No newline at end of file diff --git a/default/js/components/tooltip.js b/default/js/components/tooltip.js new file mode 100644 index 0000000..a3393a7 --- /dev/null +++ b/default/js/components/tooltip.js @@ -0,0 +1,20 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ +'use strict'; + +class Tooltip { + constructor() { + document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((element) => { + new bootstrap.Tooltip(element) + }); + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.Tooltip = new Tooltip(); +}); \ No newline at end of file diff --git a/default/js/global.js b/default/js/global.js deleted file mode 100644 index 7dd1372..0000000 --- a/default/js/global.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * fruithost | OpenSource Hosting - * - * @author Adrian Preuß - * @version 1.0.0 - * @license MIT - */ - -(() => { - 'use strict' - - const getStoredTheme = () => localStorage.getItem('theme') - const setStoredTheme = theme => localStorage.setItem('theme', theme) - - const getPreferredTheme = () => { - const storedTheme = getStoredTheme() - - if (storedTheme) { - return storedTheme - } - - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' - } - - const setTheme = theme => { - if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) { - document.documentElement.setAttribute('data-bs-theme', 'dark') - } else { - document.documentElement.setAttribute('data-bs-theme', theme) - } - } - - setTheme(getPreferredTheme()) - - const showActiveTheme = (theme, focus = false) => { - const themeSwitcher = document.querySelector('#bd-theme') - - if (!themeSwitcher) { - return - } - - const themeSwitcherText = document.querySelector('#bd-theme-text') - const themeButon = document.querySelector('.theme-icon') - const activeThemeIcon = document.querySelector('.theme-icon-active') - const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) - const svgOfActiveBtn = btnToActive.querySelector('i') - - document.querySelectorAll('[data-bs-theme-value]').forEach(element => { - element.classList.remove('active') - element.setAttribute('aria-pressed', 'false') - }) - - btnToActive.classList.add('active') - btnToActive.setAttribute('aria-pressed', 'true') - - themeButon.classList.remove(themeButon.dataset.current); - themeButon.dataset.current = svgOfActiveBtn.dataset.name; - themeButon.classList.add(svgOfActiveBtn.dataset.name); - svgOfActiveBtn.classList.add('theme-icon-active'); - - const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})` - themeSwitcher.setAttribute('aria-label', themeSwitcherLabel) - - if (focus) { - themeSwitcher.focus() - } - } - - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { - const storedTheme = getStoredTheme() - - if (storedTheme !== 'light' && storedTheme !== 'dark') { - setTheme(getPreferredTheme()) - } - }) - - window.addEventListener('DOMContentLoaded', () => { - showActiveTheme(getPreferredTheme()) - - document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => { - toggle.addEventListener('click', () => { - const theme = toggle.getAttribute('data-bs-theme-value') - setStoredTheme(theme) - setTheme(theme) - showActiveTheme(theme, true) - }) - }) - }) -})(); \ No newline at end of file diff --git a/default/js/login.js b/default/js/login.js deleted file mode 100644 index 8effa67..0000000 --- a/default/js/login.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * fruithost | OpenSource Hosting - * - * @author Adrian Preuß - * @version 1.0.0 - * @license MIT - */ - -(() => { - 'use strict' - - const getStoredTheme = () => localStorage.getItem('theme') - const setStoredTheme = theme => localStorage.setItem('theme', theme) - - const getPreferredTheme = () => { - const storedTheme = getStoredTheme() - - if (storedTheme) { - return storedTheme - } - - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' - } - - const setTheme = theme => { - if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) { - document.documentElement.setAttribute('data-bs-theme', 'dark') - } else { - document.documentElement.setAttribute('data-bs-theme', theme) - } - } - - setTheme(getPreferredTheme()) - - const showActiveTheme = (theme, focus = false) => { - const themeSwitcher = document.querySelector('#bd-theme') - - if (!themeSwitcher) { - return - } - - const themeSwitcherText = document.querySelector('#bd-theme-text') - const activeThemeIcon = document.querySelector('.theme-icon-active') - const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) - const svgOfActiveBtn = btnToActive.querySelector('i') - - document.querySelectorAll('[data-bs-theme-value]').forEach(element => { - element.classList.remove('active') - element.setAttribute('aria-pressed', 'false') - }) - - btnToActive.classList.add('active') - btnToActive.setAttribute('aria-pressed', 'true') - activeThemeIcon.classList.remove(...activeThemeIcon.classList); - activeThemeIcon.classList.add(svgOfActiveBtn.classList[0]); - activeThemeIcon.classList.add('theme-icon-active'); - const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})` - themeSwitcher.setAttribute('aria-label', themeSwitcherLabel) - - if (focus) { - themeSwitcher.focus() - } - } - - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { - const storedTheme = getStoredTheme() - - if (storedTheme !== 'light' && storedTheme !== 'dark') { - setTheme(getPreferredTheme()) - } - }) - - window.addEventListener('DOMContentLoaded', () => { - showActiveTheme(getPreferredTheme()) - - document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => { - toggle.addEventListener('click', () => { - const theme = toggle.getAttribute('data-bs-theme-value') - setStoredTheme(theme) - setTheme(theme) - showActiveTheme(theme, true) - }) - }) - }) -})() \ No newline at end of file diff --git a/default/js/navigation.js b/default/js/navigation.js new file mode 100644 index 0000000..269aa3b --- /dev/null +++ b/default/js/navigation.js @@ -0,0 +1,61 @@ +/** + * fruithost | OpenSource Hosting + * + * @author Adrian Preuß + * @version 1.0.0 + * @license MIT + */ +'use strict'; + +class Navigation { + constructor() { + let sidebar = document.querySelector('.sidebar'); + + [].map.call(document.querySelectorAll('.collapse'), (collapse) => { + collapse.addEventListener('show.bs.collapse', this.show, false); + collapse.addEventListener('shown.bs.collapse', this.show, false); + collapse.addEventListener('hide.bs.collapse', this.hide, false); + collapse.addEventListener('hidden.bs.collapse', this.hide, false); + }); + + [].map.call(document.querySelectorAll('[data-bs-toggle="collapse"] a[href]'), (collapse) => { + collapse.addEventListener('click', (event) => { + window.location.href = event.target.href; + }, false); + }); + + window.addEventListener('resize', (event) => { + if(window.getComputedStyle(sidebar, null).display !== 'block') { + sidebar.classList.remove('show'); + } + }, true); + + document.querySelector('[data-bs-toggle="sidebar"]').addEventListener('click', (event) => { + if(window.getComputedStyle(sidebar, null).display !== 'block') { + sidebar.classList.add('show'); + } else { + sidebar.classList.remove('show'); + } + }); + } + + show(event) { + [].map.call(document.querySelectorAll('.bi[data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"], [data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"] .bi'), function (icon) { + icon.classList.remove('bi-caret-up-square-fill'); + icon.classList.remove('bi-caret-down-square-fill'); + icon.classList.add('bi-caret-up-square-fill'); + }); + } + + hide(event) { + [].map.call(document.querySelectorAll('.bi[data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"], [data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"] .bi'), function (icon) { + icon.classList.remove('bi-caret-up-square-fill'); + icon.classList.remove('bi-caret-down-square-fill'); + icon.classList.add('bi-caret-down-square-fill'); + }); + } +} + +window.addEventListener('DOMContentLoaded', () => { + window.Theme = new Navigation(); +}); \ No newline at end of file diff --git a/default/js/ui.js b/default/js/ui.js deleted file mode 100644 index c809875..0000000 --- a/default/js/ui.js +++ /dev/null @@ -1,395 +0,0 @@ -/** - * fruithost | OpenSource Hosting - * - * @author Adrian Preuß - * @version 1.0.0 - * @license MIT - */ - -(() => { - function show(event) { - [].map.call(document.querySelectorAll('.bi[data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"], [data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"] .bi'), function (icon) { - icon.classList.remove('bi-caret-up-square-fill'); - icon.classList.remove('bi-caret-down-square-fill'); - icon.classList.add('bi-caret-up-square-fill'); - }); - }; - - function hide(event) { - [].map.call(document.querySelectorAll('.bi[data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"], [data-bs-toggle="collapse"][data-bs-target="#' + event.target.id + '"] .bi'), function (icon) { - icon.classList.remove('bi-caret-up-square-fill'); - icon.classList.remove('bi-caret-down-square-fill'); - icon.classList.add('bi-caret-down-square-fill'); - }); - }; - - [].map.call(document.querySelectorAll('.collapse'), function (collapse) { - collapse.addEventListener('show.bs.collapse', show, false); - collapse.addEventListener('shown.bs.collapse', show, false); - collapse.addEventListener('hide.bs.collapse', hide, false); - collapse.addEventListener('hidden.bs.collapse', hide, false); - }); - - [].map.call(document.querySelectorAll('[data-bs-toggle="collapse"] a[href]'), function (collapse) { - collapse.addEventListener('click', (event) => { - window.location.href = event.target.href; - }, false); - }); - - - let sidebar = document.querySelector('.sidebar'); - - window.addEventListener('resize', (event) => { - if (window.getComputedStyle(sidebar, null).display !== 'block') { - sidebar.classList.remove('show'); - } - }, true); - - document.querySelector('[data-bs-toggle="sidebar"]').addEventListener('click', (event) => { - if (window.getComputedStyle(sidebar, null).display !== 'block') { - sidebar.classList.add('show'); - } else { - sidebar.classList.remove('show'); - } - }); - - const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') - const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) - - const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"], [data-bs-toggle="hover"]') - const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl, { - trigger: 'hover', - html: true - })) - - function serialize(form) { - var obj = {}; - var formData = new FormData(form); - for (var key of formData.keys()) { - obj[key] = formData.get(key); - } - return obj; - }; - - function ajaxSubmit(event) { - event.preventDefault(); - - try { - let form = this; - - // @ToDo add onError? - - new Ajax(form.action).onSuccess(function (response) { - if (response.toLowerCase() === 'true' || response.toLowerCase() === '1') { - window.location.reload(); - return; - } else if (response.toLowerCase() == 'close') { - let node = form.parentNode.closest('.modal'); - node.classList.remove('fade'); - var modal = bootstrap.Modal.getInstance(node); - modal.hide(); - event.stopPropagation(); - return; - } else if (response.toLowerCase() === 'false') { - response = 'An unknown error has occurred.'; - } - - let content = form.querySelector('.modal-body'); - let alert = content.querySelector('.alert'); - - if (alert) { - content.removeChild(alert); - } - - alert = document.createElement('div'); - alert.classList.add('alert'); - alert.classList.add('alert-danger'); - alert.setAttribute('role', 'alert'); - alert.innerHTML = response; - content.prepend(alert); - }).post(serialize(form)); - } catch (e) { - - } - - return false; - }; - - [].map.call(document.querySelectorAll('.ajax'), function (form) { - /*form.addEventListener('click', function(event) { - ajaxSubmit.apply(form, [ event ]); - });*/ - - form.addEventListener('submit', function (event) { - ajaxSubmit.apply(form, [event]); - }); - }); - - window.Loading = function Loading(element) { - element.addEventListener('click', function (event) { - let element = event.target; - - element.innerHTML = ''; - - let spinner = document.createElement('span'); - spinner.classList.add('spinner-border'); - spinner.classList.add('spinner-border-sm'); - element.append(spinner); - - let text = document.createElement('span'); - text.innerText = ' ' + element.dataset.loading; - element.append(text); - - let dots = document.createElement('span'); - dots.dataset.dots = 'true'; - element.append(dots); - }, false); - }; - - [].map.call(document.querySelectorAll('[data-loading]'), Loading); - - document.querySelector('div#module_info').addEventListener('show.bs.modal', (event) => { - let modal = event.target; - modal.classList.add('modal-xl'); - modal.querySelector('.modal-loading').dataset.fetching = true; - - /* Default hide all Tabs */ - [ - 'details', - 'screenshots', - 'changelog', - 'features' - ].forEach((tab) => { - modal.querySelector('.nav-item.' + tab + '-tab').classList.add('d-none'); - modal.querySelector('#' + tab + '-pane').classList.remove('show'); - modal.querySelector('#' + tab + '-pane').classList.remove('active'); - modal.querySelector('.nav-item.' + tab + '-tab .nav-link').classList.remove('active'); - modal.querySelector('.nav-item.' + tab + '-tab .nav-link').ariaSelected = false; - }); - - new Ajax('/ajax').onSuccess(function (response) { - switch (response) { - case 'NO_PERMISSIONS': - - break; - case 'NO_REPOSITORYS': - - break; - case 'MODULE_NOT_FOUND': - - break; - case 'MODULE_EMPTY': - - break; - default: - - break; - } - - let tabs = []; - let module = response.info; - - if (typeof (response.info) == 'undefined') { - return; - } - - /* Header */ - try { - modal.querySelector('.modal-title').innerText = module.name; - modal.querySelector('.module-icon').innerHTML = module.icon; - modal.querySelector('.module-name').innerText = module.name; - modal.querySelector('.module-version').innerText = module.version; - modal.querySelector('.module-description').innerText = module.description; - - modal.querySelector('.module-author').innerHTML = '' + module.author.name + ''; - modal.querySelector('.module-url').innerHTML = '' + module.author.url + ''; - } catch (e) { - console.error('Cant handle Module-Infos:', e); - } - - /* Readme */ - try { - if (response.readme.length > 0) { - tabs.push('details'); - modal.querySelector('.nav-item.details-tab').classList.remove('d-none'); - modal.querySelector('div#details-pane').innerHTML = response.readme; - } else { - modal.querySelector('div#details-pane').innerHTML = ''; - } - } catch (e) { - console.error('Cant handle Module-Readme:', e); - } - - /* Screenshots */ - try { - if (response.screenshots.length > 0) { - tabs.push('screenshots'); - let screenshots = modal.querySelector('div#screenshotHolder div.carousel-inner'); - modal.querySelector('.nav-item.screenshots-tab').classList.remove('d-none'); - screenshots.innerHTML = ''; - - response.screenshots.forEach((entry, index) => { - let container = document.createElement('div'); - container.classList.add('carousel-item'); - if (index == 0) { - container.classList.add('active'); - } - - /* Image */ - let image = new Image(); - image.src = entry; - image.classList.add('d-block'); - image.classList.add('w-100'); - container.appendChild(image); - - /* Description */ - if (typeof (module.screenshots[index].description) !== 'undefined') { - let descripton = document.createElement('div'); - descripton.classList.add('carousel-caption'); - descripton.classList.add('d-none'); - descripton.classList.add('d-md-block'); - descripton.innerHTML = '

' + module.screenshots[index].description + '

'; - container.appendChild(descripton); - } - - screenshots.appendChild(container); - }); - } else { - screenshots.innerHTML = ''; - } - } catch (e) { - console.error('Cant handle Module-Screenshots:', e); - } - - /* Changelog */ - try { - if (response.changelog.length > 0) { - tabs.push('changelog'); - modal.querySelector('.nav-item.changelog-tab').classList.remove('d-none'); - modal.querySelector('div#changelog-pane').innerHTML = response.changelog; - } else { - modal.querySelector('div#changelog-pane').innerHTML = ''; - } - } catch (e) { - console.error('Cant handle Module-Changelog:', e); - } - - /* Find the first Tab */ - if (tabs.length > 0) { - let active = tabs[0]; - modal.querySelector('.nav.nav-tabs').classList.remove('d-none'); - modal.querySelector('.nav-item.' + active + '-tab').classList.remove('d-none'); - modal.querySelector('.nav-item.' + active + '-tab .nav-link').classList.add('active'); - modal.querySelector('.nav-item.' + active + '-tab .nav-link').ariaSelected = true; - modal.querySelector('#' + active + '-pane').classList.add('active'); - modal.querySelector('#' + active + '-pane').classList.add('show'); - } else { - modal.querySelector('.nav.nav-tabs').classList.add('d-none'); - } - - modal.querySelector('.modal-loading').dataset.fetching = false; - }).post({ - 'module': event.relatedTarget.dataset.module - }); - }); - - window.Confirm = function Confirm(element) { - element.addEventListener('mousedown', function (event) { - - let target = event.target; - let el = document.querySelector('#confirmation'); - const popup = new bootstrap.Modal(el); - let prevent = true; - let header = popup._element.querySelector('.modal-header'); - - let a = document.createElement('p'); - a.classList.add('m-0'); - a.classList.add('text-center'); - a.classList.add('p-3'); - a.innerHTML = target.dataset.confirm; - - let b = document.createElement('p'); - b.classList.add('modal-body'); - b.classList.add('d-none'); - b.innerHTML = target.dataset.confirm; - - [].map.call(header.parentNode.querySelectorAll('p'), function (e) { - e.parentNode.removeChild(e); - }); - - popup._element.querySelector('.modal-footer').classList.add('text-center'); - - - header.parentNode.insertBefore(a, header.nextSibling); - header.parentNode.insertBefore(b, header.nextSibling); - - popup.show(); - - let _watcher = setInterval(function () { - var res = el.querySelector('.alert'); - - if (res == null || typeof (res) === 'undefined') { - return; - } - var state = res.innerText; - - if (state === 'CONFIRMED') { - clearInterval(_watcher); - - if (target.tagName === 'A') { - window.location.href = target.href; - } else { - target.click(); - } - } - }, 500); - - el.addEventListener('hide.bs.modal', function (event) { - clearInterval(_watcher); - }); - - if (prevent) { - event.preventDefault(); - return false; - } - }); - }; - - [].map.call(document.querySelectorAll('[data-confirm]'), Confirm); - - - window.showing.forEach((modal) => { - new bootstrap.Modal('#' + modal, {}).show(); - }); -})(); - - -/* - - $('.sortable').sortable({ - appendTo: 'parent', - cursor: 'move', - handle: '.moveable', - dropOnEmpty: false, - forceHelperSize: false, - forcePlaceholderSize: false, - helper: 'original', - items: '> li', - placeholder: { - element: function(currentItem) { - return $('
Drop here
')[0]; - }, - update: function(container, p) { - return; - } - }, - scroll: false, - beforeStop: function onBeforeClose(event, ui) { - $('body').removeClass('sortable'); - }, - start: function onStart(event, ui) { - $('body').addClass('sortable'); - ui.placeholder.addClass('col-6'); - } - }); -}(jQuery));*/ \ No newline at end of file diff --git a/default/modal.php b/default/modal.php index ee70a78..f272a59 100644 --- a/default/modal.php +++ b/default/modal.php @@ -51,13 +51,15 @@ isShowing()) { - ?> - - isShowing()) { + ?> + + \ No newline at end of file diff --git a/handler/admin/modules.php b/handler/admin/modules.php index 1cb0dcb..0ced70b 100644 --- a/handler/admin/modules.php +++ b/handler/admin/modules.php @@ -38,6 +38,7 @@ $template->getAdminCore()->addModal((new Modal('module_info', '', 'admin/modules/info'))->addButton([ (new Button())->setName('cancel')->setLabel(I18N::get('Close'))->addClass('btn-outline-secondary')->setDismissable() ])); + $upgradeable = []; $list = sprintf('%s%s%s%s%s', dirname(PATH), DS, 'temp', DS, 'update.list'); $module = null;