diff --git a/src/package-lock.json b/src/package-lock.json --- a/src/package-lock.json +++ b/src/package-lock.json @@ -3550,6 +3550,12 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz", + "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==", + "dev": true + }, "@stylelint/postcss-css-in-js": { "version": "0.37.2", "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", @@ -4555,9 +4561,9 @@ "dev": true }, "bootstrap": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", - "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz", + "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q==", "dev": true }, "brace-expansion": { @@ -4789,6 +4795,12 @@ "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", "dev": true }, + "cash-dom": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cash-dom/-/cash-dom-8.1.0.tgz", + "integrity": "sha512-QTa50rFuPaX8klEDEbwLr+jVutwpvZEBQ0NpMMyng+je7gNe9Bz/JsOLHIG24tvNSSSIN/Q1QD0bnF6PQzWKHA==", + "dev": true + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -7662,12 +7674,6 @@ } } }, - "jquery": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9280,12 +9286,6 @@ "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", "dev": true }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha1-KiI8s9x7YhPXQOQDcr5A3kPmWxs=", - "dev": true - }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", diff --git a/src/package.json b/src/package.json --- a/src/package.json +++ b/src/package.json @@ -17,17 +17,17 @@ "@fortawesome/free-regular-svg-icons": "^5.15.3", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/vue-fontawesome": "^0.1.10", + "@popperjs/core": "^2.9.2", "anchorme": "^2.1.2", "axios": "^0.21.1", - "bootstrap": "^4.6.0", + "bootstrap": "^5.0.0", + "cash-dom": "^8.1.0", "cross-env": "^7.0.3", "eslint": "^7.26.0", "eslint-plugin-vue": "^7.9.0", "frappe-charts": "^1.5.8", - "jquery": "^3.6.0", "laravel-mix": "^6.0.27", "openvidu-browser": "^2.18.0", - "popper.js": "^1.16.0", "postcss": "^8.3.6", "resolve-url-loader": "^4.0.0", "sass": "^1.32.8", diff --git a/src/resources/js/app.js b/src/resources/js/app.js --- a/src/resources/js/app.js +++ b/src/resources/js/app.js @@ -10,9 +10,10 @@ import MenuComponent from '../vue/Widgets/Menu' import SupportForm from '../vue/Widgets/SupportForm' import store from './store' +import { Tab } from 'bootstrap' import { loadLangAsync, i18n } from './locale' -const loader = '
Loading
' +const loader = '
Loading
' let isLoading = 0 @@ -177,8 +178,14 @@ return `${this.appName}` }, // Display "loading" overlay inside of the specified element - addLoader(elem, small = true) { - $(elem).css({position: 'relative'}).append(small ? $(loader).addClass('small') : $(loader)) + addLoader(elem, small = true, style = null) { + if (style) { + $(elem).css(style) + } else { + $(elem).css('position', 'relative') + } + + $(elem).append(small ? $(loader).addClass('small') : $(loader)) }, // Remove loader element added in addLoader() removeLoader(elem) { @@ -191,7 +198,7 @@ }, tab(e) { e.preventDefault() - $(e.target).tab('show') + new Tab(e.target).show() }, errorPage(code, msg, hint) { // Until https://github.com/vuejs/vue-router/issues/977 is implemented @@ -267,10 +274,7 @@ }, clickRecord(event) { if (!/^(a|button|svg|path)$/i.test(event.target.nodeName)) { - let link = $(event.target).closest('tr').find('a')[0] - if (link) { - link.click() - } + $(event.target).closest('tr').find('a').trigger('click') } }, domainStatusClass(domain) { @@ -350,21 +354,19 @@ return page ? page : '404' }, supportDialog(container) { - let dialog = $('#support-dialog') + let dialog = $('#support-dialog')[0] - // FIXME: Find a nicer way of doing this - if (!dialog.length) { + if (!dialog) { + // FIXME: Find a nicer way of doing this SupportForm.i18n = i18n let form = new Vue(SupportForm) form.$mount($('
').appendTo(container)[0]) form.$root = this form.$toast = this.$toast - dialog = $(form.$el) + dialog = form.$el } - dialog.on('shown.bs.modal', () => { - dialog.find('input').first().focus() - }).modal() + dialog.__vue__.showDialog() }, userStatusClass(user) { if (user.isDeleted) { @@ -464,7 +466,7 @@ // Create an error message // API responses can use a string, array or object let msg_text = '' - if ($.type(msg) !== 'string') { + if (typeof(msg) !== 'string') { $.each(msg, (index, str) => { msg_text += str + ' ' }) diff --git a/src/resources/js/bootstrap.js b/src/resources/js/bootstrap.js --- a/src/resources/js/bootstrap.js +++ b/src/resources/js/bootstrap.js @@ -1,18 +1,22 @@ /** - * We'll load jQuery and the Bootstrap jQuery plugin which provides support - * for JavaScript based Bootstrap features such as modals and tabs. This - * code may be modified to fit the specific needs of your application. + * Import Cash (jQuery replacement) */ -window.Popper = require('popper.js').default -window.$ = window.jQuery = require('jquery') +import $ from 'cash-dom' +window.$ = $ -require('bootstrap') +$.fn.focus = function() { + if (this.length && this[0].focus) { + this[0].focus() + } + return this +} /** - * We'll load Vue, VueRouter and global components + * Load Vue, VueRouter and global components */ +import { Tooltip } from 'bootstrap' import FontAwesomeIcon from './fontawesome' import Vue from 'vue' import VueRouter from 'vue-router' @@ -24,26 +28,26 @@ Vue.component('SvgIcon', FontAwesomeIcon) const vTooltip = (el, binding) => { - const t = [] + let t = [] if (binding.modifiers.focus) t.push('focus') if (binding.modifiers.hover) t.push('hover') if (binding.modifiers.click) t.push('click') - if (!t.length) t.push('hover') + if (!t.length) t.push('click') - $(el).tooltip({ + el.tooltip = new Tooltip(el, { title: binding.value, placement: binding.arg || 'top', trigger: t.join(' '), - html: !!binding.modifiers.html, - }); + html: !!binding.modifiers.html + }) } Vue.directive('tooltip', { bind: vTooltip, update: vTooltip, unbind (el) { - $(el).tooltip('dispose') + el.tooltip.dispose() } }) @@ -70,7 +74,7 @@ }) /** - * We'll load the axios HTTP library which allows us to easily issue requests + * Load the axios HTTP library which allows us to easily issue requests * to our Laravel back-end. This library automatically handles sending the * CSRF token as a header based on the value of the "XSRF" token cookie. */ diff --git a/src/resources/js/meet/app.js b/src/resources/js/meet/app.js --- a/src/resources/js/meet/app.js +++ b/src/resources/js/meet/app.js @@ -1,4 +1,5 @@ import anchorme from 'anchorme' +import { Dropdown } from 'bootstrap' import { library } from '@fortawesome/fontawesome-svg-core' import { OpenVidu } from 'openvidu-browser' @@ -1033,7 +1034,7 @@ } if (params.isSelf) { - wrapper.find('.link-setup').removeClass('hidden').click(() => sessionData.onMediaSetup()) + wrapper.find('.link-setup').removeClass('hidden').on('click', () => sessionData.onMediaSetup()) } else { let volumeInput = wrapper.find('.volume input') let audioButton = wrapper.find('.link-audio') @@ -1284,19 +1285,19 @@ + '' + '
' + '' - + '
' + '' + '
' + '' - + '
' + '' + languages.join('') + '
' @@ -1307,8 +1308,9 @@ let nickname = element.find('.meet-nickname') .addClass('btn btn-outline-' + (params.isSelf ? 'primary' : 'secondary')) - .attr({title: $t('meet.menu-options'), 'data-toggle': 'dropdown'}) - .dropdown({boundary: container.parentNode}) + .attr({title: $t('meet.menu-options'), 'data-bs-toggle': 'dropdown'}) + + const dropdown = new Dropdown(nickname[0], {boundary: container.parentNode}) if (params.isSelf) { // Add events for nickname change @@ -1393,7 +1395,7 @@ .on('change', e => { const language = $(e.target).val() sessionData.onConnectionChange(params.connectionId, { language }) - element.find('.meet-nickname').dropdown('hide') + dropdown.hide() }) .on('click', e => { // Prevents from closing the dropdown menu on click diff --git a/src/resources/lang/en/ui.php b/src/resources/lang/en/ui.php --- a/src/resources/lang/en/ui.php +++ b/src/resources/lang/en/ui.php @@ -336,6 +336,7 @@ 'create' => "Create user", 'custno' => "Customer No.", 'delete' => "Delete user", + 'delete-account' => "Delete this account?", 'delete-email' => "Delete {email}", 'delete-text' => "Do you really want to delete this user permanently?" . " This will delete all account data and withdraw the permission to access the email account." diff --git a/src/resources/themes/bootstrap.scss b/src/resources/themes/bootstrap.scss --- a/src/resources/themes/bootstrap.scss +++ b/src/resources/themes/bootstrap.scss @@ -1,15 +1,13 @@ -// Bootstrap @import '~bootstrap/scss/bootstrap'; -// Bootstrap style fixes +// Bootstrap style fixes and additions -.btn-link, -.table thead th { - border: 0; +a { + text-decoration: none; } -small { - font-size: 0.875em; +.table > :not(:last-child) > :last-child > * { + border-bottom-color: #ccc; } .hidden { diff --git a/src/resources/themes/default/lang/en/faq.php b/src/resources/themes/default/lang/en/faq.php --- a/src/resources/themes/default/lang/en/faq.php +++ b/src/resources/themes/default/lang/en/faq.php @@ -5,5 +5,6 @@ 'account-upgrade' => "Can I upgrade an individual account to a group account?", 'storage' => "How much storage comes with my account?", 'tos' => "What are your terms of service?", + 'support' => "Need support?", ]; diff --git a/src/resources/themes/default/theme.json b/src/resources/themes/default/theme.json --- a/src/resources/themes/default/theme.json +++ b/src/resources/themes/default/theme.json @@ -39,6 +39,10 @@ { "href": "https://kb.kolabnow.com/faq/how-much-storage-comes-with-my-account", "label": "storage" + }, + { + "href": "/support", + "label": "support" } ] } diff --git a/src/resources/themes/forms.scss b/src/resources/themes/forms.scss --- a/src/resources/themes/forms.scss +++ b/src/resources/themes/forms.scss @@ -33,12 +33,15 @@ label { margin-right: 0.5em; + min-width: 4em; + text-align: right; + line-height: 1.7; } } .input-group-activable { &.active { - :not(.input-group-append):not(.activable) { + :not(.activable) { display: none; } } @@ -70,19 +73,32 @@ margin-top: -0.25rem; } -form.read-only { - .row { - margin-bottom: 0; - } -} - // Various improvements for mobile @include media-breakpoint-down(sm) { - .form-group { - margin-bottom: 0.5rem; + .row.mb-3 { + margin-bottom: 0.5rem !important; + } + + .nav-tabs { + flex-wrap: nowrap; + overflow-x: auto; + + .nav-link { + white-space: nowrap; + padding: 0.5rem 0.75rem; + } + } + + .tab-content { + margin-top: 0.5rem; + } + + .col-form-label { + color: #666; + font-size: 95%; } - .form-group.plaintext .col-form-label { + .row.plaintext .col-form-label { padding-bottom: 0; } @@ -93,19 +109,11 @@ width: 65%; } } -} - -@include media-breakpoint-down(xs) { - .col-form-label { - color: #666; - font-size: 95%; - } - .form-group.checkbox { + .row.checkbox { position: relative; & > div { - position: initial; padding-top: 0 !important; input { diff --git a/src/resources/themes/meet.scss b/src/resources/themes/meet.scss --- a/src/resources/themes/meet.scss +++ b/src/resources/themes/meet.scss @@ -54,8 +54,7 @@ & + .dropdown-menu { .permissions > label { - margin: 0; - padding-left: 3.75rem; + padding-left: 3.5rem; } } } @@ -514,7 +513,7 @@ } } -@include media-breakpoint-down(xs) { +@include media-breakpoint-down(sm) { #meet-session-menu { white-space: nowrap; margin: 0; diff --git a/src/resources/themes/menu.scss b/src/resources/themes/menu.scss --- a/src/resources/themes/menu.scss +++ b/src/resources/themes/menu.scss @@ -3,13 +3,10 @@ padding: 0; line-height: 85px; - .navbar { - padding-right: 0; - } - .navbar-brand { - padding: 0; + padding: 0 0.5rem; outline: 0; + margin: 0; > img { display: inline; @@ -20,7 +17,7 @@ .nav-link { color: #202020; line-height: 85px; - padding: 0 0 0 25px; + padding: 0 0.5rem 0 0.5rem; background: transparent; &:focus { @@ -46,12 +43,18 @@ overflow: hidden; padding: 0; - .navbar { - padding-right: 0; + .container { + flex-wrap: nowrap; + } + + .navbar-nav { + display: flex; + flex-direction: row; } .navbar-brand { margin: 0; + padding: 0 0.5rem; img { width: 170px; @@ -69,7 +72,6 @@ margin: 2em 0; .dropdown-toggle { - padding-left: 20px; line-height: 30px; font-weight: lighter; } @@ -88,7 +90,7 @@ line-height: 21px; letter-spacing: 1px; padding: 6px 34px; - margin: 25px 0 25px 25px; + margin: 25px 0.5rem 25px 0.5rem; &:focus, &:hover { @@ -99,25 +101,18 @@ } } } - - .navbar { - .navbar { - justify-content: flex-end; - } - } - - #footer-menu { - .navbar { - flex-direction: column; - align-items: flex-end; - } - } } -@include media-breakpoint-down(md) { +@include media-breakpoint-down(lg) { #header-menu { .navbar-nav { - padding-bottom: 1em; + display: block; + width: 100%; + padding: 0; + + li { + border-top: 1px solid #eee; + } } .nav-link { @@ -126,13 +121,15 @@ } } + #language-selector { + margin: 0; + } +} + +@include media-breakpoint-down(md) { #footer-menu { height: 80px; - .navbar { - padding-left: 0; - } - .navbar-nav { display: none; } @@ -141,29 +138,11 @@ flex-wrap: nowrap; } } - - #language-selector { - margin: 0; - - .dropdown-toggle:after { - display: none; - } - } } @include media-breakpoint-down(sm) { #header-menu { padding: 0 1em; - - .navbar-nav { - display: block; - width: 100%; - padding: 0; - - li { - border-top: 1px solid #eee; - } - } } #footer-menu { @@ -171,6 +150,10 @@ flex-direction: column; } + .navbar-brand { + padding: 0.5rem; + } + #footer-company { display: none; } diff --git a/src/resources/themes/toast.scss b/src/resources/themes/toast.scss --- a/src/resources/themes/toast.scss +++ b/src/resources/themes/toast.scss @@ -8,7 +8,7 @@ overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(52, 58, 64, 0.95) transparent; - z-index: 1055; // above Bootstrap's modal backdrop and dialogs + z-index: 1065; // above Bootstrap's modal backdrop and dialogs @media (max-width: 375px) { left: 0; @@ -42,15 +42,9 @@ margin-right: 0.5rem; } - button.close { - color: #eee; - opacity: 1 !important; - text-shadow: none; - font-size: 1.2rem; - - &:hover { - color: #fff; - } + .btn-close { + font-size: 0.8em; + cursor: pointer; } } diff --git a/src/resources/vue/Admin/Distlist.vue b/src/resources/vue/Admin/Distlist.vue --- a/src/resources/vue/Admin/Distlist.vue +++ b/src/resources/vue/Admin/Distlist.vue @@ -5,7 +5,7 @@
{{ list.email }}
-
+
@@ -15,13 +15,13 @@
-
+
{{ $root.distlistStatusText(list) }}
-
+
diff --git a/src/resources/vue/Admin/Domain.vue b/src/resources/vue/Admin/Domain.vue --- a/src/resources/vue/Admin/Domain.vue +++ b/src/resources/vue/Admin/Domain.vue @@ -5,7 +5,7 @@
{{ domain.namespace }}
-
+
@@ -15,7 +15,7 @@
-
+
@@ -62,7 +62,7 @@
-
+
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue --- a/src/resources/vue/Admin/User.vue +++ b/src/resources/vue/Admin/User.vue @@ -5,7 +5,7 @@

{{ user.email }}

-
+
@@ -13,7 +13,7 @@
-
+
@@ -21,7 +21,7 @@
-
+
@@ -29,31 +29,31 @@
-
+
{{ user.first_name }}
-
+
{{ user.last_name }}
-
+
{{ user.organization }}
-
+
{{ user.phone }}
-
+
@@ -62,13 +62,13 @@
-
+
{{ user.billing_address }}
-
+
{{ user.country }} @@ -132,7 +132,7 @@
-
+
@@ -141,7 +141,7 @@
-
+
-
+
@@ -174,8 +174,8 @@
- - +
+ @@ -198,7 +198,7 @@
{{ $t('form.email') }}
- + @@ -231,8 +231,8 @@
-
{{ $t('user.subscription') }} {{ $t('user.price') }}
- +
+ @@ -257,8 +257,8 @@
-
{{ $t('domain.namespace') }}
- +
+ @@ -284,8 +284,8 @@
-
{{ $t('form.primary-email') }}
- +
+ @@ -311,7 +311,7 @@
-
+
@@ -331,20 +331,18 @@
{{ $t('form.email') }}