Compare commits

..

4 Commits

Author SHA1 Message Date
Simon Vieille 711d169d22 Merge pull request 'add check if menu exists before adding event listeners (#210)' (#211) from feature/issue210 into v2.x
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/211
2023-01-21 21:49:02 +01:00
Simon Vieille ddcd10f7b0 release v2.5.2 2023-01-21 21:45:41 +01:00
Simon Vieille ab8b745496 add check if menu exists before adding event listeners (#210) 2023-01-21 21:41:22 +01:00
Simon Vieille 401b351fbd include v2.x in CI 2022-10-11 22:49:02 +02:00
32 changed files with 1628 additions and 1586 deletions
-1
View File
@@ -4,4 +4,3 @@
/releases /releases
/package-lock.json /package-lock.json
!/l10n/.gitkeep !/l10n/.gitkeep
/yarn*.log
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"rules": { "rules": {
"indentation": 2 "indentation": 4
} }
} }
+6 -6
View File
@@ -1,21 +1,21 @@
pipeline: pipeline:
dependencies: dependencies:
image: gitnet.fr/deblan/devenv image: deblan/devenv
commands: commands:
- make dep - npm install
when: when:
event: [tag, push, pull_request] event: [tag, push, pull_request]
branch: [master, develop, feature/*] branch: [master, develop, v2.x, feature/*]
build: build:
image: gitnet.fr/deblan/devenv image: deblan/devenv
commands: commands:
- make build - make npm-build
when: when:
event: [push, pull_request] event: [push, pull_request]
package: package:
image: gitnet.fr/deblan/devenv image: deblan/devenv
volumes: volumes:
- /var/www/html/artifacts:/var/www/html/artifacts - /var/www/html/artifacts:/var/www/html/artifacts
secrets: [app_certificate] secrets: [app_certificate]
+3 -6
View File
@@ -1,11 +1,8 @@
## [Unreleased] ## [Unreleased]
## 3.0.0 ## 2.5.2
### Added ### Fixed
* Add compatibility with NC25 (#136/#135) * add check if menu exists before adding event listeners (#210)
### Removed
* Nextcloud 20-24 are not supported anymore
* AppOrder is not supported anymore
## 2.5.1 ## 2.5.1
### Fixed ### Fixed
+3 -7
View File
@@ -1,15 +1,11 @@
build: dep npm-build:
npm run build npm run build
watch: dep npm-watch:
npm run watch npm run watch
dep:
npm i
npm link @nextcloud/vue || sudo npm link @nextcloud/vue
.ONESHELL: .ONESHELL:
release: build translations release: npm-build translations
if [ -z "$$VERSION" ]; then if [ -z "$$VERSION" ]; then
echo "VERSION required" echo "VERSION required"
exit 1 exit 1
+3 -3
View File
@@ -6,7 +6,7 @@ You can also add and sort custom categories, define apps that must be displayed
This application is rather suitable for instances that activate a lot of applications. This application is rather suitable for instances that activate a lot of applications.
You can customize colors depending of the theme (Dark theme and Breeze Dark). You can customize colors depending of the theme (Dark theme and Breeze Dark). Comptatible with AppOrder.
* [Installation and upgrade](#installation-and-upgrade) * [Installation and upgrade](#installation-and-upgrade)
* [How to contribute?](#how-to-contribute) * [How to contribute?](#how-to-contribute)
@@ -56,9 +56,9 @@ If you are a developer:
* fork the repository * fork the repository
* install an instance of Nextcloud * install an instance of Nextcloud
* go to `apps/` and clone your repository * go to `apps/` and clone your repository
* go to `apps/side_menu` and run `make dep` * go to `apps/side_menu` and run `npm install`
Build javascripts using `make build` (or `make watch` to build them in real time). Build javascripts using `make npm-build` (or `make npm-watch` to build them in real time).
Then commit and create a pull request. Then commit and create a pull request.
+3 -3
View File
@@ -11,7 +11,7 @@ This application is rather suitable for instances that activate a lot of applica
Use the shortcut `Ctrl`+`o` to open and to hide the side menu. Use `tab` to navigate. Use the shortcut `Ctrl`+`o` to open and to hide the side menu. Use `tab` to navigate.
You can customize colors depending of the theme (Dark theme and Breeze Dark). You can customize colors depending of the theme (Dark theme and Breeze Dark). Comptatible with AppOrder.
You can report a bug or request a feature by opening an issue. You can report a bug or request a feature by opening an issue.
@@ -26,7 +26,7 @@ If you like this application and if you want to support the development:
* [Donate with liberapay](https://liberapay.com/deblan) * [Donate with liberapay](https://liberapay.com/deblan)
* [Leave a comment](https://apps.nextcloud.com/apps/side_menu#comments) * [Leave a comment](https://apps.nextcloud.com/apps/side_menu#comments)
]]></description> ]]></description>
<version>3.0.0</version> <version>2.5.2</version>
<licence>agpl</licence> <licence>agpl</licence>
<author mail="contact@deblan.fr" homepage="https://www.deblan.io/">Simon Vieille</author> <author mail="contact@deblan.fr" homepage="https://www.deblan.io/">Simon Vieille</author>
<namespace>SideMenu</namespace> <namespace>SideMenu</namespace>
@@ -46,7 +46,7 @@ If you like this application and if you want to support the development:
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png</screenshot>
<dependencies> <dependencies>
<nextcloud min-version="25" max-version="25"/> <nextcloud min-version="20" max-version="24"/>
<php min-version="7.4"/> <php min-version="7.4"/>
</dependencies> </dependencies>
<settings> <settings>
+58 -57
View File
@@ -16,130 +16,131 @@
*/ */
#side-menu-section input[type="color"] { #side-menu-section input[type="color"] {
width: 100px; width: 100px;
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
} }
#-dropside-menu-section input[type="checkbox"] { #-dropside-menu-section input[type="checkbox"] {
vertical-align: middle; vertical-align: middle;
} }
#side-menu-section input[type="range"] { #side-menu-section input[type="range"] {
vertical-align: middle; vertical-align: middle;
} }
#side-menu-section select { #side-menu-section select {
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
} }
.keyboard-key { .keyboard-key {
padding: 1px 9px; padding: 1px 9px;
margin: 0 2px; margin: 0 2px;
background: #eee; background: #eee;
border: 1px solid #aaa; border: 1px solid #aaa;
color: #555; color: #555;
border-radius: 3px; border-radius: 3px;
} }
.side-menu-display { .side-menu-display {
padding: 10px; padding: 10px;
border: 2px solid transparent; border: 2px solid transparent;
max-width: 100%; max-width: 100%;
cursor: pointer; cursor: pointer;
} }
.side-menu-display.is-active { .side-menu-display.is-active {
border: 2px solid #91cb7f; border: 2px solid #91cb7f;
} }
.info { .info {
margin-top: 8px; margin-top: 8px;
padding: 5px; padding: 5px;
background: #91cb7f; background: #91cb7f;
color: #fff; color: #fff;
border-radius: var(--border-radius); border-radius: var(--border-radius);
} }
#side-menu-section h2 small { #side-menu-section h2 small {
font-size: 11px; font-size: 11px;
font-weight: normal; font-weight: normal;
} }
.side-menu-toggler { .side-menu-toggler {
cursor: pointer; cursor: pointer;
} }
.side-menu-setting-list { .side-menu-setting-list {
margin: 10px 4px 4px 0px; margin: 10px 4px 4px 0px;
} }
.side-menu-setting-list-item { .side-menu-setting-list-item {
padding: 5px 10px; padding: 5px 10px;
border: 1px solid var(--color-border-dark); border: 1px solid var(--color-border-dark);
max-width: 300px; max-width: 300px;
margin: -1px 0 0 0; margin: -1px 0 0 0;
cursor: pointer; cursor: pointer;
} }
.side-menu-setting-list-drop { .side-menu-setting-list-drop {
background: yellow; background: yellow;
border-color: yellow; border-color: yellow;
height: 34px; height: 34px;
} }
.side-menu-setting.arrow { .side-menu-setting.arrow {
color: #ccc; color: #ccc;
padding-right: 5px; padding-right: 5px;
} }
.side-menu-setting-list-item input { .side-menu-setting-list-item input {
min-height: auto; min-height: auto;
margin-top: -1px; margin-top: -1px;
} }
#apps-categories-custom-list select { #apps-categories-custom-list select {
width: 100%; width: 100%;
} }
.side-menu-setting-table { .side-menu-setting-table {
display: table; display: table;
width: 100%; width: 100%;
} }
.side-menu-setting-row { .side-menu-setting-row {
display: table; display: table;
} }
.side-menu-setting-label { .side-menu-setting-label {
display: table-cell; display: table-cell;
width: 400px; width: 400px;
padding-right: 20px; padding-right: 20px;
} }
.side-menu-setting-form { .side-menu-setting-form {
display: table-cell; display: table-cell;
min-width: 300px; min-width: 300px;
} }
.side-menu-setting-label-short { .side-menu-setting-label-short {
width: 300px; width: 300px;
} }
.side-menu-setting-form-long { .side-menu-setting-form-long {
width: 400px; width: 400px;
} }
#side-menu-save-progress { #side-menu-save-progress {
display: inline-block; display: inline-block;
width: 0; width: 0;
height: 15px; height: 15px;
background: #fff; background: #fff;
} }
.btn-reset { .btn-reset {
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
position: relative; position: absolute;
top: -8px; margin-top: 17px;
left: 5px; margin-left: 5px;
} }
+164 -163
View File
@@ -16,325 +16,326 @@
*/ */
#side-menu { #side-menu {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
height: 100vh; height: 100vh;
width: 100%; width: 100%;
max-width: 290px; max-width: 250px;
background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%); background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%);
z-index: 3000; z-index: 3000;
color: var(--side-menu-text-color, #fff); 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; 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; display: none;
} }
#side-menu a { #side-menu a {
transition: 0.2s; transition: 0.2s;
} }
#side-menu.open { #side-menu.open {
display: block; display: block;
} }
#header .side-menu-opener { #header .side-menu-opener {
margin-left: 0px; margin-left: 0px;
} }
.side-menu-settings { .side-menu-settings {
margin-right: 9px; margin-right: 9px;
margin-top: 2px; margin-top: 2px;
float: right; float: right;
line-height: 34px; line-height: 34px;
height: 28px; height: 28px;
display: none; display: none;
} }
.side-menu-settings a { .side-menu-settings a {
color: var(--side-menu-text-color, #fff); color: var(--side-menu-text-color, #fff);
display: block; display: block;
padding: 4px 7px; padding: 4px 7px;
} }
.side-menu-settings:hover a, .side-menu-settings a:active, .side-menu-settings a:focus { .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 { .side-menu-settings img {
vertical-align: bottom; vertical-align: bottom;
margin-left: 3px; margin-left: 3px;
width: 32px; width: 32px;
height: 32px; height: 32px;
} }
#side-menu.open .side-menu-settings { #side-menu.open .side-menu-settings {
display: block; display: block;
} }
.side-menu-opener { .side-menu-opener {
background: var(--side-menu-opener, url('../img/side-menu-opener.svg')); background: var(--side-menu-opener, url('../img/side-menu-opener.svg'));
background-color: transparent !important; background-color: transparent !important;
height: 40px !important; height: 40px !important;
width: 40px !important; width: 40px !important;
border-radius: 0 !important; border-radius: 0 !important;
border: 0 !important; border: 0 !important;
padding-right: 12px !important; padding-right: 12px !important;
padding-left: 12px !important; padding-left: 12px !important;
margin-left: 5px !important; margin-left: 5px !important;
margin-left: 3px !important; margin-left: 3px !important;
} }
.side-menu-opener:active, .side-menu-opener:focus { .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 { .side-menu-closer {
background: url('../img/side-menu-opener-closer.svg'); background: url('../img/side-menu-opener-closer.svg');
display: none; display: none;
} }
#side-menu.hide-opener .side-menu-opener, .side-menu-opener.hide, #side-menu.hide { #side-menu.hide-opener .side-menu-opener, .side-menu-opener.hide, #side-menu.hide {
display: none !important; display: none !important;
} }
.side-menu-apps-list { .side-menu-apps-list {
height: calc(100vh - 150px); height: calc(100vh - 150px);
z-index: 2200; z-index: 2200;
position: fixed; position: fixed;
top: 150px; top: 150px;
width: 100%; width: 100%;
max-width: 290px; max-width: 250px;
overflow: auto; overflow: auto;
} }
.side-menu-app-icon { .side-menu-app-icon {
width: 20px; width: 20px;
vertical-align: middle; vertical-align: top;
margin-top: -4px; margin-right: 10px;
margin-right: 10px; filter: invert(var(--side-menu-icon-invert-filter, 0%));
filter: invert(var(--side-menu-icon-invert-filter, 0%)); opacity: var(--side-menu-icon-opacity, 1);
opacity: var(--side-menu-icon-opacity, 1); }
.side-menu-app-icon svg {
vertical-align: middle;
margin-top: -3px;
}
.side-menu-app-icon .app-icon-notification {
display: none;
} }
.side-menu-app a { .side-menu-app a {
line-height: 30px; line-height: 30px;
color: var(--side-menu-text-color, #fff); color: var(--side-menu-text-color, #fff);
display: block; display: block;
padding: 7px 0 5px 15px; padding: 7px 0 5px 15px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.side-menu-app a:hover, .side-menu-app.active, .side-menu-app a:focus { .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 { .side-menu-logo {
text-align: center; text-align: center;
} }
.side-menu-logo img { .side-menu-logo img {
max-width: 60%; max-width: 60%;
max-height: 100px; max-height: 100px;
} }
.enu-header { .side-menu-header {
height: 150px; height: 150px;
width: 100%; width: 100%;
z-index: 2300; z-index: 2300;
max-width: 290px; max-width: 250px;
position: fixed; position: fixed;
padding-top: 2px; padding-top: 2px;
top: 0; top: 0;
} }
#side-menu.side-menu-with-categories .side-menu-header { #side-menu.side-menu-with-categories .side-menu-header {
max-width: 295px; max-width: 295px;
} }
#side-menu.hide-opener .side-menu-logo { #side-menu.hide-opener .side-menu-logo {
margin-top: 20px; margin-top: 20px;
} }
#side-menu-loader { #side-menu-loader {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
z-index: 3001; z-index: 3001;
} }
#side-menu-loader-bar { #side-menu-loader-bar {
height: 4px; height: 4px;
background: var(--side-menu-loader-color, #0e75ac); background: var(--side-menu-loader-color, #0e75ac);
width: 0; width: 0;
transition-property: width; transition-property: width;
} }
#side-menu.side-menu-big, #side-menu.side-menu-with-categories { #side-menu.side-menu-big, #side-menu.side-menu-with-categories {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
.side-menu-big .side-menu-header, .side-menu-with-categories .side-menu-header { .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 { .side-menu-big .side-menu-apps-list, .side-menu-with-categories .side-menu-apps-list {
height: auto; height: auto;
position: static; position: static;
max-width: 100vw; max-width: 100vw;
overflow: auto; overflow: auto;
} }
.side-menu-big .side-menu-app a, .side-menu-with-categories .side-menu-app a { .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 { .side-menu-categories-wrapper {
padding-bottom: 70px; padding-bottom: 70px;
} }
.side-menu-categories { .side-menu-categories {
max-height: calc(100vh - 50px); max-height: calc(100vh - 50px);
overflow: auto; overflow: auto;
position: relative; position: relative;
top: 50px; top: 50px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
padding: 0 10% 0 10%; padding: 0 10% 0 10%;
} }
.side-menu-category { .side-menu-category {
padding: 10px 20px; padding: 10px 20px;
flex: 1 1 auto; flex: 1 1 auto;
} }
.side-menu-category-title { .side-menu-category-title {
padding-left: 10px; padding-left: 10px;
color: var(--side-menu-text-color, #fff); color: var(--side-menu-text-color, #fff);
} }
.side-menu-loader { .side-menu-loader {
text-align: center; text-align: center;
} }
.side-menu-loader svg { .side-menu-loader svg {
width: 38px; width: 38px;
margin: auto; margin: auto;
stroke: var(--side-menu-text-color, #fff); stroke: var(--side-menu-text-color, #fff);
} }
.side-menu-with-categories .side-menu-app-icon, .side-menu-big .side-menu-app-icon { .side-menu-with-categories .side-menu-app-icon, .side-menu-big .side-menu-app-icon {
vertical-align: middle; vertical-align: middle;
margin-top: -2px; margin-top: -2px;
}
.side-menu-always-displayed #header,
.side-menu-always-displayed body {
width: calc(100% - 50px) !important;
} }
.side-menu-always-displayed body { .side-menu-always-displayed body {
width: calc(100% - 50px) !important; position: absolute;
} left: 50px;
.side-menu-always-displayed body {
position: absolute;
left: 50px;
} }
.side-menu-always-displayed #side-menu { .side-menu-always-displayed #side-menu {
display: block; display: block;
} }
.side-menu-always-displayed .side-menu-apps-list { .side-menu-always-displayed .side-menu-apps-list {
height: calc(100vh - 49px); height: calc(100vh - 49px);
top: 49px; top: 49px;
overflow: hidden; overflow: hidden;
} }
.side-menu-always-displayed .side-menu-apps-list:hover { .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,
.side-menu-always-displayed .side-menu-header, .side-menu-always-displayed .side-menu-header,
.side-menu-always-displayed .side-menu-apps-list { .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 #side-menu .side-menu-app-text,
.side-menu-always-displayed #header .side-menu-opener, .side-menu-always-displayed #header .side-menu-opener,
.side-menu-always-displayed .side-menu-logo { .side-menu-always-displayed .side-menu-logo {
display: none; display: none;
} }
.side-menu-always-displayed #side-menu .side-menu-header { .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-always-displayed #side-menu.open .side-menu-apps-list, .side-menu-always-displayed #side-menu.open .side-menu-apps-list,
.side-menu-always-displayed #side-menu.open .side-menu-header { .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 { .side-menu-always-displayed #side-menu.open .side-menu-app-text {
display: inline; display: inline;
} }
.side-menu-always-displayed .app-navigation--close { .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 { #side-menu.side-menu-with-categories {
max-width: 290px; max-width: 290px;
height: 100vh; height: 100vh;
} }
.side-menu-with-categories .side-menu-categories { .side-menu-with-categories .side-menu-categories {
display: block; display: block;
padding: 0; padding: 0;
} }
.side-menu-with-categories .side-menu-category { .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 { .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) { @media screen and (max-width: 1024px) {
#side-menu.side-menu-big { #side-menu.side-menu-big {
max-width: 290px; max-width: 290px;
height: 100vh; height: 100vh;
} }
.side-menu-categories { .side-menu-categories {
display: block; display: block;
padding: 0; padding: 0;
} }
.side-menu-category { .side-menu-category {
padding: 10px 0; padding: 10px 0;
} }
} }
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
.side-menu-closer { .side-menu-closer {
display: block; display: block;
float: right; float: right;
margin-right: 9px; margin-right: 9px;
} }
.side-menu-big .side-menu-header { .side-menu-big .side-menu-header {
max-width: 100%; max-width: 100%;
} }
} }
+11 -14
View File
@@ -2,15 +2,17 @@
"license": "agpl", "license": "agpl",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "NODE_ENV=production ./node_modules/.bin/webpack-cli --progress --config webpack.js", "build": "NODE_ENV=production webpack --progress --config webpack.js",
"dev": "NODE_ENV=development ./node_modules/.bin/webpack-cli --progress --config webpack.js", "dev": "NODE_ENV=development webpack --progress --config webpack.js",
"watch": "NODE_ENV=development ./node_modules/.bin/webpack-cli --progress --watch --config webpack.js", "watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
"lint": "./node_modules/.bin/eslint --ext .js,.vue src", "lint": "eslint --ext .js,.vue src",
"lint:fix": "./node_modules/.bin/eslint --ext .js,.vue src --fix", "lint:fix": "eslint --ext .js,.vue src --fix",
"stylelint": "./node_modules/.bin/stylelint src", "stylelint": "stylelint src",
"stylelint:fix": "./node_modules/.bin/stylelint src --fix" "stylelint:fix": "stylelint src --fix"
}, },
"dependencies": { "dependencies": {
"@nextcloud/axios": "^1.8.0",
"@nextcloud/vue": "^1.5.0",
"axios": "^0.24.0", "axios": "^0.24.0",
"trim": "^1.0.1", "trim": "^1.0.1",
"vue": "^2.6.11" "vue": "^2.6.11"
@@ -25,12 +27,8 @@
"@babel/core": "^7.9.0", "@babel/core": "^7.9.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.9.0", "@babel/preset-env": "^7.9.0",
"@nextcloud/axios": "^1.8.0",
"@nextcloud/browserslist-config": "^1.0.0", "@nextcloud/browserslist-config": "^1.0.0",
"@nextcloud/eslint-config": "^8.1.2", "@nextcloud/eslint-config": "^8.1.2",
"@nextcloud/initial-state": "^2.0.0",
"@nextcloud/l10n": "^1.6.0",
"@nextcloud/vue": "^7.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"css-loader": "^3.4.2", "css-loader": "^3.4.2",
@@ -52,9 +50,8 @@
"stylelint-scss": "^4.0.0", "stylelint-scss": "^4.0.0",
"stylelint-webpack-plugin": "^3.3.0", "stylelint-webpack-plugin": "^3.3.0",
"url-loader": "^4.0.0", "url-loader": "^4.0.0",
"vue-loader": "^15", "vue-loader": "^15.9.1",
"vue-style-loader": "^4.1.3", "vue-template-compiler": "^2.6.11",
"vue-template-compiler": "^2.7.13",
"webpack": "^5.0.0", "webpack": "^5.0.0",
"webpack-cli": "^4.0.0", "webpack-cli": "^4.0.0",
"webpack-merge": "^4.2.2", "webpack-merge": "^4.2.2",
+145 -145
View File
@@ -15,168 +15,168 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<div> <div>
<ul class="side-menu-setting-list"> <ul class="side-menu-setting-list">
<li v-for="item in values" class="side-menu-setting-list-item" v-on:click="showEditForm(item)"> <li v-for="item in values" class="side-menu-setting-list-item" v-on:click="showEditForm(item)">
<span v-text="item.en"></span> <span v-text="item.en"></span>
</li> </li>
</ul> </ul>
<NcActions> <Actions>
<NcActionButton @click="showAddForm" icon="icon-add"></NcActionButton> <ActionButton @click="showAddForm" icon="icon-add"></ActionButton>
</NcActions> </Actions>
<NcModal v-if="addForm" @close="hideAddForm"> <Modal v-if="addForm" @close="hideAddForm">
<div class="modal__content"> <div class="modal__content">
<div v-for="lang in langs"> <div v-for="lang in langs">
<span class="lang" v-text="lang"></span> <span class="lang" v-text="lang"></span>
<input type="text" v-model="newValue[lang]" required> <input type="text" v-model="newValue[lang]" required>
</div> </div>
<NcActions> <Actions>
<NcActionButton @click="saveAdd" icon="icon-checkmark"></NcActionButton> <ActionButton @click="saveAdd" icon="icon-checkmark"></ActionButton>
</NcActions> </Actions>
</div> </div>
</NcModal> </Modal>
<NcModal v-if="editForm" @close="hideEditForm"> <Modal v-if="editForm" @close="hideEditForm">
<div class="modal__content"> <div class="modal__content">
<div v-for="lang in langs"> <div v-for="lang in langs">
<span class="lang" v-text="lang"></span> <span class="lang" v-text="lang"></span>
<input type="text" v-model="editValue[lang]" required> <input type="text" v-model="editValue[lang]" required>
</div> </div>
<div class="pull-right"> <div class="pull-right">
<NcActions> <Actions>
<NcActionButton @click="removeEdit" icon="icon-delete"></NcActionButton> <ActionButton @click="removeEdit" icon="icon-delete"></ActionButton>
</NcActions> </Actions>
</div> </div>
<NcActions> <Actions>
<NcActionButton @click="saveEdit" icon="icon-checkmark"></NcActionButton> <ActionButton @click="saveEdit" icon="icon-checkmark"></ActionButton>
</NcActions> </Actions>
</div> </div>
</NcModal> </Modal>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.modal__content { .modal__content {
width: 200px; width: 200px;
padding: 10px; padding: 10px;
} }
.modal__content .lang { .modal__content .lang {
width: 60px; width: 60px;
display: inline-block; display: inline-block;
padding: 4px; padding: 4px;
box-sizing: border-box; box-sizing: border-box;
} }
.modal__content input[type=text] { .modal__content input[type=text] {
width: calc(100% - 85px); width: calc(100% - 85px);
display: inline-block; display: inline-block;
margin: 0; margin: 0;
} }
.pull-right { .pull-right {
float: right; float: right;
} }
</style> </style>
<script> <script>
import NcModal from '@nextcloud/vue/dist/Components/NcModal' import Modal from '@nextcloud/vue/dist/Components/Modal'
import NcActions from '@nextcloud/vue/dist/Components/NcActions' import Actions from '@nextcloud/vue/dist/Components/Actions'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton' import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
export default { export default {
name: 'AdminCategoriesCustom', name: 'AdminCategoriesCustom',
components: { components: {
NcModal, Modal,
NcActions, Actions,
NcActionButton, ActionButton,
}, },
data() { data() {
return { return {
input: null, input: null,
values: [], values: [],
langs: [], langs: [],
addForm: false, addForm: false,
editForm: false, editForm: false,
newValue: {}, newValue: {},
editValue: {}, editValue: {},
}
},
methods: {
init() {
this.values = JSON.parse(this.input.value)
this.langs = JSON.parse(this.input.getAttribute('data-langs'))
},
update() {
this.input.value = JSON.stringify(this.values)
},
showAddForm() {
this.newValue = {id: 'cat' + Math.random().toString().replace('0.', '')}
this.addForm = true
},
showEditForm(value) {
this.editValue = {id: value.id}
for (let i of this.langs) {
this.editValue[i] = typeof value[i] !== 'undefined' ? value[i] : ''
}
this.editForm = true
},
saveAdd() {
for (let i of this.langs) {
if (!this.newValue[i] || /^\s*$/.test(this.newValue[i])) {
return
}
}
this.values.push(this.newValue)
this.update()
this.hideAddForm()
this.newValue = {}
},
saveEdit() {
for (let i of this.langs) {
if (!this.editValue[i] || /^\s*$/.test(this.editValue[i])) {
return
}
}
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values[i] = this.editValue
}
}
this.update()
this.hideEditForm()
},
removeEdit() {
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values.splice(i, 1);
}
}
this.update()
this.hideEditForm()
},
hideAddForm() {
this.addForm = false
},
hideEditForm() {
this.editForm = false
},
},
mounted() {
this.input = document.querySelector('input[name="categories-custom"]')
this.init()
} }
},
methods: {
init() {
this.values = JSON.parse(this.input.value)
this.langs = JSON.parse(this.input.getAttribute('data-langs'))
},
update() {
this.input.value = JSON.stringify(this.values)
},
showAddForm() {
this.newValue = {id: 'cat' + Math.random().toString().replace('0.', '')}
this.addForm = true
},
showEditForm(value) {
this.editValue = {id: value.id}
for (let i of this.langs) {
this.editValue[i] = typeof value[i] !== 'undefined' ? value[i] : ''
}
this.editForm = true
},
saveAdd() {
for (let i of this.langs) {
if (!this.newValue[i] || /^\s*$/.test(this.newValue[i])) {
return
}
}
this.values.push(this.newValue)
this.update()
this.hideAddForm()
this.newValue = {}
},
saveEdit() {
for (let i of this.langs) {
if (!this.editValue[i] || /^\s*$/.test(this.editValue[i])) {
return
}
}
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values[i] = this.editValue
}
}
this.update()
this.hideEditForm()
},
removeEdit() {
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values.splice(i, 1);
}
}
this.update()
this.hideEditForm()
},
hideAddForm() {
this.addForm = false
},
hideEditForm() {
this.editForm = false
},
},
mounted() {
this.input = document.querySelector('input[name="categories-custom"]')
this.init()
}
} }
</script> </script>
-296
View File
@@ -1,296 +0,0 @@
<!--
- @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<nav class="app-menu show">
<ul class="app-menu-main" v-if="apps !== null">
<li v-for="app in mainAppList()"
:key="app.id"
:data-app-id="app.id"
class="app-menu-entry"
:class="{ 'app-menu-entry__active': app.active }">
<a :href="app.href"
:class="{ 'has-unread': app.unread > 0 }"
:aria-label="appLabel(app)"
:aria-current="app.active ? 'page' : false">
<img :src="app.icon" alt="">
<div class="app-menu-entry--label">
{{ app.name }}
<span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span>
</div>
</a>
</li>
</ul>
<NcActions class="app-menu-more" :aria-label="t('core', 'More apps')" v-if="apps !== null">
<NcActionLink v-for="app in popoverAppList()"
:key="app.id"
:aria-label="appLabel(app)"
:aria-current="app.active ? 'page' : false"
:href="app.href"
class="app-menu-popover-entry">
<template #icon>
<div class="app-icon" :class="{ 'has-unread': app.unread > 0 }">
<img :src="app.icon" alt="">
</div>
</template>
{{ app.name }}
<span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span>
</NcActionLink>
</NcActions>
</nav>
</template>
<script>
import { loadState } from '@nextcloud/initial-state'
import { NcActions, NcActionLink } from '@nextcloud/vue'
export default {
name: 'AppMenu',
components: {
NcActions, NcActionLink,
},
data() {
return {
apps: null,
appLimit: 0,
observer: null,
}
},
mounted() {
const ncApps = loadState('core', 'apps', {})
this.apps = {}
Array.from(window.topMenuApps).forEach((id) => {
if (ncApps.hasOwnProperty(id)) {
this.apps[id] = ncApps[id]
}
})
this.observer = new ResizeObserver(this.resize)
this.observer.observe(this.$el)
this.resize()
},
beforeDestroy() {
this.observer.disconnect()
},
methods: {
appLabel() {
return (app) => app.name
+ (app.active ? ' (' + t('core', 'Currently open') + ')' : '')
+ (app.unread > 0 ? ' (' + n('core', '{count} notification', '{count} notifications', app.unread, { count: app.unread }) + ')' : '')
},
appList() {
return Object.values(this.apps)
},
mainAppList() {
return this.appList().slice(0, this.appLimit)
},
popoverAppList() {
return this.appList().slice(this.appLimit)
},
setNavigationCounter(id, counter) {
this.$set(this.apps[id], 'unread', counter)
},
resize() {
const availableWidth = this.$el.offsetWidth
let appCount = Math.floor(availableWidth / 50) - 1
const popoverAppCount = this.appList.length - appCount
if (popoverAppCount === 1) {
appCount--
}
if (appCount < 1) {
appCount = 0
}
this.appLimit = appCount
},
},
}
</script>
<style lang="scss" scoped>
$header-icon-size: 20px;
.app-menu {
width: 100%;
display: flex;
flex-shrink: 1;
flex-wrap: wrap;
}
.app-menu-main {
display: flex;
flex-wrap: nowrap;
.app-menu-entry {
width: 50px;
height: 50px;
position: relative;
display: flex;
opacity: .7;
&.app-menu-entry__active {
opacity: 1;
&::before {
content: " ";
position: absolute;
pointer-events: none;
border-bottom-color: var(--color-main-background);
transform: translateX(-50%);
width: 12px;
height: 5px;
border-radius: 3px;
background-color: var(--color-primary-text);
left: 50%;
bottom: 6px;
display: block;
transition: all 0.1s ease-in-out;
opacity: 1;
}
.app-menu-entry--label {
font-weight: bold;
}
}
a {
width: calc(100% - 4px);
height: calc(100% - 4px);
margin: 2px;
color: var(--color-primary-text);
position: relative;
}
img {
transition: margin 0.1s ease-in-out;
width: $header-icon-size;
height: $header-icon-size;
padding: calc((100% - $header-icon-size) / 2);
filter: var(--primary-invert-if-bright);
}
.app-menu-entry--label {
opacity: 0;
position: absolute;
font-size: 12px;
color: var(--color-primary-text);
text-align: center;
bottom: -5px;
left: 50%;
display: block;
min-width: 100%;
transform: translateX(-50%);
transition: all 0.1s ease-in-out;
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
}
&:hover,
&:focus-within {
opacity: 1;
.app-menu-entry--label {
opacity: 1;
font-weight: bold;
font-size: 14px;
bottom: 0;
width: auto;
overflow: visible;
}
}
}
// Show labels
&:hover,
&:focus-within,
.app-menu-entry:hover,
.app-menu-entry:focus {
opacity: 1;
img {
margin-top: -6px;
}
.app-menu-entry--label {
opacity: 1;
bottom: 0;
}
&::before, .app-menu-entry::before {
opacity: 0;
}
}
}
::v-deep .app-menu-more .button-vue--vue-tertiary {
color: var(--color-primary-text);
opacity: .7;
margin: 3px;
&:hover {
opacity: 1;
background-color: transparent !important;
}
&:focus-visible {
opacity: 1;
background-color: transparent !important;
border-radius: var(--border-radius);
outline: none;
box-shadow: 0 0 0 2px var(--color-primary-text);
}
}
.app-menu-popover-entry {
.app-icon {
position: relative;
height: 44px;
&.has-unread::after {
background-color: var(--color-main-text);
}
img {
filter: var(--background-invert-if-bright);
width: $header-icon-size;
height: $header-icon-size;
padding: calc((50px - $header-icon-size) / 2);
}
}
}
.has-unread::after {
content: "";
width: 8px;
height: 8px;
background-color: var(--color-primary-text);
border-radius: 50%;
position: absolute;
display: block;
top: 10px;
right: 10px;
}
.unread-counter {
display: none;
}
</style>
+2 -2
View File
@@ -15,11 +15,11 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<button class="side-menu-opener side-menu-closer"></button> <button class="side-menu-opener side-menu-closer"></button>
</template> </template>
<script> <script>
export default { export default {
name: 'CloserButton', name: 'CloserButton',
} }
</script> </script>
+19 -19
View File
@@ -15,28 +15,28 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<div class="side-menu-loader"> <div class="side-menu-loader">
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg"> <svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2"> <g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/> <circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18"> <path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
type="rotate" type="rotate"
from="0 18 18" from="0 18 18"
to="360 18 18" to="360 18 18"
dur="1s" dur="1s"
repeatCount="indefinite"/> repeatCount="indefinite"/>
</path> </path>
</g> </g>
</g> </g>
</svg> </svg>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Loader', name: 'Loader',
} }
</script> </script>
+20 -20
View File
@@ -15,30 +15,30 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<div v-bind:class="classes"> <div v-bind:class="classes">
<a v-if="link !== null" v-bind:href="link"> <a v-if="link !== null" v-bind:href="link">
<img v-bind:src="image" alt="Logo"> <img v-bind:src="image" alt="Logo">
</a> </a>
<img v-else v-bind:src="image" alt="Logo"> <img v-else v-bind:src="image" alt="Logo">
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Logo', name: 'Logo',
props: { props: {
image: { image: {
type: String, type: String,
required: true required: true
},
link: {
type: String,
required: false
},
classes: {
type: Object,
required: true
},
}, },
link: {
type: String,
required: false
},
classes: {
type: Object,
required: true
},
},
} }
</script> </script>
+2 -2
View File
@@ -15,11 +15,11 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<button class="side-menu-opener"></button> <button class="side-menu-opener"></button>
</template> </template>
<script> <script>
export default { export default {
name: 'OpenerButton', name: 'OpenerButton',
} }
</script> </script>
-20
View File
@@ -1,20 +0,0 @@
const createElement = require('./lib/createElement')
const PageLoader = () => {
const pageLoader = createElement('div', {id: 'side-menu-loader'})
const pageLoaderBar = createElement('div', {id: 'side-menu-loader-bar'})
pageLoader.appendChild(pageLoaderBar)
document.querySelector('body').appendChild(pageLoader)
let pageLoaderValue = 0
window.addEventListener('beforeunload', () => {
setInterval(() => {
pageLoaderBar.style.width = pageLoaderValue.toString() + '%'
pageLoaderValue = Math.min(pageLoaderValue + .2, 100)
}, 25)
})
}
module.exports = PageLoader
+22 -22
View File
@@ -15,33 +15,33 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<div class="side-menu-settings"> <div class="side-menu-settings">
<a v-bind:href="href"> <a v-bind:href="href">
{{ label }} {{ label }}
<span class="avatardiv avatardiv-shown"> <span class="avatardiv avatardiv-shown">
<img v-bind:src="avatar"> <img v-bind:src="avatar" v-bind:alt="name" v-bind:title="name">
</span> </span>
</a> </a>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'SettingsButton', name: 'SettingsButton',
props: { props: {
label: { label: {
type: String, type: String,
required: true required: true
},
href: {
type: String,
required: true
},
avatar: {
type: String,
required: true
},
}, },
href: {
type: String,
required: true
},
avatar: {
type: String,
required: true
},
},
} }
</script> </script>
+18 -39
View File
@@ -16,56 +16,35 @@
*/ */
import Vue from 'vue' import Vue from 'vue'
import AppMenu from './AppMenu.vue'
import SideMenu from './SideMenu.vue' import SideMenu from './SideMenu.vue'
import SideMenuBig from './SideMenuBig.vue' import SideMenuBig from './SideMenuBig.vue'
import SideMenuWithCategories from './SideMenuWithCategories.vue' import SideMenuWithCategories from './SideMenuWithCategories.vue'
import PageLoader from './PageLoader'
import SMcreateElement from './lib/createElement'
Vue.prototype.OC = OC Vue.prototype.OC = OC
Vue.prototype.t = OC.L10N.translate
window.SMcreateElement = SMcreateElement
window.PageLoader = PageLoader
const mountSideMenuComponent = () => { const mountSideMenuComponent = () => {
const container = document.querySelector('#side-menu') const sideMenuContainer = document.querySelector('#side-menu')
if (!container) { if (sideMenuContainer) {
return window.setTimeout(mountSideMenuComponent, 50) let component
}
const component = (() => { if (sideMenuContainer.getAttribute('data-bigmenu')) {
if (container.getAttribute('data-bigmenu')) { component = SideMenuBig
return SideMenuBig } else if(sideMenuContainer.getAttribute('data-sidewithcategories')) {
} else if(container.getAttribute('data-sidewithcategories')) { component = SideMenuWithCategories
return 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'))
} else { } else {
return SideMenu window.setTimeout(mountSideMenuComponent, 50)
} }
})()
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() mountSideMenuComponent()
mountAppMenu()
+133 -77
View File
@@ -15,41 +15,40 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<div id="side-menu"> <div id="side-menu">
<div class="side-menu-header"> <div class="side-menu-header">
<SettingsButton <SettingsButton
v-if="settings" v-if="settings"
v-bind:href="settings.href" v-bind:href="settings.href"
v-bind:label="settings.name" v-bind:label="settings.name"
v-bind:avatar="settings.avatar" /> v-bind:avatar="settings.avatar" />
<OpenerButton /> <OpenerButton />
<Logo <Logo
v-if="!avatar && logo" v-bind:classes="{'side-menu-logo': true, 'avatardiv': false}" v-if="!avatar && logo" v-bind:classes="{'side-menu-logo': true, 'avatardiv': false}"
v-bind:image="logo" v-bind:image="logo"
v-bind:link="logoLink" v-bind:link="logoLink"
/> />
<Logo <Logo
v-if="avatar" v-bind:classes="{'side-menu-logo': true, 'avatardiv': true}" v-if="avatar" v-bind:classes="{'side-menu-logo': true, 'avatardiv': true}"
v-bind:image="avatar" v-bind:image="avatar"
v-bind:link="logoLink" v-bind:link="logoLink"
/> />
</div>
<ul class="side-menu-apps-list">
<SideMenuApp
v-for="app in apps"
v-bind:classes="{'side-menu-app': true, 'active': app.active}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
/>
</ul>
</div> </div>
<ul class="side-menu-apps-list">
<SideMenuApp
v-for="(app, key) in apps"
v-bind:classes="{'side-menu-app': true, 'active': app.active}"
v-bind:key="key"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</template> </template>
<script> <script>
@@ -59,54 +58,111 @@ import OpenerButton from './OpenerButton'
import SettingsButton from './SettingsButton' import SettingsButton from './SettingsButton'
import SideMenuApp from './SideMenuApp' import SideMenuApp from './SideMenuApp'
import Logo from './Logo' import Logo from './Logo'
import { loadState } from '@nextcloud/initial-state'
export default { export default {
name: 'SideMenu', name: 'SideMenu',
components: { components: {
SettingsButton, SettingsButton,
OpenerButton, OpenerButton,
SideMenuApp, SideMenuApp,
Logo, Logo,
}, },
data() { data() {
return { return {
apps: [], apps: [],
logo: null, logo: null,
logoLink: null, logoLink: null,
avatar: null, avatar: null,
forceLightIcon: false, forceLightIcon: false,
targetBlankApps: [], targetBlankApps: [],
settings: null, settings: null,
}
},
methods: {
retrieveApps() {
this.apps = []
const links = document.querySelectorAll('#appmenu a')
const menu = document.querySelector('#appmenu')
let menuIsHidden = true
if (menu) {
menuIsHidden = window.getComputedStyle(menu, null).getPropertyValue('display') === 'none'
}
for (let element of links) {
let href = element.getAttribute('href')
let parent = element.parentNode
if (!parent) {
continue
}
let dataId = parent.getAttribute('data-id')
dataId = dataId !== null ? dataId : ''
if (!parent.classList.contains('app-top-side-menu') && !parent.classList.contains('app-hidden') && !menuIsHidden) {
continue
}
if (href !== '#') {
let svg = element.querySelector('svg').outerHTML
svg = svg
.replace(/(height|width)="20"/, '')
.replace('id="invertMenuMain', 'id="invertSideMenu')
.replace('url(#invertMenuMain', 'url(#invertSideMenu')
if (this.forceLightIcon) {
svg = svg.replace(/filter="url[^"]+"/, '')
}
this.apps.push({
id: dataId,
href: href,
name: trim(element.querySelector('span').innerHTML),
icon: svg,
active: element.classList.contains('active')
})
}
}
(function(apps) {
window.setTimeout(function() {
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
}, 1000)
})(this.apps)
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.forceLightIcon = config['force-light-icon']
that.avatar = config['avatar']
that.logo = config['logo']
that.logoLink = config['logo-link']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
const menu = document.querySelector('#appmenu')
if (menu) {
const config = {attributes: true, childList: true, subtree: true}
const observer = new MutationObserver(this.retrieveApps)
observer.observe(menu, config)
}
} }
},
methods: {
retrieveApps() {
this.apps = loadState('core', 'apps', {})
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: this.apps},
}))
},
retrieveConfig() {
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then((response) => {
const config = response.data
this.targetBlankApps = config['target-blank-apps']
this.forceLightIcon = config['force-light-icon']
this.avatar = config['avatar']
this.logo = config['logo']
this.logoLink = config['logo-link']
this.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
}
} }
</script> </script>
+28 -28
View File
@@ -15,38 +15,38 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<li v-bind:class="classes"> <li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label"> <a v-bind:href="href" :target="target" v-bind:title="label">
<img class="side-menu-app-icon" v-bind:src="icon" v-bind:alt="label" /> <span class="side-menu-app-icon" v-html="icon"></span>
<span class="side-menu-app-text" v-text="label"></span> <span class="side-menu-app-text" v-text="label"></span>
</a> </a>
</li> </li>
</template> </template>
<script> <script>
export default { export default {
name: 'SideMenuApp', name: 'SideMenuApp',
props: { props: {
label: { label: {
type: String, type: String,
required: true required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
}, },
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
},
} }
</script> </script>
+94 -95
View File
@@ -15,42 +15,41 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<div id="side-menu" class="side-menu-big"> <div id="side-menu" class="side-menu-big">
<div class="side-menu-header"> <div class="side-menu-header">
<CloserButton /> <CloserButton />
<SettingsButton <SettingsButton
v-if="settings" v-if="settings"
v-bind:href="settings.href" v-bind:href="settings.href"
v-bind:label="settings.name" v-bind:label="settings.name"
v-bind:avatar="settings.avatar" v-bind:avatar="settings.avatar"
/>
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="(category, key) in items" v-bind:key="key">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-bind:key="appId"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/> />
</ul>
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="category in items">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
@@ -62,69 +61,69 @@ import Loader from './Loader'
import SideMenuBigApp from './SideMenuBigApp' import SideMenuBigApp from './SideMenuBigApp'
export default { export default {
name: 'SideMenuBig', name: 'SideMenuBig',
components: { components: {
SettingsButton, SettingsButton,
OpenerButton, OpenerButton,
CloserButton, CloserButton,
Loader, Loader,
SideMenuBigApp, SideMenuBigApp,
}, },
data() { data() {
return { return {
items: [], items: [],
activeApp: null, activeApp: null,
targetBlank: false, targetBlank: false,
targetBlankApps: [], targetBlankApps: [],
settings: null, settings: null,
}
},
methods: {
retrieveApps() {
this.apps = []
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then(function(response) {
that.items = response.data.items
let apps = []
for (let category of that.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
} }
},
methods: {
retrieveApps() {
this.apps = []
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then(function(response) {
that.items = response.data.items
let apps = []
for (let category of that.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
}
} }
</script> </script>
+28 -28
View File
@@ -15,38 +15,38 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<li v-bind:class="classes"> <li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label"> <a v-bind:href="href" :target="target" v-bind:title="label">
<img class="side-menu-app-icon" v-bind:src="icon" v-bind:alt="label" /> <img class="side-menu-app-icon" v-bind:src="icon" v-bind:alt="label" />
<span class="side-menu-app-text" v-text="label"></span> <span class="side-menu-app-text" v-text="label"></span>
</a> </a>
</li> </li>
</template> </template>
<script> <script>
export default { export default {
name: 'SideMenuBigApp', name: 'SideMenuBigApp',
props: { props: {
label: { label: {
type: String, type: String,
required: true required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
}, },
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
},
} }
</script> </script>
+93 -94
View File
@@ -15,40 +15,39 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<template> <template>
<div id="side-menu" class="side-menu-with-categories"> <div id="side-menu" class="side-menu-with-categories">
<div class="side-menu-header"> <div class="side-menu-header">
<SettingsButton <SettingsButton
v-if="settings" v-if="settings"
v-bind:href="settings.href" v-bind:href="settings.href"
v-bind:label="settings.name" v-bind:label="settings.name"
v-bind:avatar="settings.avatar" v-bind:avatar="settings.avatar"
/>
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="(category, key) in items" v-bind:key="key">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-bind:key="appId"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/> />
</ul>
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="category in items">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-text="category.name"></h2>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
@@ -60,69 +59,69 @@ import Loader from './Loader'
import SideMenuBigApp from './SideMenuBigApp' import SideMenuBigApp from './SideMenuBigApp'
export default { export default {
name: 'SideMenuWithCategories', name: 'SideMenuWithCategories',
components: { components: {
SettingsButton, SettingsButton,
OpenerButton, OpenerButton,
CloserButton, CloserButton,
Loader, Loader,
SideMenuBigApp, SideMenuBigApp,
}, },
data() { data() {
return { return {
items: [], items: [],
activeApp: null, activeApp: null,
targetBlank: false, targetBlank: false,
targetBlankApps: [], targetBlankApps: [],
settings: null, settings: null,
}
},
methods: {
retrieveApps() {
this.apps = []
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then(function(response) {
that.items = response.data.items
let apps = []
for (let category of that.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
} }
},
methods: {
retrieveApps() {
this.apps = []
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then(function(response) {
that.items = response.data.items
let apps = []
for (let category of that.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
}
} }
</script> </script>
+185 -185
View File
@@ -26,235 +26,235 @@ let elements = []
const selector = '#side-menu-message' const selector = '#side-menu-message'
const userConfig = (name, value, callbacks) => { const userConfig = (name, value, callbacks) => {
const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet') const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet')
const formData = [] const formData = []
formData.push('name=' + encodeURIComponent(name)) formData.push('name=' + encodeURIComponent(name))
formData.push('value=' + encodeURIComponent(value)) formData.push('value=' + encodeURIComponent(value))
fetch(url, { fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
}, },
body: formData.join('&') body: formData.join('&')
}) })
.then(callbacks.success) .then(callbacks.success)
.catch(callbacks.error) .catch(callbacks.error)
} }
const appConfig = (name, value, callbacks) => { const appConfig = (name, value, callbacks) => {
OCP.AppConfig.setValue('side_menu', name, value, callbacks) OCP.AppConfig.setValue('side_menu', name, value, callbacks)
} }
const saveSettings = (key) => { const saveSettings = (key) => {
const element = elements[key] const element = elements[key]
if (!element) { if (!element) {
return return
}
let value
let name
if (element.hasAttribute('data-checkbox')) {
name = element.getAttribute('data-name')
value = []
const inputs = document.querySelectorAll('input[name="' + name + '[]"]:checked')
for (let input of inputs) {
value.push(input.value)
} }
value = JSON.stringify(value) let value
} else { let name
name = element.getAttribute('name')
value = element.value
}
const size = elements.length if (element.hasAttribute('data-checkbox')) {
name = element.getAttribute('data-name')
value = []
if (name === 'cache') { const inputs = document.querySelectorAll('input[name="' + name + '[]"]:checked')
++value
}
const progress = document.querySelector('#side-menu-save-progress') for (let input of inputs) {
value.push(input.value)
}
progress.style.width = '40px'; value = JSON.stringify(value)
progress.style.marginLeft = '5px'; } else {
name = element.getAttribute('name')
const callbacks = { value = element.value
success: () => {
const percent = parseInt((key + 1) * 100 / size);
progress.setAttribute('value', percent)
if (key < size - 1) {
saveSettings(key + 1)
} else {
location.reload()
}
},
error: () => {
OC.msg.finishedError(selector, t('side_menu', 'Error while saving "' + element + '"'))
} }
}
if (element.hasAttribute('data-personal')) { const size = elements.length
userConfig(name, value, callbacks)
} else { if (name === 'cache') {
appConfig(name, value, callbacks) ++value
} }
const progress = document.querySelector('#side-menu-save-progress')
progress.style.width = '40px';
progress.style.marginLeft = '5px';
const callbacks = {
success: () => {
const percent = parseInt((key + 1) * 100 / size);
progress.setAttribute('value', percent)
if (key < size - 1) {
saveSettings(key + 1)
} else {
location.reload()
}
},
error: () => {
OC.msg.finishedError(selector, t('side_menu', 'Error while saving "' + element + '"'))
}
}
if (element.hasAttribute('data-personal')) {
userConfig(name, value, callbacks)
} else {
appConfig(name, value, callbacks)
}
} }
const elementToggler = (element) => { const elementToggler = (element) => {
let display = 'none' let display = 'none'
if (window.getComputedStyle(element).display === 'none') { if (window.getComputedStyle(element).display === 'none') {
display = 'block' display = 'block'
} }
element.style.display = display element.style.display = display
} }
const updateAppsCategoriesCustom = () => { const updateAppsCategoriesCustom = () => {
let values = {} let values = {}
for (let item of document.querySelectorAll('.apps-categories-custom')) { for (let item of document.querySelectorAll('.apps-categories-custom')) {
let app = item.getAttribute('data-app') let app = item.getAttribute('data-app')
let value = item.value let value = item.value
if (value) { if (value) {
values[app] = value values[app] = value
}
} }
}
document.querySelector('#apps-categories-custom').value = JSON.stringify(values) document.querySelector('#apps-categories-custom').value = JSON.stringify(values)
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
$('*[data-toggle="tooltip"]').tooltip(); $('*[data-toggle="tooltip"]').tooltip();
if (document.querySelector('#side-menu-categories-custom')) { if (document.querySelector('#side-menu-categories-custom')) {
const View = Vue.extend(AdminCategoriesCustom) const View = Vue.extend(AdminCategoriesCustom)
const adminCategoriesCustom = new View({}) const adminCategoriesCustom = new View({})
adminCategoriesCustom.$mount('#side-menu-categories-custom') adminCategoriesCustom.$mount('#side-menu-categories-custom')
} }
elements = document.querySelectorAll('.side-menu-setting') elements = document.querySelectorAll('.side-menu-setting')
document.querySelector('#side-menu-save').addEventListener('click', (event) => { document.querySelector('#side-menu-save').addEventListener('click', (event) => {
event.preventDefault() event.preventDefault()
OC.msg.startSaving(selector) OC.msg.startSaving(selector)
saveSettings(0) saveSettings(0)
})
const resets = document.querySelectorAll('.btn-reset')
for (let btn of resets) {
btn.addEventListener('click', (event) => {
const target = event.target
const values = JSON.parse(target.getAttribute('data-reset'))
for (let i in values) {
document.querySelector(`#${i}`).value = values[i]
}
}) })
}
const displays = document.querySelectorAll('.side-menu-display') const resets = document.querySelectorAll('.btn-reset')
for (let display of displays) { for (let btn of resets) {
display.addEventListener('click', (event) => { btn.addEventListener('click', (event) => {
const target = event.target const target = event.target
const values = JSON.parse(target.getAttribute('data-reset'))
for (let d of displays) { for (let i in values) {
d.classList.toggle('is-active', d === display) document.querySelector(`#${i}`).value = values[i]
} }
})
}
document.querySelector('#side-menu-always-displayed').value = target.getAttribute('data-alwaysdiplayed') const displays = document.querySelectorAll('.side-menu-display')
document.querySelector('#side-menu-big-menu').value = target.getAttribute('data-bigmenu')
document.querySelector('#side-menu-side-with-categories').value = target.getAttribute('data-sidewithcategories') for (let display of displays) {
display.addEventListener('click', (event) => {
const target = event.target
for (let d of displays) {
d.classList.toggle('is-active', d === display)
}
document.querySelector('#side-menu-always-displayed').value = target.getAttribute('data-alwaysdiplayed')
document.querySelector('#side-menu-big-menu').value = target.getAttribute('data-bigmenu')
document.querySelector('#side-menu-side-with-categories').value = target.getAttribute('data-sidewithcategories')
})
}
for (let item of document.querySelectorAll('.apps-categories-custom')) {
item.addEventListener('change', (event) => {
updateAppsCategoriesCustom()
})
}
for (let item of document.querySelectorAll('.side-menu-setting-live')) {
item.addEventListener('change', (event) => {
const target = event.target
const name = target.getAttribute('name')
let value = target.value
let id = null
if (name === 'background-color-opacity') {
id = '#side-menu-background-color, #side-menu-background-color-to'
} else if (name === 'dark-mode-background-color-opacity') {
id = '#side-menu-dark-mode-background-color, #side-menu-dark-mode-background-color-to'
}
if (id) {
document.querySelector(id).dispatchEvent(new CustomEvent('change'))
return
}
if (name === 'opener') {
const url = OC.generateUrl(`/apps/side_menu/img/${value}.svg`).replace('/index.php', '')
value = `url(${url})`
}
if (name === 'icon-invert-filter' || name === 'icon-opacity') {
value/=100
}
if (['dark-mode-background-color', 'dark-mode-background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-dark-mode-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
} else if (['background-color', 'background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
}
document.documentElement.style.setProperty('--side-menu-' + name, value)
})
}
for (let toggler of document.querySelectorAll('.side-menu-toggler')) {
toggler.addEventListener('click', (event) => {
const target = event.target
const element = document.querySelector(target.getAttribute('data-target'))
elementToggler(element)
})
}
sortable('#categories-list .side-menu-setting-list', {
placeholderClass: 'side-menu-setting-list-drop'
}) })
}
for (let item of document.querySelectorAll('.apps-categories-custom')) { try {
item.addEventListener('change', (event) => { sortable('#categories-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
updateAppsCategoriesCustom() let value = []
})
}
for (let item of document.querySelectorAll('.side-menu-setting-live')) { for (let item of document.querySelectorAll('#categories-list .side-menu-setting-list-item')) {
item.addEventListener('change', (event) => { console.log(item.getAttribute('data-id'))
const target = event.target value.push(item.getAttribute('data-id'))
const name = target.getAttribute('name') }
let value = target.value document.querySelector('input[name="categories-order"]').value = JSON.stringify(value)
let id = null })
} catch (e) {
if (name === 'background-color-opacity') { }
id = '#side-menu-background-color, #side-menu-background-color-to'
} else if (name === 'dark-mode-background-color-opacity') {
id = '#side-menu-dark-mode-background-color, #side-menu-dark-mode-background-color-to'
}
if (id) {
document.querySelector(id).dispatchEvent(new CustomEvent('change'))
return
}
if (name === 'opener') {
const url = OC.generateUrl(`/apps/side_menu/img/${value}.svg`).replace('/index.php', '')
value = `url(${url})`
}
if (name === 'icon-invert-filter' || name === 'icon-opacity') {
value/=100
}
if (['dark-mode-background-color', 'dark-mode-background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-dark-mode-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
} else if (['background-color', 'background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
}
document.documentElement.style.setProperty('--side-menu-' + name, value)
})
}
for (let toggler of document.querySelectorAll('.side-menu-toggler')) {
toggler.addEventListener('click', (event) => {
const target = event.target
const element = document.querySelector(target.getAttribute('data-target'))
elementToggler(element)
})
}
sortable('#categories-list .side-menu-setting-list', {
placeholderClass: 'side-menu-setting-list-drop'
})
try {
sortable('#categories-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
let value = []
for (let item of document.querySelectorAll('#categories-list .side-menu-setting-list-item')) {
console.log(item.getAttribute('data-id'))
value.push(item.getAttribute('data-id'))
}
document.querySelector('input[name="categories-order"]').value = JSON.stringify(value)
})
} catch (e) {
}
}) })
-11
View File
@@ -1,11 +0,0 @@
module.exports = (tagName, attributes) => {
const element = document.createElement(tagName)
if (typeof attributes === 'object') {
for (let i in attributes) {
element.setAttribute(i, attributes[i])
}
}
return element
}
+86 -98
View File
@@ -1,128 +1,116 @@
:root { :root {
<?php foreach ($_['vars'] as $key => $value): ?> <?php foreach ($_['vars'] as $key => $value): ?>
<?php if ($key === 'opener'): ?> <?php if ($key === 'opener'): ?>
--side-menu-<?php echo $key ?>: url('<?php print_unescaped(image_path('side_menu', $value.'.svg')); ?>'); --side-menu-<?php echo $key ?>: url('<?php print_unescaped(image_path('side_menu', $value.'.svg')); ?>');
<?php else: ?> <?php else: ?>
--side-menu-<?php echo $key ?>: <?php echo $value ?>; --side-menu-<?php echo $key ?>: <?php echo $value ?>;
<?php endif; ?> <?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
} }
<?php if (empty($_['top-menu-apps']) && empty($_['top-side-menu-apps'])): ?> <?php if (empty($_['top-menu-apps']) && empty($_['top-side-menu-apps'])): ?>
#appmenu { #appmenu {
display: none; display: none;
} }
#appmenu + nav { #appmenu + nav {
display: none; display: none;
} }
<?php else: ?> <?php else: ?>
.app-hidden { .app-hidden {
opacity: 0; opacity: 0;
} }
<?php endif; ?> <?php endif; ?>
<?php if ($_['opener-only']): ?> <?php if ($_['opener-only']): ?>
#nextcloud { #nextcloud {
display: none; display: none;
} }
<?php endif; ?> <?php endif; ?>
<?php if (!$_['display-logo']): ?> <?php if (!$_['display-logo']): ?>
.side-menu-logo { .side-menu-logo {
display: none; display: none;
}
.side-menu-header {
height: 50px;
}
.side-menu-apps-list {
height: calc(100vh - 49px);
top: 49px;
}
#side-menu.hide-opener .side-menu-header {
visibility: hidden;
}
<?php if ($_['size-text'] === 'hidden'): ?>
#side-menu, .side-menu-apps-list {
<?php if ($_['size-icon'] === 'big'): ?>
width: 55px;
<?php else: ?>
width: 52px;
<?php endif; ?>
} }
#side-menu .side-menu-opener { .side-menu-header {
<?php if ($_['size-icon'] === 'big'): ?> height: 50px;
margin-left: 1px;
<?php else: ?>
margin-left: 0px;
<?php endif; ?>
} }
<?php endif; ?>
.side-menu-apps-list {
height: calc(100vh - 49px);
top: 49px;
}
#side-menu.hide-opener .side-menu-header {
visibility: hidden;
}
<?php if ($_['size-text'] === 'hidden'): ?>
#side-menu, .side-menu-apps-list {
<?php if ($_['size-icon'] === 'big'): ?>
width: 55px;
<?php else: ?>
width: 52px;
<?php endif; ?>
}
#side-menu .side-menu-opener {
<?php if ($_['size-icon'] === 'big'): ?>
margin-left: 1px;
<?php else: ?>
margin-left: 0px;
<?php endif; ?>
}
<?php endif; ?>
<?php endif; ?> <?php endif; ?>
<?php if ($_['size-icon'] === 'hidden'): ?> <?php if ($_['size-icon'] === 'hidden'): ?>
.side-menu-app-icon { .side-menu-app-icon {
display: none; display: none;
} }
<?php elseif ($_['size-icon'] === 'small'): ?> <?php elseif ($_['size-icon'] === 'small'): ?>
.side-menu-app-icon svg { .side-menu-app-icon svg {
width: 15px; width: 15px;
height: 15px; height: 15px;
} }
img.side-menu-app-icon { img.side-menu-app-icon {
width: 15px; width: 15px;
height: 15px; height: 15px;
} }
<?php elseif ($_['size-icon'] === 'normal'): ?> <?php elseif ($_['size-icon'] === 'normal'): ?>
.side-menu-app-icon svg { .side-menu-app-icon svg {
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
img.side-menu-app-icon { img.side-menu-app-icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
<?php elseif ($_['size-icon'] === 'big'): ?> <?php elseif ($_['size-icon'] === 'big'): ?>
.side-menu-app-icon svg { .side-menu-app-icon svg {
width: 23px; width: 23px;
height: 23px; height: 23px;
} }
img.side-menu-app-icon { img.side-menu-app-icon {
width: 23px; width: 23px;
height: 23px; height: 23px;
} }
<?php endif; ?> <?php endif; ?>
<?php if ($_['size-text'] === 'hidden'): ?> <?php if ($_['size-text'] === 'hidden'): ?>
.side-menu-app-text { .side-menu-app-text {
display: none; display: none;
} }
<?php elseif ($_['size-text'] === 'small'): ?> <?php elseif ($_['size-text'] === 'small'): ?>
.side-menu-app-text { .side-menu-app-text {
font-size: 12px; font-size: 12px;
} }
<?php elseif ($_['size-text'] === 'big'): ?> <?php elseif ($_['size-text'] === 'big'): ?>
.side-menu-app-text { .side-menu-app-text {
font-size: 16px; font-size: 16px;
} }
<?php endif; ?>
<?php if ($_['always-displayed']): ?>
#content {
left: 53px;
width: calc(100% - (var(--body-container-margin) * 2) - 62px);
}
#content-vue {
width: calc(100% - (var(--body-container-margin) * 2) - 60px);
margin-left: 11px;
}
<?php endif; ?> <?php endif; ?>
+86
View File
@@ -0,0 +1,86 @@
const alwaysDisplayed = function() {
const elements = querySelectorAll('*')
const fixedElements = []
for (let element of elements) {
if (typeof element !== 'object') {
continue
}
const position = window.getComputedStyle(element, null).getPropertyValue('position')
if (position !== 'fixed') {
continue
}
const id = element.getAttribute('id')
if (id === 'header' || id === 'side-menu' || id === 'side-menu-loader') {
continue
}
if (element.classList.contains('oc-dialog')) {
continue
}
let elementIsInSideMenu = false
let parent = element.parentNode
while (parent && !elementIsInSideMenu) {
try {
if (parent.getAttribute('id') === 'side-menu') {
elementIsInSideMenu = true
}
} catch (e) {
}
parent = parent.parentNode
}
if (elementIsInSideMenu) {
continue
}
fixedElements.push(element)
}
for (let i in fixedElements) {
const element = fixedElements[i]
const computedStyle = window.getComputedStyle(element, null)
const left = computedStyle.getPropertyValue('left')
const right = computedStyle.getPropertyValue('right')
if (right !== '0px') {
const intValue = parseInt(left.replace('px', '')) + 50
element.style.setProperty('transform', 'translateX(' + intValue.toString() + 'px)')
}
}
}
const content = querySelector('#content')
if (content && content.classList.contains('app-settings')) {
let loaded = false
const config = {
attributes: false,
childList: true,
subtree: true
}
const observer = new MutationObserver(() => {
if (loaded) {
return
}
const element = content.querySelector('#app-category-your-apps') || content.querySelector('#app-navigation ul')
if (element) {
loaded = true
alwaysDisplayed()
}
})
observer.observe(content, config)
} else {
window.setTimeout(alwaysDisplayed, 200)
}
+14
View File
@@ -0,0 +1,14 @@
let pageLoader = createElement('div', {id: 'side-menu-loader'})
let pageLoaderBar = createElement('div', {id: 'side-menu-loader-bar'})
pageLoader.appendChild(pageLoaderBar)
querySelector('body').appendChild(pageLoader)
let pageLoaderValue = 0
window.addEventListener('beforeunload', () => {
setInterval(() => {
pageLoaderBar.style.width = pageLoaderValue.toString() + '%'
pageLoaderValue = Math.min(pageLoaderValue + .2, 100)
}, 25)
})
+216
View File
@@ -0,0 +1,216 @@
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 = `
<defs>
<filter id="invertMenuMore-${appId}">
<feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix>
</filter>
</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)
})
+185 -143
View File
@@ -13,168 +13,210 @@ if ($_['always-displayed']) {
?> ?>
(function() { (function() {
const sideMenuContainer = SMcreateElement('div', {id: 'side-menu-container'}) const querySelector = function(selector, element) {
const sideMenuOpener = SMcreateElement('button', {'class': 'side-menu-opener'}) if (element) {
const sideMenu = SMcreateElement('div', {id: 'side-menu'}) return element.querySelector(selector)
const body = document.querySelector('body')
const html = document.querySelector('html')
const nextcloud = document.querySelector('#nextcloud')
const isTouchDevice = window.matchMedia("(pointer: coarse)").matches
const targetBlankApps = <?php echo json_encode($_['target-blank-apps']) ?>
window.topMenuApps = <?php echo json_encode($_['top-menu-apps']), "\n"; ?>
window.topSideMenuApps = <?php echo json_encode($_['top-side-menu-apps']); ?>
<?php if ($display === 'big-menu'): ?>
sideMenu.setAttribute('data-bigmenu', '1')
<?php elseif ($display === 'side-with-categories'): ?>
sideMenu.setAttribute('data-sidewithcategories', '1')
<?php endif; ?>
document.querySelector('body').addEventListener('side-menu.apps', (e) => {
const apps = e.detail.apps;
<?php if ($_['hide-when-no-apps']): ?>
const sideMenu = document.querySelector('#side-menu')
if (apps.length === 0) {
sideMenu.classList.remove('open')
sideMenu.classList.add('hide')
sideMenuOpener.classList.add('hide')
} else {
sideMenu.classList.remove('hide')
sideMenuOpener.classList.remove('hide')
}
<?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
} else {
html.classList.add('side-menu-always-displayed')
} }
<?php endif; ?>
<?php else: ?>
<?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
} else {
html.classList.add('side-menu-always-displayed')
}
<?php endif; ?>
<?php endif; ?>
})
body.addEventListener('side-menu.ready', () => { return document.querySelector(selector)
const sideMenu = document.querySelector('#side-menu')
const headerMenuOpener = document.querySelector('#header .side-menu-opener')
const sideMenuOpener = document.querySelectorAll('#side-menu .side-menu-opener')
sideMenuFocus = () => {
let a = document.querySelector('.side-menu-app.active a', sideMenu)
if (!a) {
return
}
if (a.length === 0) {
a = sideMenu.querySelector('.side-menu-app:first-child a')
}
if (a.length > 0) {
a.focus()
}
} }
<?php if ($_['opener-hover']): ?> const querySelectorAll = function(selector, element) {
const sideMenuMouseLeave = () => { if (element) {
sideMenu.classList.remove('open') return element.querySelectorAll(selector)
sideMenu.removeEventListener('mouseleave', sideMenuMouseLeave) }
}
const sideMenuMouseEnter = () => { return document.querySelectorAll(selector)
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave) }
}
const sideMenuOpenerMouseEnter = () => { const createElement = function(tagName, attributes) {
sideMenu.classList.add('open') const element = document.createElement(tagName)
sideMenu.addEventListener('mouseenter', sideMenuMouseEnter)
sideMenuFocus() if (typeof attributes === 'object') {
} for (let i in attributes) {
element.setAttribute(i, attributes[i])
}
}
if (!isTouchDevice) { return element
<?php if ($_['opener-hover']): ?> }
headerMenuOpener.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
sideMenu.classList.add('hide-opener') const sideMenuContainer = createElement('div', {id: 'side-menu-container'})
<?php endif ?> const sideMenuOpener = createElement('button', {'class': 'side-menu-opener'})
const sideMenu = createElement('div', {id: 'side-menu'})
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave) const body = querySelector('body')
sideMenu.addEventListener('mouseenter', sideMenuOpenerMouseEnter) const html = querySelector('html')
} const nextcloud = querySelector('#nextcloud')
const isTouchDevice = window.matchMedia("(pointer: coarse)").matches
const targetBlankApps = <?php echo json_encode($_['target-blank-apps']) ?>
<?php if ($display === 'big-menu'): ?>
sideMenu.setAttribute('data-bigmenu', '1')
<?php elseif ($display === 'side-with-categories'): ?>
sideMenu.setAttribute('data-sidewithcategories', '1')
<?php endif; ?> <?php endif; ?>
headerMenuOpener.addEventListener('click', () => { querySelector('body').addEventListener('side-menu.apps', (e) => {
sideMenu.classList.add('open') const apps = e.detail.apps;
const a = sideMenu.querySelector('.side-menu-app.active a') <?php if ($_['hide-when-no-apps']): ?>
const sideMenu = querySelector('#side-menu')
if (a !== null) { if (apps.length === 0) {
a.focus() sideMenu.classList.remove('open')
} sideMenu.classList.add('hide')
sideMenuOpener.classList.add('hide')
} else {
sideMenu.classList.remove('hide')
sideMenuOpener.classList.remove('hide')
}
headerMenuOpener.blur() <?php if ($display === 'always-displayed'): ?>
}) if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
for (let opener of sideMenuOpener) { } else {
opener.addEventListener('click', () => { html.classList.add('side-menu-always-displayed')
<?php if ($display === 'always-displayed'): ?> }
sideMenu.classList.toggle('open') <?php endif; ?>
<?php else: ?> <?php else: ?>
sideMenu.classList.remove('open') <?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) {
html.classList.remove('side-menu-always-displayed')
} else {
html.classList.add('side-menu-always-displayed')
}
<?php endif; ?>
<?php endif; ?>
})
body.addEventListener('side-menu.ready', () => {
const sideMenu = querySelector('#side-menu')
const headerMenuOpener = querySelector('#header .side-menu-opener')
const sideMenuOpener = querySelectorAll('#side-menu .side-menu-opener')
if (!headerMenuOpener) {
return
}
sideMenuFocus = () => {
let a = querySelector('.side-menu-app.active a', sideMenu)
if (!a) {
return
}
if (a.length === 0) {
a = querySelector('.side-menu-app:first-child a', sideMenu)
}
if (a.length > 0) {
a.focus()
}
}
<?php if ($_['opener-hover']): ?>
const sideMenuMouseLeave = () => {
sideMenu.classList.remove('open')
sideMenu.removeEventListener('mouseleave', sideMenuMouseLeave)
}
const sideMenuMouseEnter = () => {
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
}
const sideMenuOpenerMouseEnter = () => {
sideMenu.classList.add('open')
sideMenu.addEventListener('mouseenter', sideMenuMouseEnter)
sideMenuFocus()
}
if (!isTouchDevice) {
<?php if ($_['opener-hover']): ?>
headerMenuOpener.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
sideMenu.classList.add('hide-opener')
<?php endif ?>
sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
sideMenu.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
}
<?php endif; ?>
headerMenuOpener.addEventListener('click', () => {
sideMenu.classList.add('open')
const a = querySelector('.side-menu-app.active a', sideMenu)
if (a !== null) {
a.focus()
}
headerMenuOpener.blur()
})
for (let opener of sideMenuOpener) {
opener.addEventListener('click', () => {
<?php if ($display === 'always-displayed'): ?>
sideMenu.classList.toggle('open')
<?php else: ?>
sideMenu.classList.remove('open')
<?php endif; ?>
})
}
document.addEventListener('keydown', (e) => {
var key = e.key || e.keyCode
if ((key === 'o' || key === 79) && e.ctrlKey === true) {
e.preventDefault()
sideMenu.classList.toggle('open')
sideMenuFocus()
}
})
const sideMenuObserver = new MutationObserver((e) => {
if (body.getAttribute('id') !== 'body-settings') {
return
}
body.classList.toggle('body-settings-side-menu', sideMenu.classList.contains('open'))
})
sideMenuObserver.observe(sideMenu, {
attributes: true,
attributeFilter: ['class'],
childList: false,
characterData: false
})
})
body.appendChild(sideMenuContainer)
sideMenuContainer.appendChild(sideMenu)
<?php if ($_['loader-enabled'] === true): ?>
<?php require_once __DIR__.'/_loaderEnabled.js'; ?>
<?php endif; ?>
if (nextcloud) {
<?php if ($_['opener-position'] === 'before'): ?>
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud)
<?php else: ?>
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud.nextSibling)
<?php endif; ?> <?php endif; ?>
})
} }
document.addEventListener('keydown', (e) => { <?php if (!empty($_['top-menu-apps']) || !empty($_['top-side-menu-apps'])): ?>
var key = e.key || e.keyCode const topMenuApps = <?php echo json_encode($_['top-menu-apps']), "\n"; ?>
const topSideMenuApps = <?php echo json_encode($_['top-side-menu-apps']); ?>
if ((key === 'o' || key === 79) && e.ctrlKey === true) { <?php require_once __DIR__.'/_topMenuApps.js'; ?>
e.preventDefault() <?php endif; ?>
sideMenu.classList.toggle('open') <?php if ($display === 'always-displayed'): ?>
sideMenuFocus() <?php require_once __DIR__.'/_alwaysDisplayed.js'; ?>
} <?php endif; ?>
})
const sideMenuObserver = new MutationObserver((e) => {
if (body.getAttribute('id') !== 'body-settings') {
return
}
body.classList.toggle('body-settings-side-menu', sideMenu.classList.contains('open'))
})
sideMenuObserver.observe(sideMenu, {
attributes: true,
attributeFilter: ['class'],
childList: false,
characterData: false
})
})
body.appendChild(sideMenuContainer)
sideMenuContainer.appendChild(sideMenu)
<?php if ($_['loader-enabled'] === true): ?>
PageLoader()
<?php endif; ?>
<?php if ($_['opener-position'] === 'before'): ?>
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud)
<?php else: ?>
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud.nextSibling)
<?php endif; ?>
})(); })();
-1
View File
@@ -3,7 +3,6 @@ const { VueLoaderPlugin } = require('vue-loader')
const StyleLintPlugin = require('stylelint-webpack-plugin') const StyleLintPlugin = require('stylelint-webpack-plugin')
module.exports = { module.exports = {
devtool: "source-map",
entry: { entry: {
'admin': path.join(__dirname, 'src', 'admin.js'), 'admin': path.join(__dirname, 'src', 'admin.js'),
'sideMenu': path.join(__dirname, 'src', 'SideMenu.js'), 'sideMenu': path.join(__dirname, 'src', 'SideMenu.js'),