diff --git a/css/sideMenu.css b/css/sideMenu.css index 80ed6b7..930a82e 100644 --- a/css/sideMenu.css +++ b/css/sideMenu.css @@ -16,326 +16,334 @@ */ #side-menu { - position: fixed; - top: 0; - left: 0; - height: 100vh; - width: 100%; - max-width: 250px; - background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%); - z-index: 3000; - color: var(--side-menu-text-color, #fff); - box-shadow: rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px; - display: none; + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100%; + max-width: 250px; + background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%); + z-index: 3000; + color: var(--side-menu-text-color, #fff); + box-shadow: rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px; + display: none; } #side-menu a { - transition: 0.2s; + transition: 0.2s; } #side-menu.open { - display: block; + display: block; } #header .side-menu-opener { - margin-left: 0px; + margin-left: 0px; } .side-menu-settings { - margin-right: 9px; - margin-top: 2px; - float: right; - line-height: 34px; - height: 28px; - display: none; + margin-right: 9px; + margin-top: 2px; + float: right; + line-height: 34px; + height: 28px; + display: none; } .side-menu-settings a { - color: var(--side-menu-text-color, #fff); - display: block; - padding: 4px 7px; + color: var(--side-menu-text-color, #fff); + display: block; + padding: 4px 7px; } .side-menu-settings:hover a, .side-menu-settings a:active, .side-menu-settings a:focus { - background: var(--side-menu-current-app-background-color, #444); + background: var(--side-menu-current-app-background-color, #444); } .side-menu-settings img { - vertical-align: bottom; - margin-left: 3px; - width: 32px; - height: 32px; + vertical-align: bottom; + margin-left: 3px; + width: 32px; + height: 32px; } #side-menu.open .side-menu-settings { - display: block; + display: block; } .side-menu-opener { - background: var(--side-menu-opener, url('../img/side-menu-opener.svg')); - background-color: transparent !important; - height: 40px !important; - width: 40px !important; - border-radius: 0 !important; - border: 0 !important; - padding-right: 12px !important; - padding-left: 12px !important; - margin-left: 5px !important; - margin-left: 3px !important; + background: var(--side-menu-opener, url('../img/side-menu-opener.svg')); + background-color: transparent !important; + height: 40px !important; + width: 40px !important; + border-radius: 0 !important; + border: 0 !important; + padding-right: 12px !important; + padding-left: 12px !important; + margin-left: 5px !important; + margin-left: 3px !important; } .side-menu-opener:active, .side-menu-opener:focus { - background-color: var(--side-menu-current-app-background-color, #444) !important; + background-color: var(--side-menu-current-app-background-color, #444) !important; } .side-menu-closer { - background: url('../img/side-menu-opener-closer.svg'); - display: none; + background: url('../img/side-menu-opener-closer.svg'); + display: none; } #side-menu.hide-opener .side-menu-opener, .side-menu-opener.hide, #side-menu.hide { - display: none !important; + display: none !important; } .side-menu-apps-list { - height: calc(100vh - 150px); - z-index: 2200; - position: fixed; - top: 150px; - width: 100%; - max-width: 250px; - overflow: auto; + height: calc(100vh - 150px); + z-index: 2200; + position: fixed; + top: 150px; + width: 100%; + max-width: 250px; + overflow: auto; } .side-menu-app-icon { - width: 20px; - vertical-align: top; - margin-right: 10px; - filter: invert(var(--side-menu-icon-invert-filter, 0%)); - opacity: var(--side-menu-icon-opacity, 1); + width: 20px; + vertical-align: top; + margin-right: 10px; + filter: invert(var(--side-menu-icon-invert-filter, 0%)); + opacity: var(--side-menu-icon-opacity, 1); } .side-menu-app-icon svg { - vertical-align: middle; - margin-top: -3px; + vertical-align: middle; + margin-top: -3px; } .side-menu-app-icon .app-icon-notification { - display: none; + display: none; } .side-menu-app a { - line-height: 30px; - color: var(--side-menu-text-color, #fff); - display: block; - padding: 7px 0 5px 15px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + line-height: 30px; + color: var(--side-menu-text-color, #fff); + display: block; + padding: 7px 0 5px 15px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .side-menu-app a:hover, .side-menu-app.active, .side-menu-app a:focus { - background: var(--side-menu-current-app-background-color, #444); + background: var(--side-menu-current-app-background-color, #444); } .side-menu-logo { - text-align: center; + text-align: center; } .side-menu-logo img { - max-width: 60%; - max-height: 100px; + max-width: 60%; + max-height: 100px; } .side-menu-header { - height: 150px; - width: 100%; - z-index: 2300; - max-width: 250px; - position: fixed; - padding-top: 2px; - top: 0; + height: 150px; + width: 100%; + z-index: 2300; + max-width: 250px; + position: fixed; + padding-top: 2px; + top: 0; } #side-menu.side-menu-with-categories .side-menu-header { - max-width: 295px; + max-width: 295px; } #side-menu.hide-opener .side-menu-logo { - margin-top: 20px; + margin-top: 20px; } #side-menu-loader { - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 3001; + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 3001; } #side-menu-loader-bar { - height: 4px; - background: var(--side-menu-loader-color, #0e75ac); - width: 0; - transition-property: width; + height: 4px; + background: var(--side-menu-loader-color, #0e75ac); + width: 0; + transition-property: width; } #side-menu.side-menu-big, #side-menu.side-menu-with-categories { - max-width: 100%; - height: auto; + max-width: 100%; + height: auto; } .side-menu-big .side-menu-header, .side-menu-with-categories .side-menu-header { - height: auto; + height: auto; } .side-menu-big .side-menu-apps-list, .side-menu-with-categories .side-menu-apps-list { - height: auto; - position: static; - max-width: 100vw; - overflow: auto; + height: auto; + position: static; + max-width: 100vw; + overflow: auto; } .side-menu-big .side-menu-app a, .side-menu-with-categories .side-menu-app a { - padding: 7px 0 7px 7px; + padding: 7px 0 7px 7px; } .side-menu-categories-wrapper { - padding-bottom: 70px; + padding-bottom: 70px; } .side-menu-categories { - max-height: calc(100vh - 50px); - overflow: auto; - position: relative; - top: 50px; - display: flex; - flex-wrap: wrap; - justify-content: center; - padding: 0 10% 0 10%; + max-height: calc(100vh - 50px); + overflow: auto; + position: relative; + top: 50px; + display: flex; + flex-wrap: wrap; + justify-content: center; + padding: 0 10% 0 10%; } .side-menu-category { - padding: 10px 20px; - flex: 1 1 auto; + padding: 10px 20px; + flex: 1 1 auto; } .side-menu-category-title { - padding-left: 10px; - color: var(--side-menu-text-color, #fff); + padding-left: 10px; + color: var(--side-menu-text-color, #fff); } .side-menu-loader { - text-align: center; + text-align: center; } .side-menu-loader svg { - width: 38px; - margin: auto; - stroke: var(--side-menu-text-color, #fff); + width: 38px; + margin: auto; + stroke: var(--side-menu-text-color, #fff); } .side-menu-with-categories .side-menu-app-icon, .side-menu-big .side-menu-app-icon { - vertical-align: middle; - margin-top: -2px; + vertical-align: middle; + margin-top: -2px; } .side-menu-always-displayed #header, .side-menu-always-displayed body { - width: calc(100% - 50px) !important; + width: calc(100% - 50px) !important; } .side-menu-always-displayed body { - position: absolute; - left: 50px; + position: absolute; + left: 50px; } .side-menu-always-displayed #side-menu { - display: block; + display: block; } .side-menu-always-displayed .side-menu-apps-list { - height: calc(100vh - 49px); - top: 49px; - overflow: hidden; + height: calc(100vh - 49px); + top: 49px; + overflow: hidden; } .side-menu-always-displayed .side-menu-apps-list:hover { - overflow: auto; + overflow: auto; } .side-menu-always-displayed #side-menu, .side-menu-always-displayed .side-menu-header, .side-menu-always-displayed .side-menu-apps-list { - width: 50px; + width: 50px; } .side-menu-always-displayed #side-menu .side-menu-app-text, .side-menu-always-displayed #header .side-menu-opener, .side-menu-always-displayed .side-menu-logo { - display: none; + display: none; } .side-menu-always-displayed #side-menu .side-menu-header { - height: 49px; + height: 49px; } .side-menu-always-displayed #side-menu.open, .side-menu-always-displayed #side-menu.open .side-menu-apps-list, .side-menu-always-displayed #side-menu.open .side-menu-header { - width: 100%; + width: 100%; } .side-menu-always-displayed #side-menu.open .side-menu-app-text { - display: inline; + display: inline; } .side-menu-always-displayed .app-navigation--close { - transform: translateX(calc(-100% + 50px)) !important; + transform: translateX(calc(-100% + 50px)) !important; } #side-menu.side-menu-with-categories { - max-width: 290px; - height: 100vh; + max-width: 290px; + height: 100vh; } .side-menu-with-categories .side-menu-categories { - display: block; - padding: 0; + display: block; + padding: 0; } .side-menu-with-categories .side-menu-category { - padding: 10px 0; + padding: 10px 0; } .side-menu-always-displayed #body-settings, #body-settings.body-settings-side-menu { - overflow-x: visible; + overflow-x: visible; +} + +.app-menu { + visibility: hidden; +} + +.app-menu.show { + visibility: visible; } @media screen and (max-width: 1024px) { - #side-menu.side-menu-big { - max-width: 290px; - height: 100vh; - } + #side-menu.side-menu-big { + max-width: 290px; + height: 100vh; + } - .side-menu-categories { - display: block; - padding: 0; - } + .side-menu-categories { + display: block; + padding: 0; + } - .side-menu-category { - padding: 10px 0; - } + .side-menu-category { + padding: 10px 0; + } } @media screen and (min-width: 1024px) { - .side-menu-closer { - display: block; - float: right; - margin-right: 9px; - } + .side-menu-closer { + display: block; + float: right; + margin-right: 9px; + } - .side-menu-big .side-menu-header { - max-width: 100%; - } + .side-menu-big .side-menu-header { + max-width: 100%; + } } diff --git a/package.json b/package.json index fb7e755..c71a094 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,6 @@ "stylelint:fix": "stylelint src --fix" }, "dependencies": { - "@nextcloud/axios": "^1.8.0", - "@nextcloud/vue": "^1.5.0", "axios": "^0.24.0", "trim": "^1.0.1", "vue": "^2.6.11" @@ -24,6 +22,10 @@ "node": ">=16.0.0" }, "devDependencies": { + "@nextcloud/axios": "^1.8.0", + "@nextcloud/initial-state": "^2.0.0", + "@nextcloud/l10n": "^1.6.0", + "@nextcloud/vue": "^1.5.0", "@babel/core": "^7.9.0", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/preset-env": "^7.9.0", diff --git a/src/AppMenu.vue b/src/AppMenu.vue new file mode 100644 index 0000000..c63a3bf --- /dev/null +++ b/src/AppMenu.vue @@ -0,0 +1,297 @@ + + + + + + + + diff --git a/src/SideMenu.js b/src/SideMenu.js index a9848c4..244a6cf 100644 --- a/src/SideMenu.js +++ b/src/SideMenu.js @@ -16,35 +16,51 @@ */ import Vue from 'vue' +import AppMenu from './AppMenu.vue' import SideMenu from './SideMenu.vue' import SideMenuBig from './SideMenuBig.vue' import SideMenuWithCategories from './SideMenuWithCategories.vue' Vue.prototype.OC = OC +Vue.prototype.t = OC.L10N.translate const mountSideMenuComponent = () => { - const sideMenuContainer = document.querySelector('#side-menu') + const container = document.querySelector('#side-menu') - if (sideMenuContainer) { - let component + if (!container) { + return window.setTimeout(mountSideMenuComponent, 50) + } - if (sideMenuContainer.getAttribute('data-bigmenu')) { - component = SideMenuBig - } else if(sideMenuContainer.getAttribute('data-sidewithcategories')) { - component = SideMenuWithCategories - } else { - component = SideMenu - } - - const View = Vue.extend(component) - const sideMenu = new View({}) - - sideMenu.$mount('#side-menu') - - document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.ready')) + const component = (() => { + if (container.getAttribute('data-bigmenu')) { + return SideMenuBig + } else if(container.getAttribute('data-sidewithcategories')) { + return SideMenuWithCategories } else { - window.setTimeout(mountSideMenuComponent, 50) + return SideMenu } + })() + + const View = Vue.extend(component) + const App = new View({}) + + App.$mount('#side-menu') + + document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.ready')) +} + +const mountAppMenu = () => { + const container = document.querySelector('#header .app-menu') + + if (!container) { + return window.setTimeout(mountAppMenu, 50) + } + + const View = Vue.extend(AppMenu) + const App = new View({}) + + App.$mount('#header .app-menu') } mountSideMenuComponent() +mountAppMenu() diff --git a/templates/js/_topMenuApps.js b/templates/js/_topMenuApps.js deleted file mode 100644 index 455b09a..0000000 --- a/templates/js/_topMenuApps.js +++ /dev/null @@ -1,216 +0,0 @@ -let menuCache = null - -const breakpointMobileWidth = 1024 -const usePercentualAppMenuLimit = 0.8 -const minAppsDesktop = 8 - -const handleMenuClick = (e, icon) => { - let element = e.target - - while (element.tagName !== 'LI') { - element = element.parentNode - } - - const a = querySelector('a', element) - - if (a.getAttribute('target') !== '_blank' && e.which === 1 && !e.ctrlKey && !e.metaKey) { - for (let tag of ['svg', 'div']) { - let el = querySelector(tag, element) - - if (el) { - el.remove() - } - } - - const loader = createElement('div', {'class': icon}) - - a.insertBefore(loader, querySelector('span', a)) - } -} - -const updateTopMenu = function() { - const isMobile = window.innerWidth < breakpointMobileWidth - const menu = querySelector('#appmenu') - const moreApps = querySelector('#more-apps') - const navigation = querySelector('#navigation') - const navigationApps = querySelector('#apps ul') - - let apps = querySelectorAll('li', menu) - let lastShownApp = null - let appShown = [] - - if ((menu.innerHTML + menu.nextSibling.innerHTML) === menuCache) { - return - } - - let navigationAppsHtml = '' - - for (let app of apps) { - const dataId = app.getAttribute('data-id') - - if (dataId === null) { - continue - } - - if (topMenuApps.indexOf(dataId) === -1 && topSideMenuApps.indexOf(dataId) === -1) { - app.classList.add('hidden') - app.classList.add('app-hidden') - } else { - app.classList.remove('hidden') - app.classList.add('app-external-site') - - if (topSideMenuApps.indexOf(dataId) !== -1) { - app.classList.add('app-top-side-menu') - } - - appShown.push(app) - - navigationAppsHtml = navigationAppsHtml + app.outerHTML - } - - if (targetBlankApps.indexOf(dataId) !== -1) { - querySelector('a', app).setAttribute('target', '_blank') - } - } - - navigationApps.innerHTML = navigationAppsHtml - - const rightHeaderWidth = querySelector('.header-right').offsetWidth - const headerWidth = querySelector('header').offsetWidth - - let availableWidth = headerWidth - - availableWidth -= nextcloud.offsetWidth - availableWidth -= querySelector('#header .side-menu-opener').offsetWidth - availableWidth -= rightHeaderWidth > 230 ? rightHeaderWidth : 230 - availableWidth *= isMobile ? usePercentualAppMenuLimit : 1 - - let appCount = Math.floor(availableWidth / querySelector('#appmenu li:not(.hidden)').offsetWidth) - - if (isMobile && appCount > minAppsDesktop) { - appCount = minAppsDesktop - } else if (!isMobile && appCount < minAppsDesktop) { - appCount = minAppsDesktop - } - - menu.style.opacity = 1 - - if (appShown.length - 1 - appCount >= 1) { - appCount-- - } - - for (let item of querySelectorAll('a', moreApps)) { - item.classList.remove('active') - } - - let k = 0 - let notInHeader = 0 - - for (let app of appShown) { - const name = app.getAttribute('data-id') - const li = querySelector('#apps li[data-id=' + name + '].app-external-site') - - if (k < appCount && appCount > 0) { - app.classList.remove('hidden') - li.classList.add('in-header') - - lastShownApp = app - } else { - app.classList.add('hidden') - li.classList.remove('in-header') - - notInHeader++ - - const a = querySelector('a', app) - - if (appCount > 0 && a.classList.contains('active')) { - lastShownApp.classList.add('hidden') - app.classList.remove('hidden') - notInHeader++ - - li.classList.add('in-header') - } - } - - k++ - } - - // Hack for: - // - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119 - // - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119 - jQuery(menu).undelegate('li:not(#more-apps) > a', 'click') - jQuery(navigation).undelegate('a', 'click') - - const confs = [ - { - items: querySelectorAll('#navigation li'), - icon: 'icon-loading-small' - }, - { - items: querySelectorAll('li:not(#more-apps)', menu), - icon: OCA.Theming && OCA.Theming.inverted ? 'icon-loading-small' : 'icon-loading-small-dark' - }, - ] - - for (let conf of confs) { - for (let item of conf.items) { - item.addEventListener('click', (e) => { - handleMenuClick(e, conf.icon) - }) - } - } - - for (let app of querySelectorAll('#apps li.app-external-site')) { - const appId = app.getAttribute('data-id') - - if (app.classList.contains('in-header')) { - for (let defs of querySelectorAll('svg defs', app)) { - defs.remove() - } - } else { - const svg = querySelector('svg', app) - - if (querySelectorAll('svg defs', app).length > 0) { - continue - } - - const defs = ` - - - - - ` - - svg.innerHTML = defs + svg.innerHTML - - for (let image of querySelectorAll('image', svg)) { - image.setAttribute('filter', `url(#invertMenuMore-${appId})`) - } - - svg.innerHTML = svg.innerHTML.replace(/fecolormatrix/g, 'feColorMatrix') - } - } - - if (notInHeader === 0) { - moreApps.style.display = 'none' - navigation.style.display = 'none' - } else { - moreApps.style.display = 'flex' - } - - menuCache = menu.innerHTML + menu.nextSibling.innerHTML -} - -for (let i = 0; i < 4000; i+= 100) { - setTimeout(updateTopMenu, i) -} - -let resizeTimeout = null; - -window.addEventListener('resize', () => { - if (resizeTimeout !== null) { - clearTimeout(resizeTimeout) - } - - resizeTimeout = setTimeout(updateTopMenu, 100) -}) diff --git a/templates/js/script.php b/templates/js/script.php index f62b7da..1c04466 100644 --- a/templates/js/script.php +++ b/templates/js/script.php @@ -203,12 +203,8 @@ if ($_['always-displayed']) { nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud.nextSibling) - - const topMenuApps = - const topSideMenuApps = - - - + window.topMenuApps = + window.topSideMenuApps =