Compare commits

..

82 Commits

Author SHA1 Message Date
Simon Vieille a2b615752b release v1.9.2 2020-09-03 10:43:40 +02:00
Simon Vieille aec89ce9fa release v1.9.1 2020-09-03 10:43:08 +02:00
Simon Vieille 02e526d717 release v1.9.1 2020-09-03 10:39:25 +02:00
Simon Vieille ebb485ffba release v1.9.0 2020-09-03 10:31:11 +02:00
Simon Vieille 80221c3ea2 release v1.9.0 2020-09-03 10:19:18 +02:00
Simon Vieille f8aebf94d2 release v1.9.0 2020-09-03 10:17:08 +02:00
Simon Vieille 97da542bd8 prepare release 1.9.0 2020-09-03 10:11:09 +02:00
Simon Vieille 612a94fd06 Merge branch 'feature/fixed-left-menu' into develop 2020-09-03 09:49:41 +02:00
Simon Vieille a7bcc7da0f replace $ by jQuery 2020-09-03 09:49:30 +02:00
Simon Vieille 3da1830f58 fix #21: add on option to always display the menu on the left 2020-09-03 09:46:39 +02:00
Simon Vieille 8a9c68c702 fix #21: add on option to always display the menu on the left 2020-09-03 09:46:36 +02:00
Simon Vieille e55419eb8b Merge branch 'develop' into feature/fixed-left-menu 2020-09-02 19:13:03 +02:00
Simon Vieille 2f0e47dcec remove experimental notices 2020-09-02 19:12:50 +02:00
Simon Vieille 361ad9211c poc #21: add a fixed menu on the left 2020-09-02 19:09:37 +02:00
Simon Vieille 2fc9a4effe update translations 2020-08-29 14:46:04 +02:00
Simon Vieille 6a5ecfa115 add translations: de 2020-08-29 14:30:46 +02:00
Simon Vieille aab26bad32 add documentation 2020-08-29 14:04:52 +02:00
Simon Vieille f3e657dcdb add gitignore 2020-08-29 14:04:45 +02:00
Simon Vieille e1ca610256 update release task 2020-08-28 20:17:58 +02:00
Simon Vieille acf2022938 update release task 2020-08-28 20:13:10 +02:00
Simon Vieille 707dbf6810 indentation 2020-08-28 17:30:08 +02:00
Simon Vieille f8dd123851 indentation 2020-08-28 17:29:03 +02:00
Simon Vieille 518f4bb70c update documentation 2020-08-28 17:28:12 +02:00
Simon Vieille c8b3258819 update update 2020-08-28 17:28:03 +02:00
Simon Vieille 186ee4d344 add Makefile 2020-08-28 17:11:12 +02:00
Simon Vieille 64ee6fb223 add gitignore 2020-08-28 17:09:24 +02:00
Simon Vieille c2662cdaae Merge branch 'feature/translations' into develop 2020-08-28 16:46:45 +02:00
Simon Vieille d3ee4120c9 add gitignore 2020-08-28 16:46:38 +02:00
Simon Vieille 4fd11f2deb add l10n generators and fr translations 2020-08-28 16:45:19 +02:00
Simon Vieille 72dc0c7d01 update forms to handle translations 2020-08-28 16:45:02 +02:00
Simon Vieille 8c38382eae remove l10n files 2020-08-28 16:44:36 +02:00
Simon Vieille 1567a8e82f fix #28: menu items invisible after 1.8.4 2020-08-25 19:06:01 +02:00
Simon Vieille e1ae5d21e2 add compatibility with NC 20 2020-08-25 13:33:02 +02:00
Simon Vieille 4370738625 fix #27: disable side menu on public pages - broken in 1.8.3 2020-08-25 12:18:39 +02:00
Simon Vieille acdd299573 release v1.8.3 2020-08-20 16:29:41 +02:00
Simon Vieille fae21390dd fix #26: alphabetic order of apps 2020-08-20 16:27:35 +02:00
Simon Vieille 4f0fdd91f9 release v1.8.2 2020-08-19 11:44:05 +02:00
Simon Vieille d084cfed86 fix #25: Icon x for closing the big menu 2020-08-19 11:41:10 +02:00
Simon Vieille 3db0c1ccd2 fix #23: hidden icons in the top menu 2020-08-19 11:29:21 +02:00
Simon Vieille e4f9765247 fix #24: missing apps 2020-08-19 09:18:29 +02:00
Simon Vieille e7d319bbf7 fix 't.apps is not iterable' 2020-08-18 14:39:45 +02:00
Simon Vieille cf0f459aeb release v1.8.1 2020-08-14 13:31:13 +02:00
Simon Vieille b5ce8b92e1 fix issue with links 2020-08-14 13:30:48 +02:00
Simon Vieille 39aede4a73 release v1.8.0 2020-08-13 23:15:03 +02:00
Simon Vieille d9ea37ba62 fix cache and config key 2020-08-13 23:02:28 +02:00
Simon Vieille aad3038003 admin ui, remove debugging code 2020-08-13 22:24:41 +02:00
Simon Vieille 011d24d3bd add icon opacity 2020-08-13 22:07:09 +02:00
Simon Vieille 9eec0ce3ef add icon color filter range 2020-08-13 21:22:10 +02:00
Simon Vieille 831d2d81fc add documentation 2020-08-13 16:51:41 +02:00
Simon Vieille c6a43f755d fix issue with empty cat 2020-08-13 16:33:05 +02:00
Simon Vieille d30603c7d1 update the big-menu size when screen < 1024px 2020-08-13 16:13:31 +02:00
Simon Vieille aa9ba60680 fix issue with the classic display 2020-08-13 13:05:38 +02:00
Simon Vieille d3b7b99aae Merge branch 'feature/issue22-favorite-apps' into develop 2020-08-13 11:34:43 +02:00
Simon Vieille 3a431f16e7 release v1.8.0-rc1 2020-08-13 11:34:40 +02:00
Simon Vieille e4abc5b43f add PSR 2 compliance 2020-08-13 11:21:59 +02:00
Simon Vieille ef72c00fbb fix translations 2020-08-13 11:21:06 +02:00
Simon Vieille e1c6784d41 update visible apps order 2020-08-12 16:32:26 +02:00
Simon Vieille 3f2a307226 refactoring 2020-08-12 16:27:43 +02:00
Simon Vieille deca4b2cba fix issue with 'top-menu-apps' 2020-08-12 16:16:50 +02:00
Simon Vieille a1b1396b63 add add top-menu-apps option in the personal settings 2020-08-12 15:59:51 +02:00
Simon Vieille 9d9afff08d fix compatibility with the option 'hide-when-no-apps' 2020-08-12 14:06:52 +02:00
Simon Vieille ffab77aff2 add options to select apps that must be displayed in the top menu (replace exteral sites option) 2020-08-12 10:51:49 +02:00
Simon Vieille caa9c092ef add external links 2020-08-11 15:52:15 +02:00
Simon Vieille 8aa08dbc6b add compatibility with basic side menu options 2020-08-11 15:26:40 +02:00
Simon Vieille 460dac5faf update admin and personal pages 2020-08-11 15:26:17 +02:00
Simon Vieille ce5c806524 update ui of the big menu, add 'files' app 2020-08-11 13:34:36 +02:00
Simon Vieille 307f69906b add links 2020-08-11 10:40:31 +02:00
Simon Vieille 5a86e16861 update ui of the big menu 2020-08-11 10:34:06 +02:00
Simon Vieille 8c3dd2a4f6 remove personal setting for the big menu 2020-08-11 10:33:48 +02:00
Simon Vieille 8e0be738a6 add base of the big menu 2020-08-06 15:00:59 +02:00
Simon Vieille ddb7cc40ce refactor appinfo 2020-08-06 15:00:24 +02:00
Simon Vieille cdcc4ddd51 refactor js 2020-08-04 09:15:16 +02:00
Simon Vieille e1d450218c release v1.7.0 2020-05-19 15:23:25 +02:00
Simon Vieille 93c8ade21a release v1.6.4 2020-05-19 15:21:44 +02:00
Simon Vieille dcc19f5b23 add loader 2020-05-19 15:17:54 +02:00
Simon Vieille 6418e79d49 compatibility with nextcloud 19 2020-05-15 16:59:52 +02:00
Simon Vieille 9b4aecaf41 add documentation 2020-05-14 17:03:09 +02:00
Simon Vieille 46148167e9 add documentation 2020-05-14 17:02:23 +02:00
Simon Vieille a0f4eddab0 release v1.6.3 2020-05-14 15:31:20 +02:00
Simon Vieille 24bf8496f3 fix issue #20: add shortcut to open and to hide the menu 2020-05-14 15:30:34 +02:00
Simon Vieille 8368562ed0 release v1.6.2 2020-05-14 13:28:39 +02:00
Simon Vieille 9a8f5517b6 add a cache for the top menu update (#19) 2020-05-13 12:25:11 +02:00
39 changed files with 1501 additions and 294 deletions
+4
View File
@@ -1,2 +1,6 @@
/js /js
/node_modules /node_modules
/l10n/*
/releases
/package-lock.json
!/l10n/.gitkeep
+24
View File
@@ -0,0 +1,24 @@
npm-build:
npm run build
npm-watch:
npm run watch
.ONESHELL:
release: npm-build translations
if [ -z "$$VERSION" ]; then
echo "VERSION required"
exit 1
fi
test -d releases/$$VERSION && rm -fr releases/$$VERSION
mkdir -p releases/$$VERSION/side_menu
cp -r README.md appinfo css lib img l10n js src templates screenshots releases/$$VERSION/side_menu
cd releases/$$VERSION
zip -r side_menu_v$$VERSION.zip side_menu
tar cvzf side_menu_v$$VERSION.tar.gz side_menu
rm -fr side_menu
openssl dgst -sha512 -sign $$HOME/.nextcloud/certificates/side_menu.key side_menu_v$$VERSION.tar.gz | openssl base64 > side_menu_v$$VERSION.sig
translations:
php bin/generate_l10n.php
+13 -37
View File
@@ -1,57 +1,33 @@
🤙 Nextcloud app / Side menu 🎨 🤙 Nextcloud app / Side menu 🎨
=============================== ===============================
Side menu allows you to modify the position of the main menu by creating a panel on the left of the interface. Side menu allows you to modify the position of the main menu by creating a panel on the left of the interface or with a big menu on the top.
You can also define apps that must be displayed in the top menu. Fully customisable.
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.
Comptatible with AppOrder. Comptatible with AppOrder.
Installation * [Installation and upgrade](#installation-and-upgrade)
------------ * [How to contribute?](#how-to-contribute)
* [Screenshots](https://gitnet.fr/deblan/side_menu/src/branch/master/screenshots/)
Side menu is availabe from the app store. Installation and upgrade
------------------------
If you want to install it from source: go to https://gitnet.fr/deblan/side_menu/releases and download the last release (side_menu_vX.Y.Z.zip). Side menu is availabe from the app store. If you want to install it from source: go to https://gitnet.fr/deblan/side_menu/releases and download the last release (side_menu_vX.Y.Z.zip). Copy the content into `apps`.
Copy the archive to your Nextcloud server.
``` ```
$ cd /path/to/nextcloud/apps $ cd /path/to/nextcloud/apps
$ unzip -d side_menu /path/to/side_menu_vX.Y.Z.zip $ unzip /path/to/side_menu_vX.Y.Z.zip # tar xvf /path/to/side_menu_vX.Y.Z.tar.gz
``` ```
### Upgrade
Side menu can be upgraded from the app store.
If you want to install it from source: go to https://gitnet.fr/deblan/side_menu/releases and download the last release (side_menu_vX.Y.Z.zip).
Copy the archive to your Nextcloud server.
```
$ cd /path/to/nextcloud/apps
$ rm -frv side_menu
$ unzip -d side_menu /path/to/side_menu_vX.Y.Z.zip
```
Preview
-------
![](https://upload.deblan.org/u/2020-03/5e81b219.jpg)
![](https://upload.deblan.org/u/2020-03/5e7fab2b.jpg)
![](https://upload.deblan.org/u/2020-05/5eb6b76e.png)
![](https://upload.deblan.org/u/2020-05/5eb6b78a.png)
Configuration
-------------
Administrators can edit many settings using the administration page. Administrators can edit many settings using the administration page.
Users can disable the menu using the page of personal settings. Users can disable the menu using the page of personal settings.
Use the shortcut `Ctrl`+`o` to open and to hide the side menu. Use `tab` to navigate.
How to contribute? How to contribute?
------------------ ------------------
@@ -64,6 +40,6 @@ If you are a developer:
* go to `apps/` and clone your repository * go to `apps/` and clone your repository
* go to `apps/side_menu` and run `npm install` * go to `apps/side_menu` and run `npm install`
Build javascripts using `webpack --config ./webpack.js` (add `-w` 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.
+5 -66
View File
@@ -1,71 +1,10 @@
<?php <?php
/** use OCA\SideMenu\AppInfo\Application;
* @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/>.
*/
namespace OCA\SideMenu\Appinfo; $app = new Application();
use OC\Security\CSP\ContentSecurityPolicy; if ($app->isEnabled()) {
use OCP\Util; $app->registerAssets();
use OCP\IUserSession; $app->registerServices();
$config = \OC::$server->getConfig();
$cspnm = \OC::$server->getContentSecurityPolicyNonceManager();
$user = \OC::$server[IUserSession::class]->getUser();
$enabled = true;
if ($user !== null) {
$enabled = (bool) $config->getUserValue($user->getUid(), 'side_menu', 'enabled', '1');
}
if ($enabled) {
Util::addScript('side_menu', 'sideMenu');
Util::addStyle('side_menu', 'sideMenu');
$stylesheet = \OC::$server->getURLGenerator()->linkToRoute(
'side_menu.Css.stylesheet',
[
'v' => $config->getAppValue('side_menu', 'cache', '0'),
]
);
$script = \OC::$server->getURLGenerator()->linkToRoute(
'side_menu.Js.script',
[
'v' => $config->getAppValue('side_menu', 'cache', '0'),
]
);
Util::addHeader(
'link',
[
'href' => $stylesheet,
'rel' => 'stylesheet'
],
''
);
Util::addHeader(
'script',
[
'src' => $script,
'nonce' => $cspnm->getNonce(),
],
''
);
} }
+24 -13
View File
@@ -3,9 +3,13 @@
<id>side_menu</id> <id>side_menu</id>
<name>Side menu</name> <name>Side menu</name>
<summary>Move the top menu to the left side.</summary> <summary>Move the top menu to the left side.</summary>
<description><![CDATA[Side menu allows you to modify the position of the main menu by creating a panel on the left of the interface. <description><![CDATA[Side menu allows you to modify the position of the main menu by creating a panel on the left of the interface or with a big menu on the top.
You can also define apps that must be displayed in the top menu. Fully customisable.
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.
Use the shortcut `Ctrl`+`o` to open and to hide the side menu. Use `tab` to navigate.
Comptatible with AppOrder. 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.
@@ -13,23 +17,30 @@ You can report a bug or request a feature by opening an issue.
]]></description> ]]></description>
<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>
<version>1.6.1</version> <version>1.9.2</version>
<namespace>SideMenu</namespace> <namespace>SideMenu</namespace>
<category>customization</category> <category>customization</category>
<website>https://gitnet.fr/deblan/side_menu</website> <website>https://gitnet.fr/deblan/side_menu</website>
<discussion>https://deblan.cloud/index.php/call/fffxkc7h</discussion>
<bugs>https://gitnet.fr/deblan/side_menu/issues</bugs> <bugs>https://gitnet.fr/deblan/side_menu/issues</bugs>
<repository type="git">https://gitnet.fr/deblan/side_menu</repository> <repository type="git">https://gitnet.fr/deblan/side_menu</repository>
<screenshot>https://upload.deblan.org/u/2020-03/5e81b219.jpg</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png</screenshot>
<screenshot>https://upload.deblan.org/u/2020-03/5e7fab2b.jpg</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/admin_settings.png</screenshot>
<screenshot>https://upload.deblan.org/u/2020-05/5eb6b76e.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/n19_big_menu.png</screenshot>
<screenshot>https://upload.deblan.org/u/2020-05/5eb6b78a.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc18_menu_always_displayed.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>
<documentation>
<admin>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</admin>
<developer>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</developer>
</documentation>
<dependencies> <dependencies>
<nextcloud min-version="18" max-version="18"/> <nextcloud min-version="18" max-version="20"/>
</dependencies> </dependencies>
<settings> <settings>
<admin>OCA\SideMenu\Settings\Admin</admin> <admin>OCA\SideMenu\Settings\Admin</admin>
<admin-section>OCA\SideMenu\Settings\AdminSection</admin-section> <admin-section>OCA\SideMenu\Settings\AdminSection</admin-section>
<personal>OCA\SideMenu\Settings\Personal</personal> <personal>OCA\SideMenu\Settings\Personal</personal>
<personal-section>OCA\SideMenu\Settings\PersonalSection</personal-section> <personal-section>OCA\SideMenu\Settings\PersonalSection</personal-section>
</settings> </settings>
</info> </info>
+1
View File
@@ -21,6 +21,7 @@ return [
'routes' => [ 'routes' => [
['name' => 'Css#stylesheet', 'url' => '/css/stylesheet', 'verb' => 'GET'], ['name' => 'Css#stylesheet', 'url' => '/css/stylesheet', 'verb' => 'GET'],
['name' => 'Js#script', 'url' => '/js/script', 'verb' => 'GET'], ['name' => 'Js#script', 'url' => '/js/script', 'verb' => 'GET'],
['name' => 'Nav#items', 'url' => '/nav/items', 'verb' => 'GET'],
['name' => 'PersonalSetting#valueSet', 'url' => '/personalSetting/valueSet', 'verb' => 'POST'], ['name' => 'PersonalSetting#valueSet', 'url' => '/personalSetting/valueSet', 'verb' => 'POST'],
], ],
]; ];
+33
View File
@@ -0,0 +1,33 @@
<?php
function generateJsContent($translations)
{
$json = json_encode($translations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return <<< EOF
OC.L10N.register("side_menu", {$json}, "nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;");
EOF;
}
function generateJsonContent($translations)
{
$datas = [
'translations' => $translations,
'pluralForm' => 'nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;',
];
return json_encode(
$datas,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
chdir(__DIR__.'/../');
foreach (glob('src/l10n/fixtures/*.yaml') as $file) {
$lang = str_replace('.yaml', '', basename($file));
$translations = yaml_parse(file_get_contents($file));
file_put_contents('l10n/'.$lang.'.js', generateJsContent($translations));
file_put_contents('l10n/'.$lang.'.json', generateJsonContent($translations));
}
+19
View File
@@ -20,6 +20,25 @@
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
} }
#side-menu-section input[type="checkbox"] {
vertical-align: middle;
}
#side-menu-section input[type="range"] {
vertical-align: middle;
}
#side-menu-section select { #side-menu-section select {
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
} }
.keyboard-key {
padding: 1px 9px;
margin: 0 2px;
background: #eee;
border: 1px solid #aaa;
color: #555;
border-radius: 3px;
}
+153 -2
View File
@@ -45,10 +45,15 @@
border: 0; border: 0;
} }
#side-menu.hide-opener .side-menu-opener, .side-menu-opener.hide { .side-menu-closer {
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 {
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;
@@ -63,6 +68,8 @@
width: 20px; width: 20px;
vertical-align: top; vertical-align: top;
margin-right: 10px; margin-right: 10px;
filter: invert(var(--side-menu-icon-invert-filter, 0%));
opacity: var(--side-menu-icon-opacity, 1);
} }
.side-menu-app-icon svg { .side-menu-app-icon svg {
@@ -80,7 +87,7 @@
white-space: nowrap; white-space: nowrap;
} }
.side-menu-app a:hover, .side-menu-app.active a { .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);
} }
@@ -107,3 +114,147 @@
#side-menu.hide-opener .side-menu-logo { #side-menu.hide-opener .side-menu-logo {
margin-top: 20px; margin-top: 20px;
} }
#side-menu-loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 3001;
}
#side-menu-loader-bar {
height: 4px;
background: var(--side-menu-loader-color, #0e75ac);
width: 0;
transition-property: width;
}
#side-menu.side-menu-big {
max-width: 100%;
height: auto;
}
.side-menu-big .side-menu-header {
height: auto;
}
.side-menu-big .side-menu-apps-list {
height: auto;
position: static;
max-width: 100vw;
overflow: auto;
}
.side-menu-big .side-menu-app a {
padding: 7px 0 7px 7px;
}
.side-menu-categories-wrapper {
padding-bottom: 70px;
}
.side-menu-categories {
max-height: calc(100vh - 50px);
overflow: auto;
position: relative;
top: 50px;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 0 10% 0 10%;
}
.side-menu-category {
padding: 10px 20px;
flex: 1 1 auto;
}
.side-menu-category-title {
padding-left: 10px;
color: var(--side-menu-text-color, #fff);
}
.side-menu-big .side-menu-app-icon {
vertical-align: middle;
margin-top: -2px;
}
.side-menu-always-displayed #header,
.side-menu-always-displayed body {
width: calc(100% - 50px) !important;
}
.side-menu-always-displayed body {
position: absolute;
left: 50px;
}
.side-menu-always-displayed #side-menu {
display: block;
}
.side-menu-always-displayed .side-menu-apps-list {
top: 49px;
}
.side-menu-always-displayed #side-menu,
.side-menu-always-displayed .side-menu-header,
.side-menu-always-displayed .side-menu-apps-list {
width: 50px;
}
.side-menu-always-displayed #side-menu .side-menu-app-text,
.side-menu-always-displayed #header .side-menu-opener,
.side-menu-always-displayed .side-menu-logo {
display: none;
}
.side-menu-always-displayed #side-menu.open,
.side-menu-always-displayed #side-menu.open .side-menu-apps-list,
.side-menu-always-displayed #side-menu.open .side-menu-header,
.side-menu-always-displayed #side-menu:hover,
.side-menu-always-displayed #side-menu:hover .side-menu-apps-list,
.side-menu-always-displayed #side-menu:hover .side-menu-header {
width: 100%;
}
.side-menu-always-displayed #side-menu.open .side-menu-app-text,
.side-menu-always-displayed #side-menu:hover .side-menu-app-text {
display: inline;
}
.side-menu-always-displayed .app-navigation--close {
transform: translateX(calc(-100% + 50px)) !important;
}
@media screen and (max-width: 1024px) {
#side-menu.side-menu-big {
max-width: 290px;
}
.side-menu-categories {
display: block;
padding: 0;
}
.side-menu-category {
padding: 10px 0;
}
#side-menu.side-menu-big {
height: 100vh;
}
}
@media screen and (min-width: 1024px) {
.side-menu-closer {
display: block;
float: right;
margin-right: 9px;
}
.side-menu-big .side-menu-header {
max-width: 100%;
}
}
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583">
<g color="#000" fill="#fff">
<rect transform="rotate(135)" ry=".545" rx=".545" y="-8.209" x="-3.608" height="1.451" width="7.216" style="marker:none" overflow="visible" paint-order="stroke markers fill"/>
<rect transform="rotate(-135)" ry=".545" rx=".545" y="-.726" x="-11.091" height="1.451" width="7.216" style="marker:none" overflow="visible" paint-order="stroke markers fill"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 498 B

View File
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace OCA\SideMenu\AppInfo;
use OC;
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OC\User\User;
use OCA\SideMenu\Service\AppRepository;
use OCP\AppFramework\App;
use OCP\IUserSession;
use OCP\Util;
use Psr\Container\ContainerInterface;
/**
* class Application.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class Application extends App
{
/**
* @var OC\AllConfig
*/
protected $config;
/**
* @var ContentSecurityPolicyNonceManager
*/
protected $cspnm;
/**
* @var User
*/
protected $user;
/**
* {@inheritdoc}
*/
public function __construct(array $urlParams = [])
{
parent::__construct('side_menu', $urlParams);
$this->config = OC::$server->getConfig();
$this->cspnm = OC::$server->getContentSecurityPolicyNonceManager();
$this->user = OC::$server[IUserSession::class]->getUser();
}
/**
* Checks if this app is enabled.
*/
public function isEnabled(): bool
{
$enabled = true;
if (null !== $this->user) {
$enabled = (bool) $this->config->getUserValue($this->user->getUid(), 'side_menu', 'enabled', '1');
}
return $enabled;
}
/**
* Registes services.
*/
public function registerServices()
{
$container = $this->getContainer();
$container->registerService('AppRepository', function (ContainerInterface $c) {
return new AppRepository(new OC_App());
});
}
/**
* Registers assets.
*/
public function registerAssets()
{
Util::addScript('side_menu', 'sideMenu');
Util::addStyle('side_menu', 'sideMenu');
$stylesheet = OC::$server->getURLGenerator()->linkToRoute(
'side_menu.Css.stylesheet',
[
'v' => $this->config->getAppValue('side_menu', 'cache', '0'),
]
);
$script = OC::$server->getURLGenerator()->linkToRoute(
'side_menu.Js.script',
[
'v' => $this->config->getAppValue('side_menu', 'cache', '0'),
]
);
Util::addHeader(
'link',
[
'href' => $stylesheet,
'rel' => 'stylesheet',
],
''
);
Util::addHeader(
'script',
[
'src' => $script,
'nonce' => $this->cspnm->getNonce(),
],
''
);
}
}
+20 -4
View File
@@ -18,15 +18,16 @@
namespace OCA\SideMenu\Controller; namespace OCA\SideMenu\Controller;
use OC;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig; use OCP\IConfig;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession;
class CssController extends Controller class CssController extends Controller
{ {
/** /**
* @var \OCP\IConfig * @var \OCP\IConfig
*/ */
@@ -34,8 +35,6 @@ class CssController extends Controller
/** /**
* @param string $appName * @param string $appName
* @param IRequest $request
* @param IConfig $config
*/ */
public function __construct($appName, IRequest $request, IConfig $config) public function __construct($appName, IRequest $request, IConfig $config)
{ {
@@ -56,19 +55,36 @@ class CssController extends Controller
$backgroundColor = $this->config->getAppValue('side_menu', 'background-color', '#333333'); $backgroundColor = $this->config->getAppValue('side_menu', 'background-color', '#333333');
$backgroundColorTo = $this->config->getAppValue('side_menu', 'background-color-to', $backgroundColor); $backgroundColorTo = $this->config->getAppValue('side_menu', 'background-color-to', $backgroundColor);
$user = OC::$server[IUserSession::class]->getUser();
$topMenuApps = (array) json_decode($this->config->getAppValue('side_menu', 'top-menu-apps', '[]'), true);
if ($user) {
$userTopMenuApps = (array) json_decode($this->config->getUserValue($user->getUid(), 'side_menu', 'top-menu-apps', '[]'), true);
if (!empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps;
}
}
$parameters = [ $parameters = [
'vars' => [ 'vars' => [
'background-color' => $backgroundColor, 'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo, 'background-color-to' => $backgroundColorTo,
'current-app-background-color' => $this->config->getAppValue('side_menu', 'current-app-background-color', '#444444'), 'current-app-background-color' => $this->config->getAppValue('side_menu', 'current-app-background-color', '#444444'),
'loader-color' => $this->config->getAppValue('side_menu', 'loader-color', '#0e75ac'),
'text-color' => $this->config->getAppValue('side_menu', 'text-color', '#FFFFFF'), 'text-color' => $this->config->getAppValue('side_menu', 'text-color', '#FFFFFF'),
'opener' => $this->config->getAppValue('side_menu', 'opener', 'side-menu-opener'), 'opener' => $this->config->getAppValue('side_menu', 'opener', 'side-menu-opener'),
'icon-invert-filter' => abs((int) $this->config->getAppValue('side_menu', 'icon-invert-filter', '0')).'%',
'icon-opacity' => abs((int) $this->config->getAppValue('side_menu', 'icon-opacity', '100') / 100),
], ],
'display-logo' => (bool) $this->config->getAppValue('side_menu', 'display-logo', 1), 'display-logo' => (bool) $this->config->getAppValue('side_menu', 'display-logo', 1),
'opener-only' => (bool) $this->config->getAppValue('side_menu', 'opener-only', 0), 'opener-only' => (bool) $this->config->getAppValue('side_menu', 'opener-only', 0),
'external-sites-in-top-menu' => (bool) $this->config->getAppValue('side_menu', 'external-sites-in-top-menu', 0), 'external-sites-in-top-menu' => (bool) $this->config->getAppValue('side_menu', 'external-sites-in-top-menu', 0),
'size-icon' => $this->config->getAppValue('side_menu', 'size-icon', 'normal'), 'size-icon' => $this->config->getAppValue('side_menu', 'size-icon', 'normal'),
'size-text' => $this->config->getAppValue('side_menu', 'size-text', 'normal'), 'size-text' => $this->config->getAppValue('side_menu', 'size-text', 'normal'),
'always-displayed' => (bool) $this->config->getAppValue('side_menu', 'always-displayed', '0'),
'big-menu' => (bool) $this->config->getAppValue('side_menu', 'big-menu', '0'),
'top-menu-apps' => $topMenuApps,
]; ];
$response = new TemplateResponse('side_menu', 'css/stylesheet', $parameters, 'blank'); $response = new TemplateResponse('side_menu', 'css/stylesheet', $parameters, 'blank');
+18 -4
View File
@@ -18,15 +18,16 @@
namespace OCA\SideMenu\Controller; namespace OCA\SideMenu\Controller;
use OC;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig; use OCP\IConfig;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession;
class JsController extends Controller class JsController extends Controller
{ {
/** /**
* @var \OCP\IConfig * @var \OCP\IConfig
*/ */
@@ -34,8 +35,6 @@ class JsController extends Controller
/** /**
* @param string $appName * @param string $appName
* @param IRequest $request
* @param IConfig $config
*/ */
public function __construct($appName, IRequest $request, IConfig $config) public function __construct($appName, IRequest $request, IConfig $config)
{ {
@@ -53,12 +52,27 @@ class JsController extends Controller
*/ */
public function script() public function script()
{ {
$user = OC::$server[IUserSession::class]->getUser();
$topMenuApps = (array) json_decode($this->config->getAppValue('side_menu', 'top-menu-apps', '[]'), true);
if ($user) {
$userTopMenuApps = (array) json_decode($this->config->getUserValue($user->getUid(), 'side_menu', 'top-menu-apps', '[]'), true);
if (!empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps;
}
}
$parameters = [ $parameters = [
'opener-position' => $this->config->getAppValue('side_menu', 'opener-position', 'before'), 'opener-position' => $this->config->getAppValue('side_menu', 'opener-position', 'before'),
'opener-hover' => (bool) $this->config->getAppValue('side_menu', 'opener-hover', '0'), 'opener-hover' => (bool) $this->config->getAppValue('side_menu', 'opener-hover', '0'),
'external-sites-in-top-menu' => (bool) $this->config->getAppValue('side_menu', 'external-sites-in-top-menu', 0), 'external-sites-in-top-menu' => (bool) $this->config->getAppValue('side_menu', 'external-sites-in-top-menu', 0),
'force-light-icon' => (bool) $this->config->getAppValue('side_menu', 'force-light-icon', '0'), 'force-light-icon' => (bool) $this->config->getAppValue('side_menu', 'force-light-icon', '0'),
'hide-when-no-apps' => (bool) $this->config->getAppValue('side_menu', 'hide-when-no-apps', '0'), 'hide-when-no-apps' => (bool) $this->config->getAppValue('side_menu', 'hide-when-no-apps', '0'),
'loader-enabled' => (bool) $this->config->getAppValue('side_menu', 'loader-enabled', '1'),
'always-displayed' => (bool) $this->config->getAppValue('side_menu', 'always-displayed', '0'),
'big-menu' => (bool) $this->config->getAppValue('side_menu', 'big-menu', '0'),
'top-menu-apps' => $topMenuApps,
]; ];
$response = new TemplateResponse('side_menu', 'js/script', $parameters, 'blank'); $response = new TemplateResponse('side_menu', 'js/script', $parameters, 'blank');
+208
View File
@@ -0,0 +1,208 @@
<?php
/**
* @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/>.
*/
namespace OCA\SideMenu\Controller;
use OC;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\URLGenerator;
use OCA\SideMenu\Service\AppRepository;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\L10N\IFactory;
class NavController extends Controller
{
/**
* @var IConfig
*/
protected $config;
/**
* @var AppRepository
*/
protected $appRepository;
/**
* @var IFactory
*/
protected $l10nFactory;
/**
* @var CategoryFetcher
*/
protected $categoryFetcher;
/**
* @var URLGenerator
*/
protected $router;
/**
* @param string $appName
*/
public function __construct(
$appName,
IRequest $request,
IConfig $config,
AppRepository $appRepository,
CategoryFetcher $categoryFetcher,
URLGenerator $router,
IL10N $trans,
IFactory $l10nFactory)
{
parent::__construct($appName, $request);
$this->config = $config;
$this->appRepository = $appRepository;
$this->categoryFetcher = $categoryFetcher;
$this->l10nFactory = $l10nFactory;
$this->router = $router;
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @return JSONResponse
*/
public function items()
{
$apps = $this->appRepository->getVisibleApps();
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$categoriesLabels = $this->categoryFetcher->get();
$externalSitesInTopMenu = (bool) $this->config->getAppValue('side_menu', 'external-sites-in-top-menu', 0);
$user = OC::$server[IUserSession::class]->getUser();
$appsCategories = [];
$categoriesAppsCount = [];
$items = [];
if (!$user) {
return new JSONResponse([
'items' => $items,
]);
}
$topMenuApps = (array) json_decode($this->config->getAppValue('side_menu', 'top-menu-apps', '[]'), true);
$userTopMenuApps = (array) json_decode($this->config->getUserValue($user->getUid(), 'side_menu', 'top-menu-apps', '[]'), true);
if (!empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps;
}
foreach ($categoriesLabels as $k => $category) {
$categoriesLabels[$category['id']] = $category['translations'][$currentLanguage]['name'] ?? $category['translations']['en']['name'];
unset($categoriesLabels[$k]);
}
$categoriesLabels['external_links'] = $this->l10nFactory->get('external')->t('External sites');
$items['other'] = [
'name' => '',
'apps' => [],
];
foreach ($apps as $app) {
if (in_array($app['id'], $topMenuApps)) {
continue;
}
$categories = (array) $app['category'];
$appsCategories[$app['id']] = [];
if (empty($categories)) {
$categories = ['other'];
}
foreach ($categories as $category) {
if (!isset($items[$category])) {
$items[$category] = [
'name' => $categoriesLabels[$category],
'apps' => [],
];
}
if (!isset($categoriesAppsCount[$category])) {
$categoriesAppsCount[$category] = 0;
}
++$categoriesAppsCount[$category];
$appsCategories[$app['id']][] = $category;
$items[$category]['apps'][$app['id']] = [
'name' => $app['name'],
'href' => $app['href'],
'icon' => $app['icon'],
];
}
}
arsort($categoriesAppsCount);
$keys = array_keys($categoriesAppsCount);
foreach ($appsCategories as $app => $appCategories) {
$smallerIndex = count($categoriesAppsCount) - 1;
foreach ($appCategories as $appCategory) {
$appKey = array_keys($keys, $appCategory)[0];
if ($appKey < $smallerIndex) {
$smallerIndex = $appKey;
}
}
$category = $keys[$smallerIndex];
foreach ($items as $itemCategory => $value) {
if ($itemCategory !== $category && isset($value['apps'][$app])) {
unset($items[$itemCategory]['apps'][$app]);
if (empty($items[$itemCategory]['apps'])) {
unset($items[$itemCategory]);
}
}
}
}
foreach ($items as $category => $value) {
if (empty($items[$category]['apps'])) {
unset($items[$category]);
} else {
uasort($items[$category]['apps'], function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1;
});
}
}
usort($items, function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1;
});
return new JSONResponse([
'items' => $items,
]);
}
}
+25 -3
View File
@@ -19,7 +19,6 @@
namespace OCA\SideMenu\Controller; namespace OCA\SideMenu\Controller;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
use OCP\IConfig; use OCP\IConfig;
use OCP\IRequest; use OCP\IRequest;
@@ -48,6 +47,9 @@ class PersonalSettingController extends Controller
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
* @param mixed $name
* @param mixed $value
*
* @return Response * @return Response
*/ */
public function valueSet($name, $value) public function valueSet($name, $value)
@@ -55,7 +57,7 @@ class PersonalSettingController extends Controller
$doSave = false; $doSave = false;
$user = $this->userSession->getUser(); $user = $this->userSession->getUser();
if ($name === 'enabled') { if ('enabled' === $name) {
$doSave = true; $doSave = true;
if (!in_array($value, ['0', '1'])) { if (!in_array($value, ['0', '1'])) {
@@ -63,8 +65,28 @@ class PersonalSettingController extends Controller
} }
} }
if ('top-menu-apps' === $name) {
$doSave = true;
$data = json_decode($value, true);
if (!is_array($data)) {
$doSave = false;
} else {
foreach ($data as $v) {
if (!is_string($v)) {
$doSave = false;
}
}
}
}
if ($doSave) { if ($doSave) {
$this->config->setUserValue($user->getUid(), 'side_menu', 'enabled', $value); $this->config->setUserValue($user->getUid(), 'side_menu', $name, $value);
return [
'name' => $name,
'value' => $value,
];
} }
return []; return [];
+84
View File
@@ -0,0 +1,84 @@
<?php
namespace OCA\SideMenu\Service;
use OC_App;
use OCP\L10N\IFactory;
/**
* class AppRepository.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AppRepository
{
/**
* @var OC_App
*/
protected $ocApp;
/**
* @var IFactory
*/
protected $l10nFactory;
public function __construct(OC_App $ocApp, IFactory $l10nFactory)
{
$this->ocApp = $ocApp;
$this->l10nFactory = $l10nFactory;
}
/**
* Retrieves visibles apps.
*
* @return array
*/
public function getVisibleApps()
{
$navigation = $this->ocApp->getNavigation();
$apps = $this->ocApp->listAllApps();
$visibleApps = [];
foreach ($apps as $app) {
$id = $app['id'];
foreach ([$app['id'], $app['id'].'_index'] as $id) {
if (isset($navigation[$id])) {
$app['name'] = $this->l10nFactory->get($id)->t($app['name']);
$app['href'] = $navigation[$id]['href'];
$app['icon'] = $navigation[$id]['icon'];
$visibleApps[$id] = $app;
}
}
}
foreach ($navigation as $app) {
if ('external_index' === substr($app['id'], 0, 14)) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->l10nFactory->get($app['id'])->t($app['name']),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [
'external_links',
],
];
} elseif ('files' === $app['id']) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->l10nFactory->get($app['id'])->t($app['name']),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [],
];
}
}
usort($visibleApps, function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1;
});
return $visibleApps;
}
}
+19 -6
View File
@@ -18,11 +18,12 @@
namespace OCA\SideMenu\Settings; namespace OCA\SideMenu\Settings;
use OCA\SideMenu\Service\AppRepository;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use OCP\ILogger;
use OCP\Settings\ISettings; use OCP\Settings\ISettings;
use OCP\IConfig;
class Admin implements ISettings class Admin implements ISettings
{ {
@@ -41,11 +42,17 @@ class Admin implements ISettings
*/ */
private $config; private $config;
public function __construct(IL10N $l, ILogger $logger, IConfig $config) /**
* @var AppRepository
*/
private $appRepository;
public function __construct(IL10N $l, ILogger $logger, IConfig $config, AppRepository $appRepository)
{ {
$this->l = $l; $this->l = $l;
$this->logger = $logger; $this->logger = $logger;
$this->config = $config; $this->config = $config;
$this->appRepository = $appRepository;
} }
/** /**
@@ -60,10 +67,15 @@ class Admin implements ISettings
'background-color' => $backgroundColor, 'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo, 'background-color-to' => $backgroundColorTo,
'current-app-background-color' => $this->config->getAppValue('side_menu', 'current-app-background-color', '#444444'), 'current-app-background-color' => $this->config->getAppValue('side_menu', 'current-app-background-color', '#444444'),
'loader-color' => $this->config->getAppValue('side_menu', 'loader-color', '#0e75ac'),
'icon-invert-filter' => (int) $this->config->getAppValue('side_menu', 'icon-invert-filter', '0'),
'icon-opacity' => (int) $this->config->getAppValue('side_menu', 'icon-opacity', '100'),
'loader-enabled' => $this->config->getAppValue('side_menu', 'loader-enabled', '1'),
'text-color' => $this->config->getAppValue('side_menu', 'text-color', '#FFFFFF'), 'text-color' => $this->config->getAppValue('side_menu', 'text-color', '#FFFFFF'),
'force-light-icon' => $this->config->getAppValue('side_menu', 'force-light-icon', '0'),
'cache' => $this->config->getAppValue('side_menu', 'cache', '0'), 'cache' => $this->config->getAppValue('side_menu', 'cache', '0'),
'opener' => $this->config->getAppValue('side_menu', 'opener', 'side-menu-opener'), 'opener' => $this->config->getAppValue('side_menu', 'opener', 'side-menu-opener'),
'always-displayed' => $this->config->getAppValue('side_menu', 'always-displayed', '0'),
'big-menu' => $this->config->getAppValue('side_menu', 'big-menu', '0'),
'display-logo' => $this->config->getAppValue('side_menu', 'display-logo', '1'), 'display-logo' => $this->config->getAppValue('side_menu', 'display-logo', '1'),
'opener-position' => $this->config->getAppValue('side_menu', 'opener-position', 'before'), 'opener-position' => $this->config->getAppValue('side_menu', 'opener-position', 'before'),
'opener-hover' => $this->config->getAppValue('side_menu', 'opener-hover', '0'), 'opener-hover' => $this->config->getAppValue('side_menu', 'opener-hover', '0'),
@@ -71,7 +83,8 @@ class Admin implements ISettings
'hide-when-no-apps' => $this->config->getAppValue('side_menu', 'hide-when-no-apps', '0'), 'hide-when-no-apps' => $this->config->getAppValue('side_menu', 'hide-when-no-apps', '0'),
'size-icon' => $this->config->getAppValue('side_menu', 'size-icon', 'normal'), 'size-icon' => $this->config->getAppValue('side_menu', 'size-icon', 'normal'),
'size-text' => $this->config->getAppValue('side_menu', 'size-text', 'normal'), 'size-text' => $this->config->getAppValue('side_menu', 'size-text', 'normal'),
'external-sites-in-top-menu' => $this->config->getAppValue('side_menu', 'external-sites-in-top-menu', '0'), 'top-menu-apps' => (array) json_decode($this->config->getAppValue('side_menu', 'top-menu-apps', '[]'), true),
'apps' => $this->appRepository->getVisibleApps(),
]; ];
return new TemplateResponse('side_menu', 'settings/admin-form', $parameters, ''); return new TemplateResponse('side_menu', 'settings/admin-form', $parameters, '');
@@ -87,8 +100,8 @@ class Admin implements ISettings
/** /**
* @return int whether the form should be rather on the top or bottom of * @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the * the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100. * priority values. It is required to return a value between 0 and 100.
* *
* E.g.: 70 * E.g.: 70
*/ */
+10 -1
View File
@@ -24,6 +24,7 @@ use OCP\ILogger;
use OCP\Settings\ISettings; use OCP\Settings\ISettings;
use OCP\IConfig; use OCP\IConfig;
use OCP\IUserSession; use OCP\IUserSession;
use OCA\SideMenu\Service\AppRepository;
class Personal implements ISettings class Personal implements ISettings
{ {
@@ -47,12 +48,18 @@ class Personal implements ISettings
*/ */
private $userSession; private $userSession;
public function __construct(IL10N $l, ILogger $logger, IConfig $config, IUserSession $userSession) /**
* @var AppRepository
*/
private $appRepository;
public function __construct(IL10N $l, ILogger $logger, IConfig $config, IUserSession $userSession, AppRepository $appRepository)
{ {
$this->l = $l; $this->l = $l;
$this->logger = $logger; $this->logger = $logger;
$this->config = $config; $this->config = $config;
$this->userSession = $userSession; $this->userSession = $userSession;
$this->appRepository = $appRepository;
} }
/** /**
@@ -64,6 +71,8 @@ class Personal implements ISettings
$parameters = [ $parameters = [
'enabled' => $this->config->getUserValue($user->getUid(), 'side_menu', 'enabled', '1'), 'enabled' => $this->config->getUserValue($user->getUid(), 'side_menu', 'enabled', '1'),
'top-menu-apps' => (array) json_decode($this->config->getUserValue($user->getUid(), 'side_menu', 'top-menu-apps', '[]'), true),
'apps' => $this->appRepository->getVisibleApps(),
]; ];
return new TemplateResponse('side_menu', 'settings/personal-form', $parameters, ''); return new TemplateResponse('side_menu', 'settings/personal-form', $parameters, '');
+1
View File
@@ -13,6 +13,7 @@
"dependencies": { "dependencies": {
"@nextcloud/axios": "^1.3.2", "@nextcloud/axios": "^1.3.2",
"@nextcloud/vue": "^1.4.0", "@nextcloud/vue": "^1.4.0",
"axios": "^0.19.2",
"trim": "0.0.1", "trim": "0.0.1",
"vue": "^2.6.11" "vue": "^2.6.11"
}, },
Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

+13 -3
View File
@@ -17,18 +17,28 @@
import Vue from 'vue' import Vue from 'vue'
import SideMenu from './SideMenu.vue' import SideMenu from './SideMenu.vue'
import SideMenuBig from './SideMenuBig.vue'
// Vue.prototype.t = t // Vue.prototype.t = t
// Vue.prototype.OC = OC Vue.prototype.OC = OC
// Vue.prototype.OC = OCP // Vue.prototype.OC = OCP
const View = Vue.extend(SideMenu)
const sideMenu = new View({})
const mountSideMenuComponent = () => { const mountSideMenuComponent = () => {
const sideMenuContainer = document.querySelector('#side-menu') const sideMenuContainer = document.querySelector('#side-menu')
if (sideMenuContainer) { if (sideMenuContainer) {
let component
if (sideMenuContainer.getAttribute('data-bigmenu')) {
component = SideMenuBig
} else {
component = SideMenu
}
const View = Vue.extend(component)
const sideMenu = new View({})
sideMenu.$mount('#side-menu') sideMenu.$mount('#side-menu')
$('body').trigger('side-menu.ready') $('body').trigger('side-menu.ready')
+7 -2
View File
@@ -51,6 +51,12 @@ export default {
retrieveApps() { retrieveApps() {
this.apps = [] this.apps = []
const links = document.querySelectorAll('#appmenu a') 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) { for (let element of links) {
let href = element.getAttribute('href') let href = element.getAttribute('href')
@@ -63,7 +69,7 @@ export default {
var dataId = parent.getAttribute('data-id') var dataId = parent.getAttribute('data-id')
dataId = dataId !== null ? dataId : '' dataId = dataId !== null ? dataId : ''
if (this.ignoreExternalSites && dataId.indexOf('external_index') !== -1) { if (!parent.classList.contains('app-hidden') && !menuIsHidden) {
continue continue
} }
@@ -113,7 +119,6 @@ export default {
this.retrieveApps() this.retrieveApps()
this.retrieveLogo() this.retrieveLogo()
this.forceLightIcon = document.querySelector('#side-menu-container').getAttribute('data-forcelighticon') == 1; this.forceLightIcon = document.querySelector('#side-menu-container').getAttribute('data-forcelighticon') == 1;
this.ignoreExternalSites = document.querySelector('#side-menu-container').getAttribute('data-externalsitesintopmenu') == 1;
const menu = document.querySelector('#appmenu') const menu = document.querySelector('#appmenu')
+86
View File
@@ -0,0 +1,86 @@
<!--
@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>
<div id="side-menu" class="side-menu-big">
<div class="side-menu-header">
<button class="side-menu-opener side-menu-closer"></button>
<button class="side-menu-opener"></button>
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<div class="side-menu-category" v-for="category in items">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-html="category.name"></h2>
<ul class="side-menu-apps-list">
<li v-bind:class="{'side-menu-app': true, 'active': activeApp === appId}" v-for="(app, appId) in category.apps">
<a v-bind:href="app.href" v-bind:title="app.name">
<img class="side-menu-app-icon" v-bind:src="app.icon"></span>
<span class="side-menu-app-text" v-html="app.name"></span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import trim from 'trim'
import axios from 'axios'
export default {
name: 'SideMenuBig',
data() {
return {
items: [],
activeApp: 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])
}
}
jQuery('body').trigger('side-menu.apps', [apps])
});
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
},
mounted() {
this.retrieveApps()
this.retrieveActiveApp()
}
}
</script>
+20 -4
View File
@@ -32,12 +32,28 @@ const appConfig = (name, value, callbacks) => {
const saveSettings = (key) => { const saveSettings = (key) => {
const element = elements.get(key) const element = elements.get(key)
const name = $(element).attr('name') let value
let value = $(element).val() let name
if ($(element).is('[data-checkbox]')) {
name = $(element).attr('data-name')
const inputs = $('input[name="' + name + '[]"]:checked')
value = []
inputs.each((i, v) => {
value.push(v.value)
})
value = JSON.stringify(value)
} else {
name = $(element).attr('name')
value = $(element).val()
}
const size = elements.length const size = elements.length
if (element === 'side-menu-cache') { if (name === 'cache') {
value++ ++value
} }
const callbacks = { const callbacks = {
+52
View File
@@ -0,0 +1,52 @@
"Side menu": "Seitenmenü"
"Enable the side menu": "Aktivieren Sie das Seitenmenü"
"No": "Nein"
"Yes": "Ja"
"Menu": "Speisekarte"
'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.': 'Verwenden Sie die Tastenkombination <span class="keyboard-key">Strg</span>+<span class="keyboard-key">o</span>, um das Seitenmenü zu öffnen und auszublenden. Verwenden Sie zum Navigieren die Registerkarte <span class="keyboard-key">tab</span>.'
"Top menu": "Hauptmenü"
"Apps that not must be moved in the side menu": "Apps, die nicht im Seitenmenü verschoben werden müssen"
"If there is no selection then the global configuration is applied.": "Wenn keine Auswahl vorhanden ist, wird die globale Konfiguration angewendet."
"Experimental": "Experimental"
"Save": "Sparen"
"You like this app and you want to support me?": "Du magst diese App und möchtest mich unterstützen?"
"Buy me a coffe ☕": "Gib mir einen Kaffee ☕"
"Hidden": "Versteckt"
"Small": "Klein"
"Normal": "Normal"
"Big": "Groß"
"Colors": "Farben"
"Background color": "Hintergrundfarbe"
"Background color of current app": "Hintergrundfarbe der aktuellen App"
"Text color": "Textfarbe"
"Loader": "Ladestandanzeige"
"Icon": "Symbol"
"Same color": "Selbe Farbe"
"Opposite color": "Gegenfarbe"
"Transparent": "Transparent"
"Opaque": "Undurchsichtig"
"Opener": "Öffner"
"Default": "Standard"
"Default (dark)": "Standard (dunkel)"
"Hamburger": "Hamburger"
"Hamburger (dark)": "Hamburger (dunkel)"
"Hamburger 2": "Hamburger 2"
"Hamburger 2 (dark)": "Hamburger 2 (dunkel)"
"Before the logo": "Vor dem Logo"
"After the logo": "Nach dem Logo"
"Position": "Position"
"Show only the opener (hidden logo)": "Nur den Öffner anzeigen (verstecktes Logo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Zeigen Sie das Seitenmenü und den Öffner nicht an, wenn keine Anwendung vorhanden ist (z. B. öffentliche Seiten)."
"Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Öffnen Sie das Menü, wenn Sie die Maus über den Öffner bewegen (auf Touchscreens automatisch deaktiviert)."
"Display the big menu": "Großes Menü anzeigen"
"The big menu is not compatible with AppOrder.": "Le menu large n'est pas compatible avec l'application AppOrder"
"Display the logo": "Logo anzeigen"
"This feature is not compatible with the <code>big menu</code> display.": "Diese Funktion ist nicht mit der großen Menüanzeige kompatibel."
"Icons and texts": "Symbole und Texte"
"Loader enabled": "Loader aktiviert"
"Tips": "Tipps"
"Always displayed": "Wird immer angezeigt"
"The logo will be hidden when the menu is always displayed.": "Das Logo wird ausgeblendet, wenn das Menü immer angezeigt wird."
"This is the automatic behavior when the menu is always displayed.": "Dies ist das automatische Verhalten, wenn das Menü immer angezeigt wird."
"Not compatible with touch screens.": "Nicht kompatibel mit Touchscreens."
+52
View File
@@ -0,0 +1,52 @@
"Side menu": "Menu latéral"
"Enable the side menu": "Activer le menu latéral"
"No": "Non"
"Yes": "Oui"
"Menu": "Menu"
'Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.': 'Utiliser le raccourcis clavier <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> pour ouvrir et fermer le menu latéral. Utiliser <span class="keyboard-key">tab</span> pour naviguer.'
"Top menu": "Menu supérieur"
"Apps that not must be moved in the side menu": "Les applications qui ne doivent pas être affichées dans le menu latéral"
"If there is no selection then the global configuration is applied.": "Si il n'y a aucune sélection alors la configuration globale sera appliquée"
"Experimental": "Expérimental"
"Save": "Sauvegarder"
"You like this app and you want to support me?": "Vous aimer cette application et vous souhaitez m'aider ?"
"Buy me a coffe ☕": "Offrez moi un café ☕"
"Hidden": "Caché"
"Small": "Petit"
"Normal": "Normal"
"Big": "Gros"
"Colors": "Couleurs"
"Background color": "Couleur de fond"
"Background color of current app": "Couleur de fond de l'application en cours"
"Text color": "Couleur du texte"
"Loader": "Indicateur de chargement"
"Icon": "Icône"
"Same color": "Même couleur"
"Opposite color": "Couleur opposée"
"Transparent": "Transparent"
"Opaque": "Opaque"
"Opener": "Bouton d'ouverture"
"Default": "Par défaut"
"Default (dark)": "Par défaut (sombre)"
"Hamburger": "Hamburger"
"Hamburger (dark)": "Hamburger (sombre)"
"Hamburger 2": "Hamburger 2"
"Hamburger 2 (dark)": "Hamburger 2 (sombre)"
"Before the logo": "Avant le logo"
"After the logo": "Après le logo"
"Position": "Position"
"Show only the opener (hidden logo)": "Afficher uniquement le bouton d'ouverture (masquer le logo)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Ne pas afficher le menu latéral et le bouton d'ouverture s'il n'y a aucune application (exemple : page publiques)."
"Panel": "Panneau"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Ouvrir le menu au passage de la souris (automatiquement désactivé sur les écrans tactiles)"
"Display the big menu": "Afficher le menu large"
"The big menu is not compatible with AppOrder.": "Le menu large n'est pas compatible avec l'application AppOrder"
"Display the logo": "Afficher le logo"
"This feature is not compatible with the <code>big menu</code> display.": "Cette fonctionnalité n'est pas compatible avec l'affichage du menu large."
"Icons and texts": "Icônes et textes"
"Loader enabled": "Activation de l'indicateur de chargement"
"Tips": "Astuces"
"Always displayed": "Toujours affiché"
"The logo will be hidden when the menu is always displayed.": "Le logo sera masque si le menu est toujours affiché."
"This is the automatic behavior when the menu is always displayed.": "C'est le comportement automatique lorsque le menu est toujours affiché."
"Not compatible with touch screens.": "Incompatible avec les écrans tactiles."
+18 -4
View File
@@ -8,7 +8,7 @@
<?php endforeach; ?> <?php endforeach; ?>
} }
<?php if ($_['external-sites-in-top-menu'] === false): ?> <?php if (empty($_['top-menu-apps'])): ?>
#appmenu { #appmenu {
display: none; display: none;
} }
@@ -22,14 +22,13 @@
} }
<?php endif; ?> <?php endif; ?>
<?php if ($_['opener-only']): ?>
<?php if ($_['opener-only'] === true): ?>
#nextcloud { #nextcloud {
display: none; display: none;
} }
<?php endif; ?> <?php endif; ?>
<?php if ($_['display-logo'] === false): ?> <?php if ($_['display-logo']): ?>
.side-menu-logo { .side-menu-logo {
display: none; display: none;
} }
@@ -75,16 +74,31 @@
width: 15px; width: 15px;
height: 15px; height: 15px;
} }
img.side-menu-app-icon {
width: 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 {
width: 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 {
width: 23px;
height: 23px;
}
<?php endif; ?> <?php endif; ?>
<?php if ($_['size-text'] === 'hidden'): ?> <?php if ($_['size-text'] === 'hidden'): ?>
+44
View File
@@ -0,0 +1,44 @@
var alwaysDisplayed = function() {
var elements = document.querySelectorAll('*');
var fixedElements = []
for (var i in elements) {
var element = elements[i]
if (typeof element !== 'object') {
continue
}
var position = window.getComputedStyle(element, null).getPropertyValue('position');
if (position !== 'fixed') {
continue
}
var id = element.getAttribute('id')
if (id === 'header' || id === 'side-menu' || id === 'side-menu-loader') {
continue
}
if (element.classList.contains('oc-dialog')) {
continue
}
if (jQuery(element).parents('#side-menu').length) {
continue
}
fixedElements.push(element)
}
for (var i in fixedElements) {
var element = fixedElements[i]
var value = window.getComputedStyle(element, null).getPropertyValue('left')
var intValue = parseInt(value.replace('px', ''))
element.style.setProperty('transform', 'translateX(' + (intValue + 50) + 'px)')
}
}
alwaysDisplayed()
+15
View File
@@ -0,0 +1,15 @@
var pageLoader = jQuery('<div id="side-menu-loader">')
var pageLoaderBar = jQuery('<div id="side-menu-loader-bar">')
body.append(pageLoader)
pageLoader.append(pageLoaderBar)
var pageLoaderValue = 0
$(window).on('beforeunload', function() {
setInterval(function() {
pageLoaderBar.width(pageLoaderValue.toString() + '%')
pageLoaderValue = Math.min(pageLoaderValue + .2, 100)
}, 25)
})
+146
View File
@@ -0,0 +1,146 @@
var menuCache = null
var updateTopMenu = function() {
var breakpointMobileWidth = 1024
var menu = jQuery('#appmenu')
var apps = menu.find('li')
var minAppsDesktop = 8
var usePercentualAppMenuLimit = 0.8
var isMobile = jQuery(window).width() < breakpointMobileWidth
var lastShownApp = null
var appShown = []
var moreApps = jQuery('#more-apps')
var navigation = jQuery('#navigation')
var navigationApps = jQuery('#apps ul')
var appCount = null
var currentMenuCache = menu.html() + menu.next().html()
if (currentMenuCache === menuCache) {
return
}
navigationApps.html('')
apps.each(function(i, app) {
var dataId = app.getAttribute('data-id')
if (dataId === null) {
return
}
if (topMenuApps.indexOf(dataId) === -1) {
app.classList.add('hidden')
app.classList.add('app-hidden')
} else {
app.classList.remove('hidden')
app.classList.add('app-external-site')
appShown.push(app)
navigationApps.append(app.outerHTML)
}
})
var rightHeaderWidth = jQuery('.header-right').outerWidth()
var headerWidth = jQuery('header').outerWidth()
var availableWidth = headerWidth - jQuery('#nextcloud').outerWidth()
- jQuery('#header .side-menu-opener').outerWidth()
- (rightHeaderWidth > 230 ? rightHeaderWidth : 230)
if (!isMobile) {
availableWidth = availableWidth * usePercentualAppMenuLimit
}
appCount = Math.floor(availableWidth / jQuery('#appmenu li').width())
if (isMobile && appCount > minAppsDesktop) {
appCount = minAppsDesktop
} else if (!isMobile && appCount < minAppsDesktop) {
appCount = minAppsDesktop
}
if (appCount === 0) {
menu.addClass('hidden')
}
menu.removeClass('hidden')
menu.css('opacity', 1)
if (appShown.length - 1 - appCount >= 1) {
appCount--
}
moreApps.find('a').removeClass('active')
var k = 0
var notInHeader = 0
var name
jQuery(appShown).each(function(i, app) {
app = jQuery(app)
name = app.data('id')
if (k < appCount && appCount > 0) {
app.removeClass('hidden')
lastShownApp = app
jQuery('#apps li[data-id=' + name + '].app-external-site').addClass('in-header')
} else {
app.addClass('hidden')
notInHeader++
jQuery('#apps li[data-id=' + name + '].app-external-site').removeClass('in-header')
if (appCount > 0 && app.children('a').hasClass('active')) {
lastShownApp.addClass('hidden')
app.removeClass('hidden')
notInHeader++
jQuery('#apps li[data-id=' + name + '].app-external-site')
.removeClass('in-header')
.addClass('in-header')
}
}
k++
})
jQuery('#apps li.app-external-site').each(function(i, app) {
app = jQuery(app)
var appId = app.attr('data-id')
if (app.hasClass('in-header')) {
app.find('svg').find('defs').remove()
} else {
var svg = app.find('svg');
if (svg.find('defs').length > 0) {
return;
}
var 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.prepend(defs)
svg.find('image').attr('filter', `url(#invertMenuMore-${appId})`)
var html = svg.get(0).innerHTML.replace(/fecolormatrix/g, 'feColorMatrix');
svg.html(html)
}
})
if (notInHeader === 0) {
moreApps.hide()
navigation.hide()
} else {
moreApps.show()
}
menuCache = menu.html() + menu.next().html()
}
setInterval(updateTopMenu, 50)
+76 -117
View File
@@ -1,32 +1,67 @@
(function() { (function() {
var sideMenuContainer = $('<div id="side-menu-container">') var sideMenuContainer = jQuery('<div id="side-menu-container">')
var sideMenuOpener = $('<button class="side-menu-opener"></button>') var sideMenuOpener = jQuery('<button class="side-menu-opener"></button>')
var sideMenu = $('<div id="side-menu">') var sideMenu = jQuery('<div id="side-menu">')
var body = $('body') var body = jQuery('body')
var html = jQuery('html')
var isTouchDevice = window.matchMedia("(pointer: coarse)").matches var isTouchDevice = window.matchMedia("(pointer: coarse)").matches
<?php if ($_['big-menu']): ?>
sideMenu.attr('data-bigmenu', '1')
<?php endif; ?>
<?php if ($_['force-light-icon']): ?> <?php if ($_['force-light-icon']): ?>
sideMenuContainer.attr('data-forcelighticon', '1') sideMenuContainer.attr('data-forcelighticon', '1')
<?php endif; ?> <?php endif; ?>
body.on('side-menu.apps', function(e, apps) { body.on('side-menu.apps', function(e, apps) {
<?php if ($_['hide-when-no-apps']): ?> <?php if ($_['hide-when-no-apps']): ?>
sideMenu = $('#side-menu') sideMenu = jQuery('#side-menu')
if (apps.length === 0) { if (apps.length === 0) {
sideMenu.removeClass('open') sideMenu.removeClass('open')
sideMenu.addClass('hide')
sideMenuOpener.addClass('hide') sideMenuOpener.addClass('hide')
} else { } else {
sideMenu.removeClass('hide')
sideMenuOpener.removeClass('hide') sideMenuOpener.removeClass('hide')
} }
<?php if ($_['always-displayed'] && !$_['big-menu']): ?>
if (apps.length === 0) {
html.removeClass('side-menu-always-displayed');
} else {
html.addClass('side-menu-always-displayed');
}
<?php endif; ?>
<?php else: ?>
<?php if ($_['always-displayed'] && !$_['big-menu']): ?>
if (apps.length === 0) {
html.removeClass('side-menu-always-displayed');
} else {
html.addClass('side-menu-always-displayed');
}
<?php endif; ?>
<?php endif; ?> <?php endif; ?>
}) })
body.on('side-menu.ready', function() { body.on('side-menu.ready', function() {
sideMenu = $('#side-menu') sideMenu = jQuery('#side-menu')
var headerMenuOpener = $('#header .side-menu-opener') var headerMenuOpener = jQuery('#header .side-menu-opener')
var sideMenuOpener = $('#side-menu .side-menu-opener') var sideMenuOpener = jQuery('#side-menu .side-menu-opener')
sideMenuFocus = function() {
var a = sideMenu.find('.side-menu-app.active a')
if (a.length === 0) {
a = sideMenu.find('.side-menu-app:first-child a')
}
if (a.length > 0) {
a.focus()
}
}
<?php if ($_['opener-hover']): ?> <?php if ($_['opener-hover']): ?>
var sideMenuMouseLeave = function() { var sideMenuMouseLeave = function() {
@@ -43,6 +78,8 @@
sideMenu sideMenu
.addClass('open') .addClass('open')
.on('mouseenter', sideMenuMouseEnter) .on('mouseenter', sideMenuMouseEnter)
sideMenuFocus()
} }
if (!isTouchDevice) { if (!isTouchDevice) {
@@ -53,130 +90,52 @@
<?php endif; ?> <?php endif; ?>
headerMenuOpener.on('click', function() { headerMenuOpener.on('click', function() {
sideMenu.addClass('open'); sideMenu.addClass('open')
sideMenu.find('.side-menu-app.active a').focus()
}) })
sideMenuOpener.on('click', function() { <?php if ($_['always-displayed'] && !$_['big-menu']): ?>
sideMenu.removeClass('open'); sideMenuOpener.on('click', function() {
sideMenu.toggleClass('open')
})
<?php else: ?>
sideMenuOpener.on('click', function() {
sideMenu.removeClass('open')
})
<?php endif; ?>
jQuery(document).keydown(function(e) {
var key = e.key || e.keyCode
if ((key === 'o' || key === 79) && e.ctrlKey === true) {
e.preventDefault()
sideMenu.toggleClass('open')
sideMenuFocus()
}
}) })
}) })
body.append(sideMenuContainer) body.append(sideMenuContainer)
sideMenuContainer.append(sideMenu) sideMenuContainer.append(sideMenu)
<?php if ($_['loader-enabled'] === true): ?>
<?php require_once __DIR__.'/_loaderEnabled.js'; ?>
<?php endif; ?>
<?php if ($_['opener-position'] === 'before'): ?> <?php if ($_['opener-position'] === 'before'): ?>
sideMenuOpener.insertBefore('#nextcloud') sideMenuOpener.insertBefore('#nextcloud')
<?php else: ?> <?php else: ?>
sideMenuOpener.insertAfter('#nextcloud') sideMenuOpener.insertAfter('#nextcloud')
<?php endif; ?> <?php endif; ?>
<?php if ($_['external-sites-in-top-menu']): ?> <?php if (!empty($_['top-menu-apps'])): ?>
sideMenuContainer.attr('data-externalsitesintopmenu', '1') var topMenuApps = <?php echo json_encode($_['top-menu-apps']); ?>;
var updateTopMenu = function() { <?php require_once __DIR__.'/_topMenuApps.js'; ?>
var breakpointMobileWidth = 1024 <?php endif; ?>
var menu = $('#appmenu')
var apps = menu.find('li')
var minAppsDesktop = 8
var usePercentualAppMenuLimit = 0.9
var isMobile = $(window).width() < breakpointMobileWidth
var lastShownApp = null
var appShown = []
var moreApps = $('#more-apps')
var navigation = $('#navigation')
var navigationApps = $('#apps ul')
var appCount = null
navigationApps.html('') <?php if ($_['always-displayed'] && !$_['big-menu']): ?>
<?php require_once __DIR__.'/_alwaysDisplayed.js'; ?>
apps.each(function(i, app) {
var dataId = app.getAttribute('data-id')
if (dataId === null) {
return
}
if (dataId.indexOf('external_index') === -1) {
app.classList.add('hidden')
app.classList.add('app-hidden')
} else {
app.classList.remove('hidden')
app.classList.add('app-external-site')
appShown.push(app)
navigationApps.append(app.outerHTML)
}
})
var rightHeaderWidth = $('.header-right').outerWidth()
var headerWidth = $('header').outerWidth()
var availableWidth = headerWidth - $('#nextcloud').outerWidth() - $('#header .side-menu-opener').outerWidth() - (rightHeaderWidth > 210 ? rightHeaderWidth : 210)
if (!isMobile) {
availableWidth = availableWidth * usePercentualAppMenuLimit
}
if (isMobile && appCount > minAppsDesktop) {
appCount = minAppsDesktop
} else if (!isMobile && appCount < minAppsDesktop) {
appCount = minAppsDesktop
} else {
appCount = Math.floor(availableWidth / $('#appmenu li').width())
}
if (appCount === 0) {
menu.addClass('hidden')
}
menu.removeClass('hidden')
menu.css('opacity', 1)
if (appShown.length - 1 - appCount >= 1) {
appCount--
}
moreApps.find('a').removeClass('active')
var k = 0
var notInHeader = 0
var name
$(appShown).each(function(i, app) {
app = $(app)
name = app.data('id')
if (k < appCount && appCount > 0) {
app.removeClass('hidden')
lastShownApp = app
$('#apps li[data-id=' + name + '].app-external-site').addClass('in-header')
} else {
app.addClass('hidden')
notInHeader++
$('#apps li[data-id=' + name + '].app-external-site').removeClass('in-header')
if (appCount > 0 && app.children('a').hasClass('active')) {
lastShownApp.addClass('hidden')
app.removeClass('hidden')
notInHeader++
$('#apps li[data-id=' + name + '].app-external-site')
.removeClass('in-header')
.addClass('in-header')
}
}
k++
})
if (notInHeader === 0) {
moreApps.hide()
navigation.hide()
} else {
moreApps.show()
}
}
setInterval(updateTopMenu, 50)
<?php endif; ?> <?php endif; ?>
})(); })();
+149 -25
View File
@@ -40,7 +40,7 @@ $choicesSizes = [
</h2> </h2>
<div> <div>
<label for="side-menu-background-color" class="settings-hint"> <label for="side-menu-background-color">
<?php p($l->t('Background color')); ?> <?php p($l->t('Background color')); ?>
</label> </label>
</div> </div>
@@ -60,7 +60,7 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label for="side-menu-current-app-background-color" class="settings-hint"> <label for="side-menu-current-app-background-color">
<?php p($l->t('Background color of current app')); ?> <?php p($l->t('Background color of current app')); ?>
</label> </label>
</div> </div>
@@ -74,7 +74,7 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label for="side-menu-text-color" class="settings-hint"> <label for="side-menu-text-color">
<?php p($l->t('Text color')); ?> <?php p($l->t('Text color')); ?>
</label> </label>
</div> </div>
@@ -88,19 +88,61 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label for="side-menu-opener-only" class="settings-hint"> <label for="side-menu-text-color">
<?php p($l->t('Force light icons')); ?> <?php p($l->t('Loader')); ?>
</label>
</div>
<div>
<input
id="side-menu-loader-color"
name="loader-color"
type="color"
class="side-menu-setting"
value="<?php print_unescaped($_['loader-color']); ?>">
</div>
<div>
<label for="side-menu-icon-invert-filter">
<?php p($l->t('Icon')); ?>
</label> </label>
</div> </div>
<div> <div>
<select id="side-menu-force-light-icon" name="force-light-icon" class="side-menu-setting"> <em>
<?php foreach ($choicesYesNo as $label => $value): ?> <?php p($l->t('Same color')); ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['force-light-icon']): ?>selected<?php endif; ?>> </em>
<?php echo $l->t($label); ?>
</option> <input
<?php endforeach; ?> type="range"
</select> min="0" max="100"
id="side-menu-icon-invert-filter"
name="icon-invert-filter"
class="side-menu-setting"
value="<?php echo $_['icon-invert-filter'] ?>"
>
<em>
<?php p($l->t('Opposite color')); ?>
</em>
</div>
<div>
<em>
<?php p($l->t('Transparent')); ?>
</em>
<input
type="range"
min="0" max="100"
id="side-menu-icon-opacity"
name="icon-opacity"
class="side-menu-setting"
value="<?php echo $_['icon-opacity'] ?>"
>
<em>
<?php p($l->t('Opaque')); ?>
</em>
</div> </div>
</div> </div>
@@ -121,7 +163,7 @@ $choicesSizes = [
?> ?>
<div> <div>
<label for="side-menu-opener" class="settings-hint"> <label for="side-menu-opener">
<?php p($l->t('Icon')); ?> <?php p($l->t('Icon')); ?>
</label> </label>
</div> </div>
@@ -144,7 +186,7 @@ $choicesSizes = [
?> ?>
<div> <div>
<label for="side-menu-opener-position" class="settings-hint"> <label for="side-menu-opener-position">
<?php p($l->t('Position')); ?> <?php p($l->t('Position')); ?>
</label> </label>
</div> </div>
@@ -160,7 +202,7 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label for="side-menu-opener-only" class="settings-hint"> <label for="side-menu-opener-only">
<?php p($l->t('Show only the opener (hidden logo)')); ?> <?php p($l->t('Show only the opener (hidden logo)')); ?>
</label> </label>
</div> </div>
@@ -176,7 +218,7 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label for="side-menu-hide-when-no-apps" class="settings-hint"> <label for="side-menu-hide-when-no-apps">
<?php p($l->t('Do not display the side menu and the opener if there is no application (eg: public pages).')); ?> <?php p($l->t('Do not display the side menu and the opener if there is no application (eg: public pages).')); ?>
</label> </label>
</div> </div>
@@ -198,11 +240,13 @@ $choicesSizes = [
</h2> </h2>
<div> <div>
<label for="side-menu-opener" class="settings-hint"> <label for="side-menu-opener">
<?php p($l->t('Open the menu when the mouse is hover the opener (automatically disabled on touch screens)')); ?> <?php p($l->t('Open the menu when the mouse is hover the opener (automatically disabled on touch screens)')); ?>
</label> </label>
</div> </div>
<p><em><?php p($l->t('This is the automatic behavior when the menu is always displayed.')); ?></em></p>
<div> <div>
<select id="side-menu-opener-hover" name="opener-hover" class="side-menu-setting"> <select id="side-menu-opener-hover" name="opener-hover" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?> <?php foreach ($choicesYesNo as $label => $value): ?>
@@ -214,11 +258,53 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label for="side-menu-opener" class="settings-hint"> <label for="side-menu-big-menu">
<?php p($l->t('Always displayed')); ?>
<small><span class="warning"><?php p($l->t('Experimental')); ?></span></small>
</label>
</div>
<p><em><?php p($l->t('This feature is not compatible with the <code>big menu</code> display.')); ?></em></p>
<p><em><?php p($l->t('Not compatible with touch screens.')); ?></em></p>
<div>
<select id="side-menu-always-displayed" name="always-displayed" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['always-displayed']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="side-menu-big-menu">
<?php p($l->t('Display the big menu')); ?>
</label>
</div>
<p><em><?php p($l->t('The big menu is not compatible with AppOrder.')); ?></em></p>
<div>
<select id="side-menu-big-menu" name="big-menu" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['big-menu']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="side-menu-opener">
<?php p($l->t('Display the logo')); ?> <?php p($l->t('Display the logo')); ?>
</label> </label>
</div> </div>
<p><em><?php p($l->t('This feature is not compatible with the <code>big menu</code> display.')); ?></em></p>
<p><em><?php p($l->t('The logo will be hidden when the menu is always displayed.')); ?></em></p>
<div> <div>
<select id="side-menu-display-logo" name="display-logo" class="side-menu-setting"> <select id="side-menu-display-logo" name="display-logo" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?> <?php foreach ($choicesYesNo as $label => $value): ?>
@@ -230,7 +316,7 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label class="settings-hint"> <label>
<?php p($l->t('Icons and texts')); ?> <?php p($l->t('Icons and texts')); ?>
</label> </label>
</div> </div>
@@ -254,16 +340,15 @@ $choicesSizes = [
</div> </div>
<div> <div>
<label for="side-menu-external-sites-in-top-menu" class="settings-hint"> <label for="side-menu-loader-enabled">
<?php p($l->t('Do not move external sites in the side menu')); ?> <?php p($l->t('Loader enabled')); ?>
<small><span class="warning">Experimental</span></small>
</label> </label>
</div> </div>
<div> <div>
<select id="side-menu-external-sites-in-top-menu" name="external-sites-in-top-menu" class="side-menu-setting"> <select id="side-menu-loader-enabled" name="loader-enabled" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?> <?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['external-sites-in-top-menu']): ?>selected<?php endif; ?>> <option value="<?php echo $value ?>" <?php if ($value === $_['loader-enabled']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?> <?php echo $l->t($label); ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
@@ -271,6 +356,45 @@ $choicesSizes = [
</div> </div>
</div> </div>
<div class="section">
<h2>
<?php p($l->t('Top menu')); ?>
</h2>
<div>
<label for="side-menu-top-menu-apps">
<?php p($l->t('Apps that not must be moved in the side menu')); ?>
</label>
</div>
<div class="side-menu-setting" data-name="top-menu-apps" data-checkbox>
<?php foreach ($_['apps'] as $app): ?>
<div>
<input
type="checkbox"
name="top-menu-apps[]"
value="<?php echo $app['id'] ?>"
id="top-menu-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['top-menu-apps'])): ?>checked<?php endif; ?>
/>
<label for="top-menu-app-<?php echo $app['id'] ?>">
<?php echo p($l->t($app['name'])); ?>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="section">
<h2>
<?php p($l->t('Tips')); ?>
</h2>
<p>
<em><?php echo $l->t('Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'); ?></em>
</p>
</div>
<div class="section"> <div class="section">
<input type="hidden" id="side-menu-cache" name="cache" value="<?php print_unescaped($_['cache']); ?>" class="side-menu-setting"> <input type="hidden" id="side-menu-cache" name="cache" value="<?php print_unescaped($_['cache']); ?>" class="side-menu-setting">
@@ -281,7 +405,7 @@ $choicesSizes = [
<div style="height: 30px"></div> <div style="height: 30px"></div>
<div> <div>
<span for="side-menu-opener" class="settings-hint"> <span for="side-menu-opener">
<?php p($l->t('You like this app and you want to support me?')); ?> <?php p($l->t('You like this app and you want to support me?')); ?>
<a style="margin-left: 10px" target="_blank" href="https://www.buymeacoffee.com/deblan"> <a style="margin-left: 10px" target="_blank" href="https://www.buymeacoffee.com/deblan">
+42 -3
View File
@@ -34,22 +34,61 @@ $choicesYesNo = [
</h2> </h2>
<div> <div>
<label for="side-menu-enabled" class="settings-hint"> <label for="side-menu-enabled">
<?php p($l->t('Enable the side menu')); ?> <?php p($l->t('Enable the side menu')); ?>
</label> </label>
</div> </div>
<p>
<em><?php echo $l->t('Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'); ?></em>
</p>
<div> <div>
<select id="side-menu-enabled" name="enabled" class="side-menu-setting" data-personal> <select id="side-menu-enabled" name="enabled" class="side-menu-setting" data-personal>
<?php foreach ($choicesYesNo as $label => $value): ?> <?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['enabled']): ?>selected<?php endif; ?>> <option value="<?php echo $value ?>" <?php if ($value === $_['enabled']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?> <?php echo $l->t($label); ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
</div> </div>
<div class="section">
<h2>
<?php p($l->t('Top menu')); ?>
</h2>
<div>
<label for="side-menu-top-menu-apps">
<?php p($l->t('Apps that not must be moved in the side menu')); ?>
</label>
</div>
<p>
<em>
<?php p($l->t('If there is no selection then the global configuration is applied.')); ?>
</em>
</p>
<div class="side-menu-setting" data-name="top-menu-apps" data-checkbox data-personal>
<?php foreach ($_['apps'] as $app): ?>
<div>
<input
type="checkbox"
name="top-menu-apps[]"
value="<?php echo $app['id'] ?>"
id="top-menu-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['top-menu-apps'])): ?>checked<?php endif; ?>
/>
<label for="top-menu-app-<?php echo $app['id'] ?>">
<?php echo $app['name'] ?>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="section"> <div class="section">
<button id="side-menu-save" class="btn btn-primary"><?php p($l->t('Save')); ?></button> <button id="side-menu-save" class="btn btn-primary"><?php p($l->t('Save')); ?></button>
@@ -58,7 +97,7 @@ $choicesYesNo = [
<div style="height: 30px"></div> <div style="height: 30px"></div>
<div> <div>
<span for="side-menu-opener" class="settings-hint"> <span for="side-menu-opener">
<?php p($l->t('You like this app and you want to support me?')); ?> <?php p($l->t('You like this app and you want to support me?')); ?>
<a style="margin-left: 10px" target="_blank" href="https://www.buymeacoffee.com/deblan"> <a style="margin-left: 10px" target="_blank" href="https://www.buymeacoffee.com/deblan">