Compare commits

..

No commits in common. "develop" and "master" have entirely different histories.

131 changed files with 3344 additions and 17414 deletions

View file

@ -1,14 +1,5 @@
module.exports = {
env: {
node: true,
},
extends: [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"prettier",
],
rules: {
// override/add rules settings here, such as:
// 'vue/no-unused-vars': 'error'
}
}
rules: {
'no-console': 'off',
},
};

View file

@ -1,34 +0,0 @@
name: New feature
about: Use this template if you want to request a feature
title: "[FEATURE] "
labels:
- enhancement
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
- type: textarea
id: description
attributes:
label: Description
description: Describe the feature.
validations:
required: true
- type: textarea
id: benefits
attributes:
label: Benefits
description: Describe the benefits of this feature.
validations:
required: true
- type: textarea
id: extra
attributes:
label: More informations
description: If you want to share more things, this is here!
validations:
required: false

View file

@ -1,69 +0,0 @@
name: New issue
about: Use this template if you have a bug
title: "[Bug] "
labels:
- bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: environment
attributes:
label: Environment
value: |
* Custom menu version:
* Nextcloud version:
* PHP version:
* Web server (Nginx, Apache2):
* Web browser and version (Firefox 80, Google Chrome 74, etc):
validations:
required: true
- type: textarea
id: configuration
attributes:
label: Configuration
description: Export the configuration using the admin page and copy/paste here ([documentation](https://deblan.gitnet.page/side_menu_doc/docs/FAQ/export-config/)).
value: |
```
{
...
}
```
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: How reproduce the bug?
validations:
required: false
- type: textarea
id: resuts
attributes:
label: Observed Results
description: What happened?
validations:
required: false
- type: textarea
id: expected
attributes:
label: Expected Results
description: What should happen?
validations:
required: false
- type: textarea
id: extra
attributes:
label: More informations
description: If you want to share more things, this is here!
validations:
required: false

View file

@ -1,30 +0,0 @@
name: New question
about: Use this template when you don't know how to do something
title: "[Question] "
labels:
- question
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill information.
- type: textarea
id: environment
attributes:
label: Environment
value: |
* Custom menu version:
* Nextcloud version:
* PHP version:
* Web server (Nginx, Apache2):
* Web browser and version (Firefox 80, Google Chrome 74, etc):
validations:
required: true
- type: textarea
id: question
attributes:
label: Question
validations:
required: true

View file

@ -1,8 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://deblan.gitnet.page/side_menu_doc/
about: Official documentation web site
- name: Ask a question in our Matrix room
about: If you prefer a chat-like conversation or in need for quick help, this might be an alternative to opening an issue.
url: https://matrix.to/#/#custommenu:neutralnetwork.org

1
.gitignore vendored
View file

@ -2,4 +2,5 @@
/node_modules
/l10n/*
/releases
/package-lock.json
!/l10n/.gitkeep

View file

@ -1,8 +0,0 @@
{
"bracketSpacing": true,
"bracketSameLine": false,
"semi": false,
"singleQuote": true,
"singleAttributePerLine": true,
"printWidth": 160
}

View file

@ -1,5 +0,0 @@
{
"rules": {
"indentation": 2
}
}

View file

@ -1,22 +0,0 @@
variables:
volumes: &volumes
- /data/${CI_REPO}:/builds
when:
event: [tag, push, pull_request, manual]
branch: [master, develop, feature/*, fix/*, bugfix/*, translations]
steps:
"Build JS":
image: node:20
commands:
- make build
"Build translations":
image: deblan/php:8.3
commands:
- php bin/generate_l10n.php
"Build cache":
image: gitnet.fr/deblan/woodpecker-cache
volumes: *volumes

View file

@ -1,66 +0,0 @@
variables:
volumes: &volumes
- /data/${CI_REPO}:/builds
- /var/www/html/artifacts:/var/www/html/artifacts
depends_on:
- build
when:
event: [tag]
steps:
"Verify tag and app version":
image: alpine
commands:
- TAG=${CI_COMMIT_TAG/v//}
- grep "<version>$TAG</version>" appinfo/info.xml
"Create signature":
image: nextcloud:25
volumes: *volumes
environment:
APP_CERTIFICATE:
from_secret: app_certificate
APP_PUBLIC_CERTIFICATE:
from_secret: app_public_certificate
SQLITE_DATABASE: /var/www/data/data.db
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: admin
commands:
- cd "/builds/$CI_COMMIT_SHA"
- echo "$APP_CERTIFICATE" > "/tmp/side_menu.key"
- echo "$APP_PUBLIC_CERTIFICATE" > "/tmp/side_menu.crt"
- mkdir /tmp/app
- cp -r README.md CHANGELOG.md appinfo lib img l10n js src templates screenshots vendor /tmp/app
- /usr/src/nextcloud/occ integrity:sign-app
--privateKey=/tmp/side_menu.key
--certificate=/tmp/side_menu.crt
--path=/tmp/app
- mv /tmp/app/appinfo/signature.json appinfo/
"Create package":
image: deblan/php:8.3
volumes: *volumes
environment:
APP_CERTIFICATE:
from_secret: app_certificate
commands:
- cd "/builds/$CI_COMMIT_SHA"
- apt-get update
- apt-get install -y zip make
- mkdir -p "$HOME/.nextcloud/certificates"
- echo "$APP_CERTIFICATE" > "$HOME/.nextcloud/certificates/side_menu.key"
- export VERSION=$(grep "<version>" appinfo/info.xml | grep -o "[0-9]*\.[0-9]*\.[0-9]*" --color=never)
- export RELEASE_DIRECTORY="/var/www/html/artifacts/deblan/side_menu"
- make release
"Push release":
image: plugins/gitea-release
volumes: *volumes
settings:
api_key:
from_secret: gitnet_api_key
base_url: https://gitnet.fr
# note: ${CI_COMMIT_MESSAGE}
files: /var/www/html/artifacts/deblan/side_menu/${CI_COMMIT_TAG/v//}/*

View file

@ -1,17 +0,0 @@
variables:
volumes: &volumes
- /data/${CI_REPO}:/builds
depends_on:
- build
skip_clone: true
steps:
"Check dependencies":
image: gitnet.fr/deblan/osv-detector:v0.10
volumes: *volumes
commands:
- cd "/builds/$CI_COMMIT_SHA"
- osv-detector package-lock.json
failure: ignore

View file

@ -1,677 +0,0 @@
## [Unreleased]
## 5.2.2
### Fixed
* fix #464: add Intl.Segmenter polyfill
### Added
* add new translations
## 5.2.1
### Added
* chore: set side_menu as package name
### Fixed
* fix(LangRepository): check orm capabilities to query entities
* fix(admin/\*SaveButton): cast settings to string
## 5.2.0
### Added
* add compatibility with NC33
### Fixed
* fix #468: force nextcloud logo display css rule (opener-only)
## 5.1.3
### Fixed
* fix #445: fix build by adding package-lock.json
## 5.1.2
### Added
* add new translations
### Fixed
* fix #441: Side bar not working with Nextcloud 32 (thanks to AndyXheli)
## 5.1.1
### Fixed
* fix(build): define appName to fix this error: "The `@nextcloud/vue` library was used without setting / replacing the `appName`"
* fix #349: add custom controller to retrieve core apps
## 5.1.0
### Added
* fix #425: allow to set a color using hex code
### Fixed
* #422: usage of `OC\AppFramework\Http\Request` instead of `$_SERVER`
## 5.0.3
### Fixed
* fix #422: undefined array key "HTTP_USER_AGENT"
## 5.0.2
### Fixed
* fix #413: add user-agent check for memories mobile app
* fix #418: allow non admin user to access their settings
## 5.0.1
### Fixed
* fix(StandardMenu): appLimit must return a value > 0
## 5.0.0
### Fixed
* fix apps's order in the standard menu
### Added
* add new translations
* add route `/apps/side_menu/user/config`
* add new UI for admin and personals settings
### Changed
* migrate to Vue 3 and so add/update or remove dependencies
* replace CSS with SCSS
* remove route `/apps/side_menu/js/script`
* remove generated Javascript using PHP
* rewrite the standard menu of Nextcloud
### Security
* fix CVE-2023-44270
* fix CVE-2024-9506
* fix CVE-2024-6783
## 4.1.1
### Fixed
* fix(CssController): add missing NoCSRFRequired import (#397)
* fix(SideMenu): ncApps must be an array (#369)
## 4.1.0
### Added
* add compatibility with NC31
### Fixed
* fix(service): add service constructor arguments
* fix(settings): remove non-existing and unused ILogger service
### Changed
* refactor(controller): usage of attributes instead of annotations
## 4.0.1
### Fixed
* fix top menu labels (fix #368)
* fix #369: The menu is displayed even if there are no apps
## 4.0.0
### Added
* add compatibility with NC30
## 3.13.1
### Fixed
* fix #354: remove the opener when the menu is always displayed
* fix extra margin between the logo and the opener
## 3.13.0
### Added
* show apps generated with Tables (fix #349)
* add constructor property promotion
### Fixed
* remove .app-navigation--close translationX for always-displayed menu (fix #348)
## 3.12.0
### Added
* add compatibility with NC29
## 3.11.8
### Fixed
* move the logo inside #nextcloud element (fix #278 #239) [NC26]
## 3.11.7
### Added
* update translations
* update ci steps names
* fully apply Nextcloud AppMenu.vue updates
### Fixed
* add accessibility to open and close buttons (#311)
* add missing label on the 'save' button in personal settings (fix #318)
### Changed
* upgrade axios
* upgrade css-loader
## 3.11.6
### Fixed
* add --background-invert-if-bright in top menu (fix #326)
## 3.11.5
### Fixed
* add missing label on buttons for accessiblity (fix #311)
## 3.11.4
### Fixed
* add label on buttons for accessiblity (fix #311)
## 3.11.3
### Fixed
* fix menu icon in decks, collectives and other apps (#302)
## 3.11.2
### Fixed
* add default translations for Slovak - fix #298
## 3.11.1
### Added
* add Portuguese (Brazil) translations - Thanks to igorfreire
### Fixed
* add width to .side-menu-categories for side menu with categories display - fix #294
## 3.11.0
### Added
* add a search component in menus - fix #282
### Fixed
* remove the label of the link to personal settings - fix #283
## 3.10.3
### Fixed
* change the way to load nextcloud components (NcActionLink/NcActions) - fix #274
* update @nexcloud/* packages
## 3.10.2
### Fixed
* add missing properties
## 3.10.1
### Fixed
* fix #269: use php7 syntax
## 3.10.0
### Added
* add compatibility with NC28
### Fixed
* fix NC28 error: remove deprecated method `OC_App::getNavigation()`
## 3.9.1
### Fixed
* fix fixed menu on dashboard (#262)
## 3.9.0
### Added
* add compatibility with NC27
### Fixed
* fix app redirect (#261)
## 3.8.0
### Added
* add option to show hovered label only on top menu (fix #253)
## 3.7.4
### Fixed
* fix Integrity failed (#247)
## 3.7.3
### Fixed
* fix #244: use app href for redirection
### Added
* add signature on build
## 3.7.2
### Added
* update pipeline conditions allowing `fix/*`
### Fixed
* fix #233: load configuration and then retrieve apps in default side menu display
## 3.7.1
### Fixed
* fix build process (#230)
## 3.7.0
### Added
* add translations (thanks to AHOHNMYC)
* add compatibility with NC26
## 3.6.0
### Added
* add hidden apps compatible with default menu (#219)
## 3.5.2
### Fixed
* add check if menu exists before adding event listeners (#210)
## 3.5.1
### Added
* add translations (thanks to p-bo and gallegonovato)
### Fixed
* fix #189: sorting not applied on mobile
## 3.5.0
### Added
* add dependency check (ci)
* add code quality check (ci)
* add translations (thanks to gallegonovato)
* add option to disable the display labels in the top menu (#194)
### Fixed
* fix missing img alt (settings image)
* fix code quality alerts
## 3.4.1
### Added
* add translations (thanks to zonorti, jorisvandijk, jak2k)
### Fixed
* fix #183: hide custom categories list when empty (admin page)
## 3.4.0
### Added
* add translations (thanks to Pavelb, nier, Timur, p-bo)
* add possibility to define Custom Menu as default app and redirect to the first top menu app (#177)
## 3.3.2
### Fixed
* fix #173: reduce the height of categories list
## 3.3.1
### Fixed
* fix #162: top and side apps does work correctly
## 3.3.0
### Added
* add documentation in admin page
* add app sorter in user config side (#160)
### Fixed
* fix #164: open apps in new tab does not work
* fix #162 #159: top and side apps does work correctly
## 3.2.1
### Fixed
* fix #150: active app is not visible has active in menu (except in default menu)
* fix #151: opener position
## 3.2.0
### Added
* use custom app names using 'app.navigation.name' (#148)
* app sorting with all displays (#147)
## 3.1.0
### Added
* add global custom app sorting for the top menu
### Fixed
* fix admin list/modal look
## 3.0.1
### Fixed
* Remove the gap between the window's top and menu categories (large menu)
## 3.0.0
### Added
* Add compatibility with NC25 (#136/#135)
### Removed
* Nextcloud 20-24 are not supported anymore
* AppOrder is not supported anymore
## 2.5.1
### Fixed
* fix icon render (#133)
## 2.5.0
### Changed
* upgrade dependencies
### Fixed
* remove duplicated code
* remove unused variables and packages
* replace repeated strings with variables
* add html attributes
* replace var with let
* replace v-html with v-text to prevent xss
## 2.4.3
### Fixed
* fix translations
## 2.4.2
### Fixed
* fix typo
### Changed
* change ci/cd
## 2.4.1
### Fixed
* fix user setting save
## 2.4.0
### Added
* remove focus on opener after click
* add button to set default colors
* add menu hover effect
* add translations
### Fixed
* fix deprecated app.php file
* fix menu with categories header
* fix minor issues
### Changed
* change saving progression
### Removed
* Nextcloud 19 is not supported anymore
* PHP 7.3 is not supported anymore
## 2.3.5
### Fixed
* fix white square (#99)
## 2.3.4
### Fixed
* fix blank line when settings are open (#96)
## 2.3.3
### Added
* hide the scrollbar when mouse is out (menu always displayed)
### Fixed
* fix SQL Exception InvalidFieldNameException (#93)
## 2.3.2
### Fixed
* fix hidden menu
## 2.3.1
### Fixed
* fix #88: does not work with default menu
## 2.3.0
### Added
* fix #82: add an option to keep visible an app in both menus
* fix #83: add custom categories
* add auto-reload when settings are saved
## 2.2.0
### Added
* fix #84: update icons
* fix #85: use Nextcloud colors by default
### Fixed
* fix categories order in large menu
## 2.1.0
### Added
* add compatibility with Nextcloud 23
## 2.0.1
### Fixed
* fix #78: Top menu is broken - invisible apps are shown
* fix #77: Update personal settings - HTTP error 412 (Precondition Failed)
* fix js error on the personal settings page (undefined sortable)
## 2.0.0
### Fixed
* fix #66: removing usage of setInterval
* fix #73: icon background
### Changed
* fix #67: replace jQuery with Vanilla JS
### Removed
* Nextcloud 18 is not supported anymore
## 1.28.0
### Added
* fix #63: add a new side menu with categories
## 1.27.2
### Fixed
* fix #62: hide app notification icon
## 1.27.1
### Fixed
* fix German translation render
## 1.27.0
### Added
* hide personal settings access when settings are forced by the administrator
### Fixed
* improve German translations
## 1.26.0
### Added
* add Czech translation
## 1.25.2
### Fixed
* fix CHANGELOG
## 1.25.1
### Added
* add PHP version as dependency
* add chinese translation
### Fixed
* fix CHANGELOG
## 1.25.0
### Added
* add compatibility with Nextcloud 22
* add CHANGELOG.md (fix #59)
* update app icon
## 1.24.0
### Added
* add option to define the background opacity (fix #53)
* add missing translations
## 1.23.1
### Fixed
* fix but wih dark mode opener option
## 1.23.0
### Added
* add support of dark Theme and Breeze Dark
* add support of Nextcloud 21
## 1.22.2
### Fixed
* fix regression: apps does not open in new tab (fix #55)
## 1.22.1
### Fixed
* fix regression: apps does not open in new tab
## 1.22.0
### Added
* Add option to sort categories (fix #53)
* Update admin UI
## 1.21.0
### Added
* [FEATURE] Logo in the menu links to main page of installation (#51)
## 1.20.1
### Fixed
* Fix translations
## 1.20.0
### Added
* [FEATURE] Ability to remove apps from the Big Menu (#49)
## 1.19.1
### Fixed
* fix #47: setting for list/grid view in files app flashes and dissapears
## 1.19.0
### Added
* add option: the menu is enabled by default for users (fix #46)
## 1.18.0
### Added
* add option to show link to settings (fix #44)
* refactor menus using several components
## 1.17.0
### Added
* compliance with the app checker
* add an action to export the configuration
* add an action to purge the cache
## 1.16.3
### Fixed
* fix links that must be opened in new window (https://help.nextcloud.com/t/external-petit-probleme-concernant-lapplication-external-sites-ou-sites-externes/94884/11)
## 1.16.2
### Fixed
* fix issue with personal settings when global settings are forced
## 1.16.1
### Fixed
* fix #42: add cache to manage failures to access apps.nextcloud.com
* fix #41: side menu was hover apps list
## 1.16.0
### Added
* add an option to force settings to users (fix #38)
## 1.15.0
### Changed
* New name
### Fixed
* Fix #36: always displayed is not expanding
## 1.14.0
### Added
* add an api accessed by components
* add a config proxy in controllers
### Fixed
* fix translations
## 1.13.0
### Added
* add an option to display the avatar instead of the logo (fix #34)
## 1.12.3
### Added
* add a delay before moving elements (fix #33)
## 1.12.2
### Fixed
* fix #30: `Always displayed` menu can not be close using touchscreens
## 1.12.1
### Fixed
* fix typo
* fix translations
## 1.12.0
### Fixed
* fix #30: administrators and users can select what apps must be opened in new tab
* fix typo
## 1.11.0
### Added
* add the option for opening apps in new tab (fix #29)
### Fixed
* fix issue with the header of the always displayed menu
## 1.10.0
### Added
* add images to select the display of the menu
* add live preview
* update translations
* update app info
## 1.9.3
### Fixed
* fix regression with logo display
## 1.9.2
### Added
* add the option “always displayed” which fixes the position of the menu to the left and always displays the application icons (fix #21, fix #2)
## 1.8.6
### Added
* add translations: `fr` and `de`
* improve and publish the `Makefile`
* update documentation
## 1.8.5
### Fixed
* fix #28: menu items invisible after 1.8.4
## 1.8.4
### Fixed
* fix #27: disable side menu on public pages - broken in 1.8.3
## 1.8.3
### Fixed
* fix alphabetic order of apps (#26)
## 1.8.2
### Added
* add icon for closing the big menu (fix #25)
### Fixed
* fix hidden icons in the top menu (fix #23)
* fix missing apps (fix #24)
## 1.8.1
### Fixed
* fix issue with links
* fix missing l10n files
## 1.8.0
### Added
* add a `big menu` display (fix #22)
* add the possibility to choose what apps are displayed in the top menu (fix #22)
* add icon color filter
* add icon opacity filter
## 1.8.0-rc2
## 1.7.0
### Added
* add a loader when the page is unloading
* add compatibility with Nextcloud 19
## 1.6.3
### Fixed
* fix #20: add a shortcut to open and to hide the menu
## 1.6.2
### Fixed
* fix #19: add a cache to limit flashes
## 1.6.1
### Fixed
* fix #19: add a hack to show external sites in the top menu with navigation
## 1.6.0
### Added
* Add a page of personal settings
* Add an option to disable the side menu as user
* Refactoring of javascripts
## 1.5.0
### Added
* add option to force light icons instead of dark icons
### Fixed
* fix #19: add option to keep external sites in the top menu
* fix #16 #17: add dark icons and handle the svg filters
## 1.4.1
### Added
* Rendering harmonization with browsers
### Fixed
* FIX #15: make menu start after icon
## 1.4.0
### Fixed
* Fix #12: add an option to hide the opener and the panel when there is no application
## 1.3.4
### Fixed
* FIX #14: add option to show only the opener
* FIX #13: add alternate hamburger icon
* FIX #11: remove error in the console
## 1.3.3
### Added
* Add a second background color to create a background gradiant
* Update documentation
### Fixed
* FIX #10: add options to select the size of the icons and the text, or hide them
## 1.3.2
### Fixed
* FIX #9: opener icon not visible with multiple apps_paths
## 1.3.1
### Fixed
* FIX #3: Add an opition the choose the position of the opener (after or before the logo)
* FIX #8: Remove extension of dynamic asset's routes (js, css)
## 1.3.0
### Added
* `main.js` is replaced by a controller and a template
### Fixed
* FIX #2: add option to open the menu by hovering over opener
## 1.2.4
### Fixed
* FIX #7: Opener icon not visible in Safari

View file

@ -1,5 +0,0 @@
# Contributor Code of Conduct
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage.

33
ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,33 @@
## Feature
### Description
...
### Benefits
...
---
## Issue
### Environment
* Side menu version: ...
* PHP version: ...
* Web server (Nginx, Apache2): ...
### Steps to reproduce
* ...
* ...
* ...
### Observed Results
...
### Expected Results
...

View file

@ -1,27 +1,20 @@
build: dep
npm-build:
npm run build
watch: dep
npm-watch:
npm run watch
dep:
npm i
.ONESHELL:
release:
release: npm-build translations
if [ -z "$$VERSION" ]; then
echo "VERSION required"
exit 1
fi
if [ -z "$$RELEASE_DIRECTORY" ]; then
RELEASE_DIRECTORY=releases
fi
test -d $$RELEASE_DIRECTORY/$$VERSION && rm -fr $$RELEASE_DIRECTORY/$$VERSION
mkdir -p $$RELEASE_DIRECTORY/$$VERSION/side_menu
cp -r README.md CHANGELOG.md appinfo lib img l10n js src templates screenshots vendor $$RELEASE_DIRECTORY/$$VERSION/side_menu
cd $$RELEASE_DIRECTORY/$$VERSION
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
@ -29,8 +22,3 @@ release:
translations:
php bin/generate_l10n.php
.ONESHELL:
run-code-quality-analysis:
export SONAR_TOKEN="$$SONAR_TOKEN_DEBLAN_SIDE_MENU"
sonar-scanner -Dsonar.projectKey=deblan-side_menu -Dsonar.sources=. -Dsonar.host.url=$$SONAR_SERVER -Dsonar.branch.name=$$(git branch --show-current)

View file

@ -1,46 +1,27 @@
🤙 Nextcloud app / Custom menu 🎨
🤙 Nextcloud app / Side menu 🎨
===============================
[![Build Status](https://ci.gitnet.fr/api/badges/deblan/side_menu/status.svg)](https://ci.gitnet.fr/deblan/side_menu)
[![Translations](https://translate.codeberg.org/widgets/custom-menu/-/application/svg-badge.svg)](https://translate.codeberg.org/engage/custom-menu/)
![Downloads](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=downloads&query=%24.K_downloads&suffix=K&url=https%3A%2F%2Fapi-side-menu.deblan.org%2Fdownloads.php)
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 add and sort custom categories, define apps that must be displayed in the top menu, etc. Fully customisable.
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.
You can customize colors depending of the theme (Dark theme and Breeze Dark).
Comptatible with AppOrder.
* [Installation and upgrade](#installation-and-upgrade)
* [How to contribute?](#how-to-contribute)
* [Support](#support)
* [Screenshots](https://gitnet.fr/deblan/side_menu/src/branch/master/screenshots/)
You like this app and you want to support me? ☕ [Buy me a coffee](https://www.buymeacoffee.com/deblan) or [Donate with liberapay](https://liberapay.com/deblan)
## [📘 Read the documentation](https://deblan.gitnet.page/side_menu_doc/)
Requirements
------------
* PHP >= 8.1
You like this app and you want to support me? [Buy me a coffe](https://www.buymeacoffee.com/deblan) ☕
Installation and upgrade
------------------------
Custom menu is available from the app store.
```
$ cd /path/to/nextcloud
$ php occ app:install side_menu
```
If you want to install it from source, go to https://gitnet.fr/deblan/side_menu/releases and copy the link to the last release (side_menu_vX.Y.Z.tar.gz). Then:
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`.
```
$ cd /path/to/nextcloud/apps
$ VERSION=x.y.z; curl -sS "https://gitnet.fr/deblan/side_menu/releases/download/${VERSION}/side_menu_v${VERSION}.tar.gz" | tar xvfz -
$ unzip /path/to/side_menu_vX.Y.Z.zip # tar xvf /path/to/side_menu_vX.Y.Z.tar.gz
```
Administrators can edit many settings using the administration page.
@ -49,44 +30,18 @@ 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.
### Use first top menu app as default app
You can easily let Custom Menu redirect to the first app in the top menu by changing the following parameter in your `config/config.php`:
```
'defaultapp' => 'side_menu',
```
If the top menu is empty then it redirects to files.
How to contribute?
------------------
You can report a bug or request a feature by opening an issue: https://gitnet.fr/deblan/side_menu/issues
### You are a translator
Translations are managed from [translate.codeberg.org](https://translate.codeberg.org/projects/custom-menu/application/).
### You are a developer
If you are a developer:
* fork the repository
* install an instance of Nextcloud
* 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.
Support
-------
You can join the official room on Matrix: [#custommenu:neutralnetwork.org](https://matrix.to/#/#custommenu:neutralnetwork.org).
Notice
------
Because I believe in a free and decentralized Internet, [Gitnet](https://gitnet.fr) is **self-hosted at home**.
In case of downtime, you can download **Custom Menu** from [here](https://kim.deblan.fr/~side_menu/).

10
appinfo/app.php Normal file
View file

@ -0,0 +1,10 @@
<?php
use OCA\SideMenu\AppInfo\Application;
$app = new Application();
if ($app->isEnabled()) {
$app->registerAssets();
$app->registerServices();
}

View file

@ -1,59 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<?xml version="1.0"?>
<info>
<id>side_menu</id>
<name>Custom menu</name>
<name>Side menu</name>
<summary>Modify the display of the menu.</summary>
<description><![CDATA[Custom 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.
<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.
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.
Comptatible with AppOrder.
To report a bug or request a feature, please open an issue.
You can report a bug or request a feature by opening an issue.
Requirements:
* PHP >= 8.1
If you like this application and if you want to support the development:
* [Buy me a coffee](https://www.buymeacoffee.com/deblan)
* [Donate with liberapay](https://liberapay.com/deblan)
* [Leave a comment](https://apps.nextcloud.com/apps/side_menu#comments)
Notice
------
Because I believe in a free and decentralized Internet, [Gitnet](https://gitnet.fr) is **self-hosted at home**.
In case of downtime, you can download **Custom Menu** from [here](https://kim.deblan.fr/~side_menu/).
]]></description>
<version>5.2.2</version>
<licence>agpl</licence>
<author mail="contact@deblan.fr" homepage="https://www.deblan.fr/">Simon Vieille</author>
<author mail="contact@deblan.fr" homepage="https://www.deblan.io/">Simon Vieille</author>
<version>1.12.0</version>
<namespace>SideMenu</namespace>
<documentation>
<admin>https://deblan.gitnet.page/side_menu_doc/</admin>
<developer>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</developer>
</documentation>
<category>customization</category>
<website>https://gitnet.fr/deblan/side_menu</website>
<discussion><![CDATA[https://matrix.to/#/!TFPucDATKODpHNVAtu:neutralnetwork.org?via=neutralnetwork.org]]></discussion>
<discussion>https://deblan.cloud/index.php/call/fffxkc7h</discussion>
<bugs>https://gitnet.fr/deblan/side_menu/issues</bugs>
<repository type="git">https://gitnet.fr/deblan/side_menu</repository>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/admin_settings.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/n19_big_menu.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc18_menu_always_displayed.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_big_menu.png]]></screenshot>
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_default_menu.png]]></screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/admin_settings.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/n19_big_menu.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>
<php min-version="8.1" max-version="8.4" />
<nextcloud min-version="31" max-version="33"/>
<nextcloud min-version="18" max-version="20"/>
</dependencies>
<settings>
<admin>OCA\SideMenu\Settings\Admin</admin>

View file

@ -1,3 +1,5 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -15,14 +17,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const focusActiveApp = (menu) => {
window.setTimeout(() => {
const a = menu.querySelector('.side-menu-app.active a') || menu.querySelector('.side-menu-app a')
if (a) {
a.focus()
}
}, 500)
}
export { focusActiveApp }
return [
'routes' => [
['name' => 'Css#stylesheet', 'url' => '/css/stylesheet', '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'],
],
];

View file

@ -1,10 +1,4 @@
<?php
/**
* Generates l10n files using Yaml.
*
* Usage:
* php bin/generate_l10n.php
*/
function generateJsContent($translations)
{
@ -31,7 +25,6 @@ function generateJsonContent($translations)
chdir(__DIR__.'/../');
foreach (glob('src/l10n/fixtures/*.yaml') as $file) {
echo "$file\n";
$lang = str_replace('.yaml', '', basename($file));
$translations = yaml_parse(file_get_contents($file));

View file

@ -1,78 +0,0 @@
<?php
function showUsageAndExit(int $code)
{
global $argv;
echo "${argv[0]} [--help] --config /path/to/config/config.php --file /path/to/config.json\n";
exit($code);
}
function value(string $shortName, string $longName, array $options, bool $required = true): ?string
{
$value = $options[$shortName] ?? $options[$longName] ?? null;
if (is_array($value)) {
echo "To much --{$longName}\n";
showUsageAndExit(1);
}
if (empty($value) && $required) {
echo "--{$longName} is missing\n";
showUsageAndExit(1);
}
return $value;
}
$options = getopt('t:f:c:h', [
'type:',
'file:',
'config:',
'help',
]);
$help = value('h', 'help', $options, false);
$config = value('c', 'config', $options);
$file = value('f', 'file', $options);
if (!is_readable($config) && !is_file($config)) {
echo "No such file: {$config}\n";
exit(1);
}
if (!is_readable($file) && !is_file($file)) {
echo "No such file: {$file}\n";
exit(1);
}
$appConfig = json_decode(file_get_contents($file), true);
require $config;
if ('mysql' === $CONFIG['dbtype']) {
$pdo = new \PDO(
'mysql:host='.$CONFIG['dbhost'].';dbname='.$CONFIG['dbname'],
$CONFIG['dbuser'],
$CONFIG['dbpassword']
);
} elseif ($CONFIG['dbtype']) {
$pdo = new \PDO(sprintf('sqlite:%s', $CONFIG['datadirectory'].'/owncloud.db'));
} else {
echo "dbtype is not valid\n";
exit(1);
}
$stmt = $pdo->prepare('UPDATE '.$CONFIG['dbtableprefix'].'appconfig SET configvalue=:value WHERE configkey=:key and appid=:appId');
foreach ($appConfig as $key => $value) {
$stmt->execute([
'appId' => 'side_menu',
'key' => $key,
'value' => $value,
]);
}

70
css/admin.css Normal file
View file

@ -0,0 +1,70 @@
/**
* @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/>.
*/
#side-menu-section input[type="color"] {
width: 100px;
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 {
margin: 10px 0 10px 0;
}
.keyboard-key {
padding: 1px 9px;
margin: 0 2px;
background: #eee;
border: 1px solid #aaa;
color: #555;
border-radius: 3px;
}
.side-menu-display {
padding: 10px;
border: 2px solid transparent;
max-width: 100%;
cursor: pointer;
}
.side-menu-display.is-active {
border: 2px solid #91cb7f;
}
.info {
margin-top: 8px;
padding: 5px;
background: #91cb7f;
color: #fff;
border-radius: var(--border-radius);
}
#side-menu-section h2 small {
font-size: 11px;
font-weight: normal;
}
.side-menu-toggler {
cursor: pointer;
}

265
css/sideMenu.css Normal file
View file

@ -0,0 +1,265 @@
/**
* @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/>.
*/
#side-menu {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100%;
max-width: 250px;
background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%);
z-index: 3000;
color: var(--side-menu-text-color, #fff);
box-shadow: rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px;
display: none;
}
#side-menu.open {
display: block;
}
#header .side-menu-opener {
margin-left: 5px;
}
.side-menu-opener {
background: var(--side-menu-opener, url('../img/side-menu-opener.svg'));
height: 40px;
width: 40px;
border-radius: 0;
border: 0;
}
.side-menu-closer {
background: url('../img/side-menu-opener-closer.svg');
display: none;
}
#side-menu.hide-opener .side-menu-opener, .side-menu-opener.hide, #side-menu.hide {
display: none !important;
}
.side-menu-apps-list {
height: calc(100vh - 150px);
z-index: 2200;
position: fixed;
top: 150px;
width: 100%;
max-width: 250px;
overflow: auto;
}
.side-menu-app-icon {
width: 20px;
vertical-align: top;
margin-right: 10px;
filter: invert(var(--side-menu-icon-invert-filter, 0%));
opacity: var(--side-menu-icon-opacity, 1);
}
.side-menu-app-icon svg {
vertical-align: middle;
margin-top: -3px;
}
.side-menu-app a {
line-height: 30px;
color: var(--side-menu-text-color, #fff);
display: block;
padding: 7px 0 5px 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.side-menu-app a:hover, .side-menu-app.active, .side-menu-app a:focus {
background: var(--side-menu-current-app-background-color, #444);
}
.side-menu-logo {
text-align: center;
}
.side-menu-logo img {
max-width: 60%;
max-height: 100px;
}
.side-menu-header {
height: 150px;
width: 100%;
z-index: 2300;
max-width: 250px;
position: fixed;
padding-top: 2px;
padding-left: 5px;
top: 0;
}
#side-menu.hide-opener .side-menu-logo {
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 {
height: calc(100vh - 49px);
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 .side-menu-header {
height: 49px;
}
.side-menu-always-displayed #side-menu.open,
.side-menu-always-displayed #side-menu.open .side-menu-apps-list,
.side-menu-always-displayed #side-menu.open .side-menu-header,
.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%;
}
}

View file

@ -1,224 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="571.907"
height="156.921"
viewBox="0 0 151.317 41.519"
version="1.1"
id="svg901"
sodipodi:docname="layout-side-menu-with-categories.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata907">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs905" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1036"
id="namedview903"
showgrid="false"
inkscape:zoom="1.6086532"
inkscape:cx="455.61054"
inkscape:cy="16.694081"
inkscape:window-x="0"
inkscape:window-y="21"
inkscape:window-maximized="0"
inkscape:current-layer="svg901"
showguides="false" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18zM62.735.18h58.532v41.16H62.735z"
id="path853" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g857">
<path
d="M84.066 21.35l5.055-5.056M82.925 29.835l18.152-18.151M95.893 25.225l3.585-3.586"
id="path855" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M62.735.18h58.532v41.16H62.735z"
id="path861" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v5.063H.18z"
id="path877" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18z"
id="path879" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M1.357 1.034h3.496V4.39H1.357z"
id="path881" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M6.068 1.034h6.102V4.39H6.068z"
id="path883" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g887">
<path
d="M21.51 21.35l5.056-5.056M20.37 29.835L38.52 11.684M33.337 25.225l3.586-3.586"
id="path885" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.28v41.16h-25.28z"
id="path889" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v5.063h-25.257z"
id="path891" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v41.16h-25.257z"
id="path893" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#4d4d4d"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h20.829v41.16h-20.83z"
id="path895" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.034 1.034h3.496V4.39h-3.496z"
id="path897" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.569 10.155h1.893v1.81h-1.893zM130.498 10.486h12.463v1.148h-12.463zM127.569 13.696h1.893v1.81h-1.893zM130.498 14.027h10.592v1.148h-10.592zM127.569 17.104h1.893v1.81h-1.893zM130.498 17.435h12.997v1.148h-12.997zM127.569 24.708h1.893v1.81h-1.893zM130.498 25.04h7.585v1.147h-7.585zM127.569 28.116h1.893v1.81h-1.893zM130.498 28.447h11.46v1.148h-11.46zM127.569 31.657h1.893v1.81h-1.893zM130.498 31.988h9.322v1.148h-9.322zM127.569 35.065h1.893v1.81h-1.893zM130.498 35.396h11.126v1.148h-11.126zM127.616 6.848h11.518v1.148h-11.518zM127.569 21.311h9.344v1.148h-9.344z"
id="path899" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#4d4d4d;stroke:#b3b3b3;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 62.728587,0.17999881 h 20.829 V 41.34 h -20.83 z"
id="path895-5" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#666666;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 63.905587,1.0339988 h 3.496 V 4.389999 h -3.496 z"
id="path897-3" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#e6e6e6;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 64.440587,10.154999 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.463 v 1.148 h -12.463 z m -2.929,3.21 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 10.592 v 1.148 h -10.592 z m -2.929,3.077 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.997 v 1.148 h -12.997 z m -2.929,7.273 h 1.893 V 26.518 h -1.893 z m 2.929,0.332 h 7.585 v 1.147 h -7.585 z m -2.929,3.076001 h 1.893 v 1.81 h -1.893 z m 2.929,0.330999 h 11.46 v 1.147999 h -11.46 z m -2.929,3.21 h 1.893 v 1.809999 h -1.893 z m 2.929,0.330999 h 9.322 v 1.148001 h -9.322 z m -2.929,3.077001 h 1.893 v 1.809999 h -1.893 z m 2.929,0.331001 h 11.126 v 1.147999 h -11.126 z m -2.882,-28.5480012 h 11.518 v 1.1480001 h -11.518 z m -0.047,14.4630002 h 9.344 v 1.148 h -9.344 z"
id="path899-5" />
</svg>

Before

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1,223 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="571.907"
height="156.921"
viewBox="0 0 151.317 41.519"
version="1.1"
id="svg901"
sodipodi:docname="layout-side-menu-with-categories.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata907">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs905" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1036"
id="namedview903"
showgrid="false"
inkscape:zoom="1.6086532"
inkscape:cx="455.61054"
inkscape:cy="16.694081"
inkscape:window-x="0"
inkscape:window-y="21"
inkscape:window-maximized="0"
inkscape:current-layer="svg901" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18zM62.735.18h58.532v41.16H62.735z"
id="path853" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g857">
<path
d="M84.066 21.35l5.055-5.056M82.925 29.835l18.152-18.151M95.893 25.225l3.585-3.586"
id="path855" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M62.735.18h58.532v41.16H62.735z"
id="path861" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v5.063H.18z"
id="path877" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18z"
id="path879" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M1.357 1.034h3.496V4.39H1.357z"
id="path881" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M6.068 1.034h6.102V4.39H6.068z"
id="path883" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g887">
<path
d="M21.51 21.35l5.056-5.056M20.37 29.835L38.52 11.684M33.337 25.225l3.586-3.586"
id="path885" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.28v41.16h-25.28z"
id="path889" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v5.063h-25.257z"
id="path891" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v41.16h-25.257z"
id="path893" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#4d4d4d"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h20.829v41.16h-20.83z"
id="path895" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.034 1.034h3.496V4.39h-3.496z"
id="path897" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.569 10.155h1.893v1.81h-1.893zM130.498 10.486h12.463v1.148h-12.463zM127.569 13.696h1.893v1.81h-1.893zM130.498 14.027h10.592v1.148h-10.592zM127.569 17.104h1.893v1.81h-1.893zM130.498 17.435h12.997v1.148h-12.997zM127.569 24.708h1.893v1.81h-1.893zM130.498 25.04h7.585v1.147h-7.585zM127.569 28.116h1.893v1.81h-1.893zM130.498 28.447h11.46v1.148h-11.46zM127.569 31.657h1.893v1.81h-1.893zM130.498 31.988h9.322v1.148h-9.322zM127.569 35.065h1.893v1.81h-1.893zM130.498 35.396h11.126v1.148h-11.126zM127.616 6.848h11.518v1.148h-11.518zM127.569 21.311h9.344v1.148h-9.344z"
id="path899" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#4d4d4d;stroke:#b3b3b3;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 62.728587,0.17999881 h 20.829 V 41.34 h -20.83 z"
id="path895-5" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#666666;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 63.905587,1.0339988 h 3.496 V 4.389999 h -3.496 z"
id="path897-3" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#e6e6e6;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 64.440587,10.154999 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.463 v 1.148 h -12.463 z m -2.929,3.21 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 10.592 v 1.148 h -10.592 z m -2.929,3.077 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.997 v 1.148 h -12.997 z m -2.929,7.273 h 1.893 V 26.518 h -1.893 z m 2.929,0.332 h 7.585 v 1.147 h -7.585 z m -2.929,3.076001 h 1.893 v 1.81 h -1.893 z m 2.929,0.330999 h 11.46 v 1.147999 h -11.46 z m -2.929,3.21 h 1.893 v 1.809999 h -1.893 z m 2.929,0.330999 h 9.322 v 1.148001 h -9.322 z m -2.929,3.077001 h 1.893 v 1.809999 h -1.893 z m 2.929,0.331001 h 11.126 v 1.147999 h -11.126 z m -2.882,-28.5480012 h 11.518 v 1.1480001 h -11.518 z m -0.047,14.4630002 h 9.344 v 1.148 h -9.344 z"
id="path899-5" />
</svg>

Before

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="145.889" height="145.889" viewBox="0 0 38.6 38.6"><g transform="translate(-59.172 -91.92)" stroke="#000" fill="none"><path d="M66.595 111.162h9.997M66.595 103.805h11.4m-11.4 14.714h11.4" stroke-width="3.165"/><rect width="34.535" height="34.535" x="61.205" y="93.952" overflow="visible" rx="5.481" ry="5.481" style="marker:none" color="#000" stroke-width="4.065" stroke-linecap="round" paint-order="fill markers stroke"/></g></svg>

Before

Width:  |  Height:  |  Size: 478 B

View file

@ -1,17 +0,0 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 694 B

View file

@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill="#fff" color="#000" transform="translate(-.067 .224)"><rect width="5.027" height=".777" x="-2.307" y="6.984" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none" transform="rotate(-45)"/><rect width="5.027" height=".777" x="4.859" y="-.595" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none" transform="rotate(45)"/></g></svg>
<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>

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 498 B

Before After
Before After

View file

@ -1 +1,173 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill-opacity=".898" color="#000" transform="matrix(.90559 0 0 .86896 .772 -247.893)"><rect width="1.451" height="1.451" x="4.266" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="290.631" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 2.157)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="292.808" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 4.334)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg935"
sodipodi:docname="side-menu-opener-dark.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata941">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs939" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview937"
showgrid="false"
inkscape:zoom="8.34386"
inkscape:cx="83.025045"
inkscape:cy="14.304895"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg935" />
<g
transform="translate(.3 -286.074)"
fill="#fff"
color="#000"
fill-opacity=".855"
id="g933"
style="fill:#000000;fill-opacity:0.89855075">
<rect
ry="0"
rx="0"
y="288.474"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#000000;fill-opacity:0.89855075"
overflow="visible"
paint-order="stroke markers fill"
id="rect909" />
<g
transform="translate(-.13)"
id="g915"
style="fill:#000000;fill-opacity:0.89855075">
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect911" />
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect913" />
</g>
<rect
ry="0"
rx="0"
y="290.631"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#000000;fill-opacity:0.89855075"
overflow="visible"
paint-order="stroke markers fill"
id="rect917" />
<g
transform="translate(-.13 2.157)"
id="g923"
style="fill:#000000;fill-opacity:0.89855075">
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect919" />
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect921" />
</g>
<rect
ry="0"
rx="0"
y="292.808"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#000000;fill-opacity:0.89855075"
overflow="visible"
paint-order="stroke markers fill"
id="rect925" />
<g
transform="translate(-.13 4.334)"
id="g931"
style="fill:#000000;fill-opacity:0.89855075">
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect927" />
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect929" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

View file

@ -1 +1,102 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><rect width="7.62" height="7.62" x="1.482" y="1.482" fill="none" stroke="#000" stroke-linecap="square" stroke-width=".777" color="#000" overflow="visible" paint-order="markers fill stroke" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="3.148" color="#000" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="6.658" color="#000" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="4.936" color="#000" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1090"
sodipodi:docname="side-menu-opener-hamburger-2-dark.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1096">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1094" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1092"
showgrid="false"
inkscape:zoom="8.34386"
inkscape:cx="84.523455"
inkscape:cy="12.270318"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1090" />
<rect
overflow="visible"
style="marker:none;fill:#000000;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="2.787"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1082" />
<rect
overflow="visible"
style="marker:none;fill:#000000;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="4.655"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1084" />
<rect
overflow="visible"
style="marker:none;fill:#000000;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="6.54"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1086" />
<path
style="marker:none;fill:#000000;fill-opacity:1"
d="M1.243.71A.469.469 0 0 0 .881.88a.469.469 0 0 0-.171.362V9.34c0 .146.067.275.171.362a.469.469 0 0 0 .362.171H9.34a.469.469 0 0 0 .362-.171.469.469 0 0 0 .171-.362V1.243a.469.469 0 0 0-.171-.362A.469.469 0 0 0 9.34.71zm.723 1.256h6.65v6.65h-6.65z"
color="#000"
overflow="visible"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="path1088" />
</svg>

Before

Width:  |  Height:  |  Size: 786 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

@ -1 +1,101 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><rect width="7.62" height="7.62" x="1.482" y="1.482" fill="none" stroke="#fff" stroke-linecap="square" stroke-width=".777" color="#000" overflow="visible" paint-order="markers fill stroke" rx="0" ry="0" style="marker:none"/><g fill="#fff" color="#000" transform="translate(0 .034)"><rect width="5.027" height=".777" x="2.778" y="3.114" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="6.624" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="4.903" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1443"
sodipodi:docname="side-menu-opener-hamburger-2.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1449">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1447" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1445"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="90.508475"
inkscape:cy="4.5762712"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1443" />
<rect
overflow="visible"
style="marker:none;fill:#ffffff;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="2.787"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1435" />
<rect
overflow="visible"
style="marker:none;fill:#ffffff;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="4.655"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1437" />
<rect
overflow="visible"
style="marker:none;fill:#ffffff;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="6.54"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1439" />
<path
style="marker:none;fill:#ffffff;fill-opacity:1"
d="M1.243.71A.469.469 0 0 0 .881.88a.469.469 0 0 0-.171.362V9.34c0 .146.067.275.171.362a.469.469 0 0 0 .362.171H9.34a.469.469 0 0 0 .362-.171.469.469 0 0 0 .171-.362V1.243a.469.469 0 0 0-.171-.362A.469.469 0 0 0 9.34.71zm.723 1.256h6.65v6.65h-6.65z"
color="#000"
overflow="visible"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="path1441" />
</svg>

Before

Width:  |  Height:  |  Size: 809 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

@ -1 +1,92 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g color="#000" transform="matrix(.85624 0 0 .9944 .747 .03)"><g transform="matrix(1.0055 0 0 1 -.013 0)"><rect width="5.839" height=".782" x="2.372" y="2.764" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.839" height=".782" x="2.372" y="7.037" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="5.871" height=".782" x="2.372" y="4.901" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1168"
sodipodi:docname="side-menu-opener-hamburger-dark.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1174">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1172" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1170"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="112.71186"
inkscape:cy="0.16949153"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1168" />
<g
color="#000"
fill="#fff"
fill-opacity=".855"
transform="translate(0 -286.417)"
id="g1166"
style="fill:#000000;fill-opacity:0.99637681">
<rect
ry=".545"
rx="0"
y="288.816"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#000000;fill-opacity:0.99637681"
overflow="visible"
paint-order="stroke markers fill"
id="rect1160" />
<rect
ry=".545"
rx="0"
y="290.973"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#000000;fill-opacity:0.99637681"
overflow="visible"
paint-order="stroke markers fill"
id="rect1162" />
<rect
ry=".545"
rx="0"
y="293.15"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#000000;fill-opacity:0.99637681"
overflow="visible"
paint-order="stroke markers fill"
id="rect1164" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 637 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

@ -1 +1,91 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill="#fff" color="#000" transform="matrix(.85624 0 0 .9944 .747 .03)"><g transform="matrix(1.0055 0 0 1 -.013 0)"><rect width="5.839" height=".782" x="2.372" y="2.764" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.839" height=".782" x="2.372" y="7.037" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="5.871" height=".782" x="2.372" y="4.901" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1382"
sodipodi:docname="side-menu-opener-hamburger.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1388">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1386" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1384"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="108.13559"
inkscape:cy="3.8983051"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1382" />
<g
color="#000"
fill="#fff"
fill-opacity=".855"
transform="translate(0 -286.417)"
id="g1380"
style="fill:#ffffff;fill-opacity:1">
<rect
ry=".545"
rx="0"
y="288.816"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#ffffff;fill-opacity:1"
overflow="visible"
paint-order="stroke markers fill"
id="rect1374" />
<rect
ry=".545"
rx="0"
y="290.973"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#ffffff;fill-opacity:1"
overflow="visible"
paint-order="stroke markers fill"
id="rect1376" />
<rect
ry=".545"
rx="0"
y="293.15"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#ffffff;fill-opacity:1"
overflow="visible"
paint-order="stroke markers fill"
id="rect1378" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 649 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

@ -1 +1,172 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill="#fff" fill-opacity=".946" color="#000" transform="matrix(.90559 0 0 .86896 .772 -247.893)"><rect width="1.451" height="1.451" x="4.266" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="290.631" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 2.157)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="292.808" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 4.334)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1321"
sodipodi:docname="side-menu-opener.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1327">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1325" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1323"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="78.983051"
inkscape:cy="5.5932203"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1321" />
<g
transform="translate(.3 -286.074)"
fill="#fff"
color="#000"
fill-opacity=".855"
id="g1319"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
ry="0"
rx="0"
y="288.474"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
overflow="visible"
paint-order="stroke markers fill"
id="rect1295" />
<g
transform="translate(-.13)"
id="g1301"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1297" />
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1299" />
</g>
<rect
ry="0"
rx="0"
y="290.631"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
overflow="visible"
paint-order="stroke markers fill"
id="rect1303" />
<g
transform="translate(-.13 2.157)"
id="g1309"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1305" />
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1307" />
</g>
<rect
ry="0"
rx="0"
y="292.808"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
overflow="visible"
paint-order="stroke markers fill"
id="rect1311" />
<g
transform="translate(-.13 4.334)"
id="g1317"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1313" />
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1315" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

View file

@ -2,25 +2,12 @@
namespace OCA\SideMenu\AppInfo;
use OC\AllConfig;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\AppFramework\Http\Request;
use OC;
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OC\User\User;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\INavigationManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\Util;
use Psr\Container\ContainerInterface;
@ -29,123 +16,99 @@ use Psr\Container\ContainerInterface;
*
* @author Simon Vieille <simon@deblan.fr>
*/
class Application extends App implements IBootstrap
class Application extends App
{
public const APP_ID = 'side_menu';
public const APP_NAME = 'Custom menu';
/**
* @var OC\AllConfig
*/
protected $config;
protected AllConfig $config;
protected ContentSecurityPolicyNonceManager $cspnm;
protected Request $request;
protected ?User $user = null;
/**
* @var ContentSecurityPolicyNonceManager
*/
protected $cspnm;
/**
* @var User
*/
protected $user;
/**
* {@inheritdoc}
*/
public function __construct(array $urlParams = [])
{
parent::__construct(self::APP_ID, $urlParams);
parent::__construct('side_menu', $urlParams);
$this->config = OC::$server->getConfig();
$this->cspnm = OC::$server->getContentSecurityPolicyNonceManager();
$this->user = OC::$server[IUserSession::class]->getUser();
}
public function register(IRegistrationContext $context): void
/**
* Checks if this app is enabled.
*/
public function isEnabled(): bool
{
$context->registerService(CategoryRepository::class, function (ContainerInterface $c) {
return new CategoryRepository(
$c->get(CategoryFetcher::class),
$c->get(ConfigProxy::class),
$c->get(IConfig::class),
$c->get(IFactory::class),
$c->get(IUserSession::class)
);
});
$context->registerService(AppRepository::class, function (ContainerInterface $c) {
return new AppRepository(
$c->get(\OC_App::class),
$c->get(INavigationManager::class),
$c->get(IFactory::class),
$c->get(ConfigProxy::class),
$c->get(CategoryRepository::class),
$c->get(IEventDispatcher::class),
$c->get(IUserSession::class)
);
});
$context->registerService(ConfigProxy::class, function (ContainerInterface $c) {
return new ConfigProxy(
$c->get(IConfig::class),
);
});
$context->registerService(Color::class, function (ContainerInterface $c) {
return new Color(
$c->get(ThemingDefaults::class),
);
});
}
public function boot(IBootContext $context): void
{
$this->config = \OC::$server->getConfig();
$this->cspnm = \OC::$server->getContentSecurityPolicyNonceManager();
$this->user = \OC::$server[IUserSession::class]->getUser();
$this->request = \OC::$server->getRequest();
if (!$this->isEnabled()) {
return;
}
$this->addAssets();
}
protected function isEnabled(): bool
{
if (isset($this->request->server['HTTP_USER_AGENT']) && preg_match('/MemoriesNative/', $this->request->server['HTTP_USER_AGENT'])) {
return false;
}
$enabled = true;
$isForced = (bool) $this->config->getAppValue(self::APP_ID, 'force', '0');
if (null !== $this->user && !$isForced) {
$enabled = (bool) $this->config->getUserValue(
$this->user->getUid(),
self::APP_ID,
'enabled',
$this->config->getAppValue(
self::APP_ID,
'default-enabled',
'1'
)
);
if (null !== $this->user) {
$enabled = (bool) $this->config->getUserValue($this->user->getUid(), 'side_menu', 'enabled', '1');
}
return $enabled;
}
protected function addAssets()
/**
* Registes services.
*/
public function registerServices()
{
Util::addScript(self::APP_ID, 'side_menu-menu');
$container = $this->getContainer();
$assets = [
'stylesheet' => [
'route' => 'side_menu.Css.stylesheet',
'type' => 'link',
'route_attr' => 'href',
'attr' => [
'rel' => 'stylesheet',
],
$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',
],
];
''
);
$cache = $this->config->getAppValue(self::APP_ID, 'cache', '0');
foreach ($assets as $value) {
$route = \OC::$server->getURLGenerator()->linkToRoute(
$value['route'],
['v' => $cache]
);
$value['attr'][$value['route_attr']] = $route;
Util::addHeader($value['type'], $value['attr'], '');
}
Util::addHeader(
'script',
[
'src' => $script,
'nonce' => $this->cspnm->getNonce(),
],
''
);
}
}

View file

@ -1,218 +0,0 @@
<?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 OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\SideMenu\Service\LangRepository;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IURLGenerator;
class AdminSettingController extends Controller
{
public function __construct(
$appName,
IRequest $request,
protected IConfig $config,
protected ConfigProxy $configProxy,
protected IURLGenerator $urlGenerator,
protected Color $color,
protected LangRepository $langRepository,
) {
parent::__construct($appName, $request);
}
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/admin/cache/remove')]
public function removeCache(): RedirectResponse
{
$this->config->setAppValue(Application::APP_ID, 'cache-categories', '[]');
return new RedirectResponse($this->urlGenerator->linkToRoute('settings.AdminSettings.index', [
'section' => Application::APP_ID,
]).'#more');
}
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/admin/config/export')]
public function exportConfiguration(): DataDownloadResponse
{
$keys = $this->config->getAppKeys(Application::APP_ID);
$appConfig = [];
$excludedKeys = [
'cache',
'cache-categories',
'langs',
];
foreach ($keys as $key) {
if (in_array($key, $excludedKeys)) {
continue;
}
$appConfig[$key] = $this->config->getAppValue(Application::APP_ID, $key);
}
return new DataDownloadResponse(
json_encode($appConfig, JSON_PRETTY_PRINT),
'config.json',
'text/json'
);
}
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/admin/config')]
public function configuration(): JSONResponse
{
$keys = $this->config->getAppKeys(Application::APP_ID);
$booleans = [
'opener-only',
'opener-hover',
'display-logo',
'use-avatar',
'add-logo-link',
'show-settings',
'loader-enabled',
'always-displayed',
'enabled',
'force',
'big-menu',
'external-sites-in-top-menu',
'force-light-icon',
'side-with-categories',
'default-enabled',
];
$arrays = [
'apps-categories-custom',
'big-menu-hidden-apps',
'apps-order',
'categories-custom',
'categories-order',
'target-blank-apps',
'top-menu-apps',
'top-side-menu-apps',
];
$integers = [
'background-color-opacity',
'dark-mode-background-color-opacity',
'dark-mode-icon-invert-filter',
'dark-mode-icon-opacity',
'icon-invert-filter',
'icon-opacity',
'target-blank-mode',
'top-menu-mouse-over-hidden-label',
];
$defaults = [
'opener-only' => '0',
'opener-hover' => '0',
'display-logo' => '1',
'use-avatar' => '0',
'add-logo-link' => '1',
'show-settings' => '0',
'loader-enabled' => '1',
'always-displayed' => '0',
'enabled' => '1',
'force' => '0',
'big-menu' => '0',
'external-sites-in-top-menu' => '0',
'force-light-icon' => '0',
'side-with-categories' => '0',
'cache' => '1',
'default-enabled' => '1',
'apps-categories-custom' => '[]',
'big-menu-hidden-apps' => '[]',
'apps-order' => '[]',
'categories-custom' => '[]',
'categories-order' => '[]',
'target-blank-apps' => '[]',
'top-menu-apps' => '[]',
'top-side-menu-apps' => '[]',
'cache-categories' => '[]',
'background-color-opacity' => '100',
'dark-mode-background-color-opacity' => '100',
'dark-mode-icon-invert-filter' => '0',
'dark-mode-icon-opacity' => '100',
'icon-invert-filter' => '0',
'icon-opacity' => '100',
'top-menu-mouse-over-hidden-label' => '0',
'opener' => 'side-menu-opener',
'dark-mode-opener' => 'side-menu-opener',
'size-icon' => 'normal',
'size-text' => 'normal',
'opener-position' => 'before',
'background-color' => $this->color->getPrimaryColor(),
'background-color-to' => $this->color->getLightenPrimaryColor(),
'current-app-background-color' => $this->color->getDarkenPrimaryColor(),
'text-color' => $this->color->getTextColorPrimary(),
'loader-color' => $this->color->getLightenPrimaryColor(),
'dark-mode-background-color' => $this->color->getDarkenPrimaryColor(),
'dark-mode-background-color-to' => $this->color->getDarkenPrimaryColor(),
'dark-mode-current-app-background-color' => $this->color->getDarkenPrimaryColor2(),
'dark-mode-text-color' => $this->color->getTextColorPrimary(),
'dark-mode-loader-color' => $this->color->getLightenPrimaryColor(),
'categories-order-type' => 'default',
];
$config = [];
foreach ($keys as $key) {
if (!isset($defaults[$key])) {
continue;
}
if (in_array($key, $booleans)) {
$config[$key] = $this->configProxy->getAppValueBool($key, $defaults[$key]);
} elseif (in_array($key, $arrays)) {
$config[$key] = $this->configProxy->getAppValueArray($key, $defaults[$key]);
} elseif (in_array($key, $integers)) {
$config[$key] = $this->configProxy->getAppValueInt($key, $defaults[$key]);
} else {
$config[$key] = $this->configProxy->getAppValue($key, $defaults[$key]);
}
}
foreach ($defaults as $key => $default) {
if (!array_key_exists($key, $config)) {
$config[$key] = $default;
}
}
$config['langs'] = $this->langRepository->getUsedLangs();
return new JSONResponse($config);
}
}

View file

@ -1,98 +0,0 @@
<?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 OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
class AppController extends Controller
{
public function __construct(
string $appName,
IRequest $request,
protected AppRepository $appRepository,
protected IURLGenerator $urlGenerator,
protected ConfigProxy $config,
) {
parent::__construct($appName, $request);
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[FrontpageRoute(verb: 'GET', url: '/')]
public function index(): RedirectResponse
{
$user = \OC::$server[IUserSession::class]->getUser();
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$hiddenApps = $this->config->getAppValueArray('big-menu-hidden-apps', '[]');
$isForced = $this->config->getAppValueBool('force', '0');
$userTopMenuApps = $this->config->getUserValueArray($user, 'top-menu-apps', '[]');
$apps = $this->appRepository->getOrderedApps($user);
if (!$isForced && !empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps;
}
foreach ($apps as $app) {
$inTopMenuApps = in_array($app['id'], $topMenuApps);
$inHiddenApps = in_array($app['id'], $hiddenApps);
if (!$inTopMenuApps && $inHiddenApps) {
continue;
}
return $this->redirectToApp($app, true);
}
return $this->redirectToApp('files');
}
protected function redirectToApp($app, bool $isHref = false): RedirectResponse
{
if (!$isHref) {
$isIgnoreFrontController = true === \OC::$server->getConfig()->getSystemValue(
'htaccess.IgnoreFrontController',
false
);
$isFrontControllerActive = 'true' === getenv('front_controller_active');
if ($isIgnoreFrontController || $isFrontControllerActive) {
$path = '/apps/%s/';
} else {
$path = '/index.php/apps/%s/';
}
$url = $this->urlGenerator->getAbsoluteURL(sprintf($path, $app));
} else {
$url = $app['href'];
}
return new RedirectResponse($url);
}
}

View file

@ -1,74 +0,0 @@
<?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 OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUserSession;
class CoreController extends Controller
{
public function __construct(
string $appName,
IRequest $request,
protected ConfigProxy $config,
protected AppRepository $appRepository,
) {
parent::__construct($appName, $request);
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/core/apps')]
public function items(): JSONResponse
{
$user = \OC::$server[IUserSession::class]->getUser();
$items = [];
if (!$user) {
return new JSONResponse([
'items' => $items,
]);
}
$apps = $this->appRepository->getOrderedApps($user);
$keys = ['id', 'name', 'category', 'href', 'icon'];
foreach ($apps as &$app) {
foreach ($app as $key => $value) {
if (!in_array($key, $keys)) {
unset($app[$key]);
}
}
}
return new JSONResponse([
'items' => $apps,
]);
}
}

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,120 +18,78 @@
namespace OCA\SideMenu\Controller;
use OC\User\User;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\Theming\ThemingDefaults;
use OC;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUserSession;
class CssController extends Controller
{
protected ?User $user;
/**
* @var \OCP\IConfig
*/
protected $config;
public function __construct(
string $appName,
IRequest $request,
protected ConfigProxy $config,
protected ThemingDefaults $theming,
protected Color $color,
) {
/**
* @param string $appName
*/
public function __construct($appName, IRequest $request, IConfig $config)
{
parent::__construct($appName, $request);
$this->user = \OC::$server[IUserSession::class]->getUser();
$this->config = $config;
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/css/stylesheet')]
public function stylesheet(): TemplateResponse
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @return Response
*/
public function stylesheet()
{
$response = new TemplateResponse(Application::APP_ID, 'css/stylesheet', $this->getConfig(), 'blank');
$backgroundColor = $this->config->getAppValue('side_menu', 'background-color', '#333333');
$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 = [
'vars' => [
'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo,
'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'),
'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),
'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),
'size-icon' => $this->config->getAppValue('side_menu', 'size-icon', '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->addHeader('Content-Type', 'text/css');
return $response;
}
protected function getConfig(): array
{
$isForced = $this->config->getAppValueBool('force', '0');
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$topSideMenuApps = $this->config->getAppValueArray('top-side-menu-apps', '[]');
if ($this->user) {
$userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]');
$userTopSideMenuApps = $this->config->getUserValueArray($this->user, 'top-side-menu-apps', '[]');
if (!empty($userTopMenuApps) && !$isForced) {
$topMenuApps = $userTopMenuApps;
}
if (!empty($userTopSideMenuApps) && !$isForced) {
$topSideMenuApps = $userTopSideMenuApps;
}
}
$lightenPrimaryColor = $this->color->getLightenPrimaryColor();
$darkenPrimaryColor = $this->color->getDarkenPrimaryColor();
$darkenPrimaryColor2 = $this->color->getDarkenPrimaryColor2();
$textColor = $this->color->getTextColorPrimary();
$backgroundColor = $this->config->getAppValue('background-color', $darkenPrimaryColor);
$backgroundColorTo = $this->config->getAppValue('background-color-to', $darkenPrimaryColor);
$opacity = $this->config->getAppValueInt('background-color-opacity', '100');
$backgroundOpacity = dechex($opacity * 255 / 100);
$backgroundColor .= $backgroundOpacity;
$backgroundColorTo .= $backgroundOpacity;
$darkBackgroundColor = $this->config->getAppValue('dark-mode-background-color', $darkenPrimaryColor);
$darkBackgroundColorTo = $this->config->getAppValue('dark-mode-background-color-to', $darkenPrimaryColor);
$darkOpacity = $this->config->getAppValueInt('dark-mode-background-color-opacity', '100');
$darkBackgroundOpacity = dechex($opacity * 255 / 100);
$darkBackgroundColor .= $darkBackgroundOpacity;
$darkBackgroundColorTo .= $darkBackgroundOpacity;
return [
'vars' => [
'light' => [
'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo,
'current-app-background-color' => $this->config->getAppValue(
'current-app-background-color',
$darkenPrimaryColor2
),
'loader-color' => $this->config->getAppValue('loader-color', $lightenPrimaryColor),
'text-color' => $this->config->getAppValue('text-color', $textColor),
'opener' => $this->config->getAppValue('opener', 'side-menu-opener'),
'icon-invert-filter' => abs($this->config->getAppValueInt('icon-invert-filter', '0')).'%',
'icon-opacity' => abs($this->config->getAppValueInt('icon-opacity', '100') / 100),
],
'dark' => [
'background-color' => $darkBackgroundColor,
'background-color-to' => $darkBackgroundColorTo,
'current-app-background-color' => $this->config->getAppValue(
'dark-mode-current-app-background-color',
$darkenPrimaryColor2
),
'loader-color' => $this->config->getAppValue('dark-mode-loader-color', $lightenPrimaryColor),
'text-color' => $this->config->getAppValue('dark-mode-text-color', $textColor),
'opener' => $this->config->getAppValue('dark-mode-opener', 'side-menu-opener'),
'icon-invert-filter' => abs($this->config->getAppValueInt('dark-mode-icon-invert-filter', '0')).'%',
'icon-opacity' => abs($this->config->getAppValueInt('dark-mode-icon-opacity', '100') / 100),
]
],
'opener-only' => $this->config->getAppValueBool('opener-only', '0'),
'size-icon' => $this->config->getAppValue('size-icon', 'normal'),
'size-text' => $this->config->getAppValue('size-text', 'normal'),
'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'),
];
}
}

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,140 +18,76 @@
namespace OCA\SideMenu\Controller;
use OC\User\User;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\Theming\ThemingDefaults;
use OC;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\IAvatarManager;
use OCP\INavigationManager;
use OCP\IURLGenerator;
class JsController extends Controller
{
protected ?User $user;
/**
* @var \OCP\IConfig
*/
protected $config;
public function __construct(
string $appName,
IRequest $request,
protected ConfigProxy $config,
protected ThemingDefaults $themingDefaults,
protected IFactory $l10nFactory,
protected IAvatarManager $avatarManager,
protected IUserSession $userSession,
protected INavigationManager $navigationManager,
protected IURLGenerator $urlGenerator,
) {
/**
* @param string $appName
*/
public function __construct($appName, IRequest $request, IConfig $config)
{
parent::__construct($appName, $request);
$this->user = $this->userSession->getUser();
$this->config = $config;
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/js/config')]
public function config(): JSONResponse
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @return Response
*/
public function script()
{
return new JSONResponse($this->getConfig());
}
$user = OC::$server[IUserSession::class]->getUser();
$topMenuApps = (array) json_decode($this->config->getAppValue('side_menu', 'top-menu-apps', '[]'), true);
$targetBlankApps = (array) json_decode($this->config->getAppValue('side_menu', 'target-blank-apps', '[]'), true);
protected function getConfig(): array
{
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$topSideMenuApps = $this->config->getAppValueArray('top-side-menu-apps', '[]');
$targetBlankApps = $this->config->getAppValueArray('target-blank-apps', '[]');
$useAvatar = $this->config->getAppValueBool('use-avatar', '0');
$isForced = $this->config->getAppValueBool('force', '0');
$addLogoLink = $this->config->getAppValueBool('add-logo-link', '1');
$appsOrder = $this->config->getAppValueArray('apps-order', '[]');
$avatar = null;
$settings = null;
if ($user) {
$userTopMenuApps = (array) json_decode($this->config->getUserValue($user->getUid(), 'side_menu', 'top-menu-apps', '[]'), true);
if ($this->user) {
$userAppsOrder = $this->config->getUserValueArray($this->user, 'apps-order', '[]');
$userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]');
$userTopSideMenuApps = $this->config->getUserValueArray($this->user, 'top-side-menu-apps', '[]');
if (!empty($userTopMenuApps) && !$isForced) {
if (!empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps;
}
if (!empty($userTopSideMenuApps) && !$isForced) {
$topSideMenuApps = $userTopSideMenuApps;
}
$userTargetBlankMode = (int) $this->config->getUserValue($user->getUid(), 'side_menu', 'target-blank-mode', '1');
$userTargetBlankApps = (array) json_decode($this->config->getUserValue($user->getUid(), 'side_menu', 'target-blank-apps', '[]'), true);
if (!empty($userAppsOrder) && !$isForced) {
$appsOrder = $userAppsOrder;
}
$userTargetBlankMode = $this->config->getUserValueInt($this->user, 'target-blank-mode', '1');
$userTargetBlankApps = $this->config->getUserValueArray($this->user, 'target-blank-apps', '[]');
if (2 === $userTargetBlankMode && !$isForced) {
if ($userTargetBlankMode === 2) {
$targetBlankApps = $userTargetBlankApps;
}
$isAvatarSet = $this->avatarManager->getAvatar($this->user->getUID())->exists();
if ($useAvatar && $isAvatarSet) {
$avatar = $this->urlGenerator->linkToRoute('core.avatar.getAvatar', [
'userId' => $this->user->getUID(),
'size' => 128,
'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0),
]);
}
if ($this->config->getAppValueBool('show-settings', '0')) {
$settingsNav = $this->navigationManager->getAll('settings');
if (isset($settingsNav['settings'])) {
$settings = [
'href' => $settingsNav['settings']['href'],
'name' => $settingsNav['settings']['name'],
'avatar' => $this->urlGenerator->linkToRoute('core.avatar.getAvatar', [
'userId' => $this->user->getUID(),
'size' => 32,
'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0),
]),
];
}
}
}
$indexUrl = $this->urlGenerator->linkTo('', 'index.php');
return [
'opener-position' => $this->config->getAppValue('opener-position', 'before'),
'opener-hover' => $this->config->getAppValueBool('opener-hover', '0'),
'external-sites-in-top-menu' => $this->config->getAppValueBool('external-sites-in-top-menu', '0'),
'force-light-icon' => $this->config->getAppValueBool('force-light-icon', '0'),
'display-logo' => $this->config->getAppValueBool('display-logo', '1'),
'use-avatar' => $this->config->getAppValueBool('use-avatar', '0'),
'hide-when-no-apps' => $this->config->getAppValueBool('hide-when-no-apps', '0'),
'loader-enabled' => $this->config->getAppValueBool('loader-enabled', '1'),
'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'),
'side-with-categories' => $this->config->getAppValueBool('side-with-categories', '0'),
'big-menu' => $this->config->getAppValueBool('big-menu', '0'),
'big-menu-hidden-apps' => $this->config->getAppValueArray('big-menu-hidden-apps', '[]'),
'apps-order' => $appsOrder,
'avatar' => $avatar,
$parameters = [
'opener-position' => $this->config->getAppValue('side_menu', 'opener-position', 'before'),
'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),
'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'),
'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,
'top-side-menu-apps' => $topSideMenuApps,
'top-menu-mouse-over-hidden-label' => $this->config->getAppValueInt(
'top-menu-mouse-over-hidden-label',
'0'
),
'target-blank-apps' => $targetBlankApps,
'settings' => $settings,
'logo' => $this->themingDefaults->getLogo(),
'logo-link' => $addLogoLink ? $indexUrl : null,
];
$response = new TemplateResponse('side_menu', 'js/script', $parameters, 'blank');
$response->addHeader('Content-Type', 'text/javascript');
return $response;
}
}

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,41 +18,83 @@
namespace OCA\SideMenu\Controller;
use OC;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\URLGenerator;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
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(
string $appName,
$appName,
IRequest $request,
protected ConfigProxy $config,
protected AppRepository $appRepository,
protected CategoryRepository $categoryRepository,
protected URLGenerator $router,
protected IFactory $l10nFactory,
) {
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;
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/nav/items')]
public function items(): JSONResponse
/**
* @NoAdminRequired
* @NoCSRFRequired
* @PublicPage
*
* @return JSONResponse
*/
public function items()
{
$user = \OC::$server[IUserSession::class]->getUser();
$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) {
@ -62,31 +103,28 @@ class NavController extends Controller
]);
}
$apps = $this->appRepository->getOrderedApps($user);
$categoriesLabels = $this->categoryRepository->getOrderedCategories();
$hiddenApps = $this->config->getAppValueArray('big-menu-hidden-apps', '[]');
$isForced = $this->config->getAppValueBool('force', '0');
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$topSideMenuApps = $this->config->getAppValueArray('top-side-menu-apps', '[]');
$userTopSideMenuApps = $this->config->getUserValueArray($user, 'top-side-menu-apps', '[]');
$userTopMenuApps = $this->config->getUserValueArray($user, 'top-menu-apps', '[]');
$appsCategories = [];
$categoriesAppsCount = [];
$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 (!$isForced && !empty($userTopMenuApps)) {
if (!empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps;
}
if (!$isForced && !empty($userTopSideMenuApps)) {
$topSideMenuApps = $userTopSideMenuApps;
foreach ($categoriesLabels as $k => $category) {
$categoriesLabels[$category['id']] = $category['translations'][$currentLanguage]['name'] ?? $category['translations']['en']['name'];
unset($categoriesLabels[$k]);
}
foreach ($apps as $app) {
$inTopMenuApps = in_array($app['id'], $topMenuApps);
$inTopSideMenuApps = in_array($app['id'], $topSideMenuApps);
$inHiddenApps = in_array($app['id'], $hiddenApps);
$categoriesLabels['external_links'] = $this->l10nFactory->get('external')->t('External sites');
if (($inTopMenuApps && !$inTopSideMenuApps) || $inHiddenApps) {
$items['other'] = [
'name' => '',
'apps' => [],
];
foreach ($apps as $app) {
if (in_array($app['id'], $topMenuApps)) {
continue;
}
@ -100,8 +138,7 @@ class NavController extends Controller
foreach ($categories as $category) {
if (!isset($items[$category])) {
$items[$category] = [
'name' => $categoriesLabels[$category] ?? $category,
'categoryId' => $category,
'name' => $categoriesLabels[$category],
'apps' => [],
];
}
@ -115,7 +152,6 @@ class NavController extends Controller
$appsCategories[$app['id']][] = $category;
$items[$category]['apps'][$app['id']] = [
'id' => $app['id'],
'name' => $app['name'],
'href' => $app['href'],
'icon' => $app['icon'],
@ -154,29 +190,15 @@ class NavController extends Controller
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) use ($categoriesLabels) {
foreach ($categoriesLabels as $key => $value) {
if ('other' === $a['categoryId']) {
return -1;
}
if ('other' === $b['categoryId']) {
return 1;
}
if ($a['categoryId'] === $key) {
return -1;
}
if ($b['categoryId'] === $key) {
return 1;
}
}
return 0;
usort($items, function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1;
});
return new JSONResponse([

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,33 +18,41 @@
namespace OCA\SideMenu\Controller;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUserSession;
class PersonalSettingController extends Controller
{
public function __construct(
$appName,
IRequest $request,
protected IConfig $config,
protected ConfigProxy $configProxy,
protected IUserSession $userSession,
) {
/**
* @var \OCP\IConfig
*/
protected $config;
/**
* @var IUserSession
*/
private $userSession;
public function __construct($appName, IRequest $request, IConfig $config, IUserSession $userSession)
{
parent::__construct($appName, $request);
$this->config = $config;
$this->userSession = $userSession;
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/user/valueSet')]
public function valueSet($name, $value): array
/**
* @NoAdminRequired
*
* @param mixed $name
* @param mixed $value
*
* @return Response
*/
public function valueSet($name, $value)
{
$doSave = false;
$user = $this->userSession->getUser();
@ -66,7 +73,7 @@ class PersonalSettingController extends Controller
}
}
if (in_array($name, ['target-blank-apps', 'top-menu-apps', 'top-side-menu-apps', 'apps-order'])) {
if ('target-blank-apps' === $name) {
$doSave = true;
$data = json_decode($value, true);
@ -81,12 +88,23 @@ class PersonalSettingController extends Controller
}
}
if ($this->configProxy->getAppValueBool('force', '0')) {
$doSave = false;
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) {
$this->config->setUserValue($user->getUid(), Application::APP_ID, $name, $value);
$this->config->setUserValue($user->getUid(), 'side_menu', $name, $value);
return [
'name' => $name,
@ -96,63 +114,4 @@ class PersonalSettingController extends Controller
return [];
}
#[NoCSRFRequired]
#[NoAdminRequired]
#[FrontpageRoute(verb: 'GET', url: '/user/config')]
public function configuration(): JSONResponse
{
$user = $this->userSession->getUser();
$keys = $this->config->getUserKeys($user->getUid(), Application::APP_ID);
$booleans = [
'enabled',
];
$arrays = [
'apps-order',
'target-blank-apps',
'top-menu-apps',
'top-side-menu-apps',
];
$integers = [
'target-blank-mode',
];
$defaults = [
'enabled' => '1',
'target-blank-mode' => '1',
'apps-order' => '[]',
'target-blank-apps' => '[]',
'top-menu-apps' => '[]',
'top-side-menu-apps' => '[]',
];
$config = [];
foreach ($keys as $key) {
if (!isset($defaults[$key])) {
continue;
}
if (in_array($key, $booleans)) {
$config[$key] = $this->configProxy->getUserValueBool($user, $key, $defaults[$key]);
} elseif (in_array($key, $arrays)) {
$config[$key] = $this->configProxy->getUserValueArray($user, $key, $defaults[$key]);
} elseif (in_array($key, $integers)) {
$config[$key] = $this->configProxy->getUserValueInt($user, $key, $defaults[$key]);
} else {
$config[$key] = $this->configProxy->getUserValue($user, $key, $defaults[$key]);
}
}
foreach ($defaults as $key => $default) {
if (!array_key_exists($key, $config)) {
$config[$key] = $default;
}
}
return new JSONResponse($config);
}
}

View file

@ -2,13 +2,7 @@
namespace OCA\SideMenu\Service;
use OC\User\User;
use OCA\SideMenu\AppInfo\Application;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\INavigationManager;
use OCP\IUserSession;
use OC_App;
use OCP\L10N\IFactory;
/**
@ -18,29 +12,30 @@ use OCP\L10N\IFactory;
*/
class AppRepository
{
public function __construct(
protected \OC_App $ocApp,
protected INavigationManager $navigationManager,
protected IFactory $l10nFactory,
protected ConfigProxy $config,
protected CategoryRepository $categoryRepository,
protected IEventDispatcher $dispatcher,
protected IUserSession $userSession,
) {
$this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent(
$this->userSession->isLoggedIn(),
new TemplateResponse(Application::APP_NAME, '')
));
/**
* @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(): array
public function getVisibleApps()
{
$navigation = $this->navigationManager->getAll();
$appCategoriesCustom = $this->config->getAppValueArray('apps-categories-custom', '[]');
$categories = $this->categoryRepository->getOrderedCategories();
$navigation = $this->ocApp->getNavigation();
$apps = $this->ocApp->listAllApps();
$visibleApps = [];
@ -49,7 +44,7 @@ class AppRepository
foreach ([$app['id'], $app['id'].'_index'] as $id) {
if (isset($navigation[$id])) {
$app['name'] = $this->getAppName($app);
$app['name'] = $this->l10nFactory->get($id)->t($app['name']);
$app['href'] = $navigation[$id]['href'];
$app['icon'] = $navigation[$id]['icon'];
@ -62,25 +57,17 @@ class AppRepository
if ('external_index' === substr($app['id'], 0, 14)) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->getAppName($app),
'name' => $this->l10nFactory->get($app['id'])->t($app['name']),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [
'external_links',
],
];
} elseif ('tables_application' === substr($app['id'], 0, 18)) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->getAppName($app),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [],
];
} elseif ('files' === $app['id']) {
$visibleApps[$app['id']] = [
'id' => $app['id'],
'name' => $this->getAppName($app),
'name' => $this->l10nFactory->get($app['id'])->t($app['name']),
'href' => $app['href'],
'icon' => $app['icon'],
'category' => [],
@ -88,48 +75,10 @@ class AppRepository
}
}
foreach ($visibleApps as $id => $app) {
if (isset($appCategoriesCustom[$id], $categories[$appCategoriesCustom[$id]])) {
$visibleApps[$id]['category'] = [$appCategoriesCustom[$id]];
}
}
usort($visibleApps, function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1;
});
return $visibleApps;
}
public function getAppName($app): string
{
return $this->config->getAppValue(
'app.navigation.name',
$this->l10nFactory->get($app['id'])->t($app['name']),
$app['id']
);
}
public function getOrderedApps(?User $user = null): array
{
$apps = $this->getVisibleApps();
$orders = $this->config->getAppValueArray('apps-order', '[]');
if (null !== $user && !$this->config->getAppValueBool('force', '0')) {
$userOrders = $this->config->getUserValueArray($user, 'apps-order', '[]');
if (!empty($userOrders)) {
$orders = $userOrders;
}
}
usort($apps, function ($a, $b) use ($orders) {
$ak = array_keys($orders, $a['id'])[0] ?? null;
$bk = array_keys($orders, $b['id'])[0] ?? null;
if (null === $ak || null === $bk) {
return ($a['name'] < $b['name']) ? -1 : 1;
}
return $ak < $bk ? -1 : 1;
});
return $apps;
}
}

View file

@ -1,88 +0,0 @@
<?php
namespace OCA\SideMenu\Service;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OCA\SideMenu\AppInfo\Application;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\L10N\IFactory;
/**
* class CategoryRepository.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class CategoryRepository
{
public function __construct(
protected CategoryFetcher $categoryFetcher,
protected ConfigProxy $config,
protected IConfig $iConfig,
protected IFactory $l10nFactory,
protected IUserSession $userSession,
) {
}
/**
* Retrieves categories.
*/
public function getOrderedCategories(): array
{
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$type = $this->config->getAppValue('categories-order-type', 'default');
$order = $this->config->getAppValueArray('categories-order', '[]');
$categoriesLabels = $this->config->getAppValueArray('cache-categories', '[]');
$customCategories = $this->config->getAppValueArray('categories-custom', '[]');
if (empty($categoriesLabels)) {
$categoriesLabels = $this->categoryFetcher->get();
$this->iConfig->setAppValue(Application::APP_ID, 'cache-categories', json_encode($categoriesLabels));
}
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');
$categoriesLabels['other'] = '';
$user = $this->userSession->getUser();
if ($user) {
$lang = $this->iConfig->getUserValue($user->getUid(), 'core', 'lang');
} else {
$lang = 'en';
}
foreach ($customCategories as $category) {
$categoriesLabels[$category['id']] = $category[$lang] ?? $category['en'];
}
asort($categoriesLabels);
if ('custom' === $type) {
$ordered = [];
foreach ($order as $id) {
if (isset($categoriesLabels[$id])) {
$ordered[$id] = $categoriesLabels[$id];
}
}
foreach ($categoriesLabels as $id => $value) {
if (!isset($ordered[$id])) {
$ordered[$id] = $value;
}
}
$categoriesLabels = $ordered;
}
return $categoriesLabels;
}
}

View file

@ -1,65 +0,0 @@
<?php
namespace OCA\SideMenu\Service;
use OCA\Theming\ThemingDefaults;
/**
* class Color.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class Color
{
public function __construct(protected ThemingDefaults $theming)
{
}
/**
* @thanks https://stackoverflow.com/posts/54393956/revision
*/
public function adjustBrightness(string $hexCode, float $adjustPercent): string
{
$hexCode = ltrim($hexCode, '#');
if (3 == strlen($hexCode)) {
$hexCode = $hexCode[0].$hexCode[0].$hexCode[1].$hexCode[1].$hexCode[2].$hexCode[2];
}
$hexCode = array_map('hexdec', str_split($hexCode, 2));
foreach ($hexCode as &$color) {
$adjustableLimit = $adjustPercent < 0 ? $color : 255 - $color;
$adjustAmount = ceil($adjustableLimit * $adjustPercent);
$color = str_pad(dechex($color + $adjustAmount), 2, '0', STR_PAD_LEFT);
}
return '#'.implode($hexCode);
}
public function getPrimaryColor()
{
return $this->theming->getColorPrimary();
}
public function getLightenPrimaryColor()
{
return $this->adjustBrightness($this->getPrimaryColor(), 0.2);
}
public function getDarkenPrimaryColor()
{
return $this->adjustBrightness($this->getPrimaryColor(), -0.2);
}
public function getDarkenPrimaryColor2()
{
return $this->adjustBrightness($this->getPrimaryColor(), -0.3);
}
public function getTextColorPrimary()
{
return $this->theming->getTextColorPrimary();
}
}

View file

@ -1,64 +0,0 @@
<?php
namespace OCA\SideMenu\Service;
use OC\User\User;
use OCA\SideMenu\AppInfo\Application;
use OCP\IConfig;
/**
* class Config.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class ConfigProxy
{
public function __construct(protected IConfig $config)
{
$this->config = $config;
}
public function getAppValue(string $name, string $default, $appId = null): string
{
return (string) $this->config->getAppValue($appId ?? Application::APP_ID, $name, $default);
}
public function getUserValue(User $user, string $name, string $default, $appId = null): string
{
return (string) $this->config->getUserValue($user->getUid(), $appId ?? Application::APP_ID, $name, $default);
}
public function getAppValueBool(string $name, string $default, $appId = null): bool
{
return (bool) $this->getAppValue($name, $default, $appId);
}
public function getAppValueArray(string $name, string $default, $appId = null): array
{
return (array) json_decode($this->getAppValue($name, $default, $appId), true);
}
public function getAppValueInt(string $name, string $default, $appId = null): int
{
return (int) $this->getAppValue($name, $default, $appId);
}
public function getUserValueBool(User $user, string $name, string $default, $appId = null): bool
{
return (bool) str_replace(
['yes', 'no'],
['1', '0'],
$this->getUserValue($user, $name, $default, $appId)
);
}
public function getUserValueArray(User $user, string $name, string $default, $appId = null): array
{
return (array) json_decode($this->getUserValue($user, $name, $default, $appId), true);
}
public function getUserValueInt(User $user, string $name, string $default, $appId = null): int
{
return (int) $this->getUserValue($user, $name, $default, $appId);
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace OCA\SideMenu\Service;
use OCP\IDBConnection;
/**
* class LangRepository.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class LangRepository
{
public function __construct(protected IDBConnection $db)
{
$this->db = $db;
}
public function getUsedLangs(): array
{
$qb = $this->db->getQueryBuilder();
$qb->select($qb->createFunction('DISTINCT configvalue'))
->where('configkey=:configkey and appid=:appid and configvalue<>:configvalue')
->setParameters([
'configkey' => 'lang',
'appid' => 'core',
'configvalue' => 'en',
])
->from('preferences')
;
// Nextcloud >=33+
if (method_exists($qb, 'executeQuery')) {
$stmt = $qb->executeQuery();
} else {
$stmt = $qb->execute();
}
$langs = ['en'];
foreach ($stmt->fetchAll() as $result) {
$langs[] = $result['configvalue'];
}
return $langs;
}
}

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,28 +18,41 @@
namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy;
use OCA\SideMenu\Service\LangRepository;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
use OCP\Settings\ISettings;
class Admin implements ISettings
{
public function __construct(
protected IL10N $l,
protected ConfigProxy $config,
protected AppRepository $appRepository,
protected CategoryRepository $categoryRepository,
protected ThemingDefaults $theming,
protected Color $color,
protected LangRepository $langRepository,
) {
/**
* @var IL10N
*/
private $l;
/**
* @var ILogger
*/
private $logger;
/**
* @var IConfig
*/
private $config;
/**
* @var AppRepository
*/
private $appRepository;
public function __construct(IL10N $l, ILogger $logger, IConfig $config, AppRepository $appRepository)
{
$this->l = $l;
$this->logger = $logger;
$this->config = $config;
$this->appRepository = $appRepository;
}
/**
@ -48,95 +60,35 @@ class Admin implements ISettings
*/
public function getForm()
{
$primaryColor = $this->theming->getColorPrimary();
$lightenPrimaryColor = $this->color->adjustBrightness($primaryColor, 0.2);
$darkenPrimaryColor = $this->color->adjustBrightness($primaryColor, -0.2);
$darkenPrimaryColor2 = $this->color->adjustBrightness($primaryColor, -0.3);
$textColor = $this->theming->getTextColorPrimary();
$backgroundColor = $this->config->getAppValue('background-color', $darkenPrimaryColor);
$backgroundColorTo = $this->config->getAppValue('background-color-to', $darkenPrimaryColor);
$darkModeBackgroundColor = $this->config->getAppValue('dark-mode-background-color', $darkenPrimaryColor);
$darkModeBackgroundColorTo = $this->config->getAppValue('dark-mode-background-color-to', $darkenPrimaryColor);
$backgroundColor = $this->config->getAppValue('side_menu', 'background-color', '#333333');
$backgroundColorTo = $this->config->getAppValue('side_menu', 'background-color-to', $backgroundColor);
$parameters = [
'defaults' => [
'background-color' => $darkenPrimaryColor,
'background-color-to' => $darkenPrimaryColor,
'current-app-background-color' => $darkenPrimaryColor2,
'text-color' => $textColor,
'loader-color' => $lightenPrimaryColor,
'dark-mode-background-color' => $darkenPrimaryColor,
'dark-mode-background-color-to' => $darkenPrimaryColor,
'dark-mode-current-app-background-color' => $darkenPrimaryColor2,
'dark-mode-text-color' => $textColor,
'dark-mode-loader-color' => $textColor,
],
'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo,
'background-color-opacity' => $this->config->getAppValueInt('background-color-opacity', '100'),
'current-app-background-color' => $this->config->getAppValue(
'current-app-background-color',
$darkenPrimaryColor2
),
'loader-color' => $this->config->getAppValue('loader-color', $lightenPrimaryColor),
'icon-invert-filter' => $this->config->getAppValueInt('icon-invert-filter', '0'),
'icon-opacity' => $this->config->getAppValueInt('icon-opacity', '100'),
'text-color' => $this->config->getAppValue('text-color', $textColor),
'dark-mode-background-color' => $darkModeBackgroundColor,
'dark-mode-background-color-to' => $darkModeBackgroundColorTo,
'dark-mode-background-color-opacity' => $this->config->getAppValueInt(
'dark-mode-background-color-opacity',
'100'
),
'dark-mode-current-app-background-color' => $this->config->getAppValue(
'dark-mode-current-app-background-color',
$darkenPrimaryColor2
),
'dark-mode-loader-color' => $this->config->getAppValue('dark-mode-loader-color', $textColor),
'dark-mode-icon-invert-filter' => $this->config->getAppValueInt('dark-mode-icon-invert-filter', '0'),
'dark-mode-icon-opacity' => $this->config->getAppValueInt('dark-mode-icon-opacity', '100'),
'dark-mode-text-color' => $this->config->getAppValue('dark-mode-text-color', $textColor),
'dark-mode-opener' => $this->config->getAppValue('dark-mode-opener', 'side-menu-opener'),
'opener' => $this->config->getAppValue('opener', 'side-menu-opener'),
'loader-enabled' => $this->config->getAppValue('loader-enabled', '1'),
'cache' => $this->config->getAppValue('cache', '0'),
'always-displayed' => $this->config->getAppValue('always-displayed', '0'),
'big-menu' => $this->config->getAppValue('big-menu', '0'),
'side-with-categories' => $this->config->getAppValue('side-with-categories', '0'),
'big-menu-hidden-apps' => $this->config->getAppValueArray('big-menu-hidden-apps', '[]'),
'display-logo' => $this->config->getAppValue('display-logo', '1'),
'add-logo-link' => $this->config->getAppValue('add-logo-link', '1'),
'use-avatar' => $this->config->getAppValue('use-avatar', '0'),
'opener-position' => $this->config->getAppValue('opener-position', 'before'),
'opener-hover' => $this->config->getAppValue('opener-hover', '0'),
'opener-only' => $this->config->getAppValue('opener-only', '0'),
'show-settings' => $this->config->getAppValue('show-settings', '0'),
'hide-when-no-apps' => $this->config->getAppValue('hide-when-no-apps', '0'),
'size-icon' => $this->config->getAppValue('size-icon', 'normal'),
'size-text' => $this->config->getAppValue('size-text', 'normal'),
'force' => $this->config->getAppValue('force', '0'),
'target-blank-apps' => $this->config->getAppValueArray('target-blank-apps', '[]'),
'top-menu-apps' => $this->config->getAppValueArray('top-menu-apps', '[]'),
'top-menu-mouse-over-hidden-label' => $this->config->getAppValue(
'top-menu-mouse-over-hidden-label',
'0'
),
'apps-order' => $this->config->getAppValueArray('apps-order', '[]'),
'ordered-apps' => $this->appRepository->getOrderedApps(),
'top-side-menu-apps' => $this->config->getAppValueArray('top-side-menu-apps', '[]'),
'default-enabled' => $this->config->getAppValue('default-enabled', '1'),
'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'),
'cache' => $this->config->getAppValue('side_menu', 'cache', '0'),
'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'),
'opener-position' => $this->config->getAppValue('side_menu', 'opener-position', 'before'),
'opener-hover' => $this->config->getAppValue('side_menu', 'opener-hover', '0'),
'opener-only' => $this->config->getAppValue('side_menu', 'opener-only', '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-text' => $this->config->getAppValue('side_menu', 'size-text', 'normal'),
'target-blank-apps' => (array) json_decode($this->config->getAppValue('side_menu', 'target-blank-apps', '[]'), true),
'top-menu-apps' => (array) json_decode($this->config->getAppValue('side_menu', 'top-menu-apps', '[]'), true),
'apps' => $this->appRepository->getVisibleApps(),
'apps-categories-custom' => $this->config->getAppValueArray('apps-categories-custom', '[]'),
'categories-order-type' => $this->config->getAppValue('categories-order-type', 'default'),
'categories-order' => $this->config->getAppValueArray('categories-order', '[]'),
'categories-custom' => $this->config->getAppValueArray('categories-custom', '[]'),
'categories' => $this->categoryRepository->getOrderedCategories(),
'langs' => $this->langRepository->getUsedLangs(),
];
return new TemplateResponse(Application::APP_ID, 'settings/admin-form', $parameters, '');
return new TemplateResponse('side_menu', 'settings/admin-form', $parameters, '');
}
/**
@ -144,7 +96,7 @@ class Admin implements ISettings
*/
public function getSection()
{
return Application::APP_ID;
return 'side_menu';
}
/**

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,36 +18,71 @@
namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
class AdminSection implements IIconSection
{
public function __construct(
protected IURLGenerator $url,
protected IL10N $l,
) {
/**
* @var IL10N
*/
private $l;
/**
* @var IURLGenerator
*/
private $url;
/**
* @param IURLGenerator $url
* @param IL10N $l
*/
public function __construct(IURLGenerator $url, IL10N $l)
{
$this->url = $url;
$this->l = $l;
}
/**
* returns the ID of the section. It is supposed to be a lower case string,
* e.g. 'ldap'
*
* @returns string
*/
public function getID()
{
return Application::APP_ID;
return 'side_menu';
}
/**
* returns the translated name as it should be displayed, e.g. 'LDAP / AD
* integration'. Use the L10N service to translate it.
*
* @return string
*/
public function getName()
{
return $this->l->t(Application::APP_NAME);
return $this->l->t('Side menu');
}
/**
* @return int whether the form should be rather on the top or bottom of
* the settings navigation. The sections are arranged in ascending order of
* the priority values. It is required to return a value between 0 and 99.
*
* E.g.: 70
*/
public function getPriority()
{
return 70;
}
/**
* {@inheritdoc}
*/
public function getIcon()
{
return $this->url->imagePath(Application::APP_ID, 'icon.svg');
return $this->url->imagePath('theming', 'app-dark.svg');
}
}

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,22 +18,48 @@
namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IL10N;
use OCP\IUserSession;
use OCP\ILogger;
use OCP\Settings\ISettings;
use OCP\IConfig;
use OCP\IUserSession;
use OCA\SideMenu\Service\AppRepository;
class Personal implements ISettings
{
public function __construct(
protected IL10N $l,
protected ConfigProxy $config,
protected IUserSession $userSession,
protected AppRepository $appRepository,
) {
/**
* @var IL10N
*/
private $l;
/**
* @var ILogger
*/
private $logger;
/**
* @var IConfig
*/
private $config;
/**
* @var IUserSession
*/
private $userSession;
/**
* @var AppRepository
*/
private $appRepository;
public function __construct(IL10N $l, ILogger $logger, IConfig $config, IUserSession $userSession, AppRepository $appRepository)
{
$this->l = $l;
$this->logger = $logger;
$this->config = $config;
$this->userSession = $userSession;
$this->appRepository = $appRepository;
}
/**
@ -45,22 +70,14 @@ class Personal implements ISettings
$user = $this->userSession->getUser();
$parameters = [
'force' => $this->config->getAppValueBool('force', '0'),
'enabled' => $this->config->getUserValue(
$user,
'enabled',
$this->config->getAppValue('default-enabled', '1')
),
'top-menu-apps' => $this->config->getUserValueArray($user, 'top-menu-apps', '[]'),
'top-side-menu-apps' => $this->config->getUserValueArray($user, 'top-side-menu-apps', '[]'),
'target-blank-mode' => $this->config->getUserValue($user, 'target-blank-mode', '1'),
'target-blank-apps' => $this->config->getUserValueArray($user, 'target-blank-apps', '[]'),
'apps-order' => $this->config->getUserValueArray($user, 'apps-order', '[]'),
'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),
'target-blank-mode' => $this->config->getUserValue($user->getUid(), 'side_menu', 'target-blank-mode', '1'),
'target-blank-apps' => (array) json_decode($this->config->getUserValue($user->getUid(), 'side_menu', 'target-blank-apps', '[]'), true),
'apps' => $this->appRepository->getVisibleApps(),
'ordered-apps' => $this->appRepository->getOrderedApps($user),
];
return new TemplateResponse(Application::APP_ID, 'settings/personal-form', $parameters, '');
return new TemplateResponse('side_menu', 'settings/personal-form', $parameters, '');
}
/**
@ -68,13 +85,13 @@ class Personal implements ISettings
*/
public function getSection()
{
return Application::APP_ID;
return 'side_menu';
}
/**
* @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
* priority values. It is required to return a value between 0 and 100.
* 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.
*
* E.g.: 70
*/

View file

@ -1,5 +1,4 @@
<?php
/**
* @license GNU AGPL version 3 or any later version
*
@ -19,50 +18,71 @@
namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
class PersonalSection implements IIconSection
{
public function __construct(
protected IURLGenerator $url,
protected IL10N $l,
protected ConfigProxy $configProxy,
) {
/**
* @var IL10N
*/
private $l;
/**
* @var IURLGenerator
*/
private $url;
/**
* @param IURLGenerator $url
* @param IL10N $l
*/
public function __construct(IURLGenerator $url, IL10N $l)
{
$this->url = $url;
$this->l = $l;
}
/**
* returns the ID of the section. It is supposed to be a lower case string,
* e.g. 'ldap'
*
* @returns string
*/
public function getID()
{
if ($this->configProxy->getAppValueBool('force', '0')) {
return '';
}
return Application::APP_ID;
return 'side_menu';
}
/**
* returns the translated name as it should be displayed, e.g. 'LDAP / AD
* integration'. Use the L10N service to translate it.
*
* @return string
*/
public function getName()
{
if ($this->configProxy->getAppValueBool('force', '0')) {
return '';
}
return $this->l->t(Application::APP_NAME);
return $this->l->t('Side menu');
}
/**
* @return int whether the form should be rather on the top or bottom of
* the settings navigation. The sections are arranged in ascending order of
* the priority values. It is required to return a value between 0 and 99.
*
* E.g.: 70
*/
public function getPriority()
{
if ($this->configProxy->getAppValueBool('force', '0')) {
return null;
}
return 70;
}
/**
* {@inheritdoc}
*/
public function getIcon()
{
return $this->url->imagePath(Application::APP_ID, 'icon.svg');
return $this->url->imagePath('theming', 'app-dark.svg');
}
}

8188
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,55 +1,60 @@
{
"license": "agpl",
"private": true,
"module": true,
"scripts": {
"build": "NODE_ENV=production ./node_modules/.bin/webpack-cli --progress --config webpack.config.js",
"dev": "NODE_ENV=development ./node_modules/.bin/webpack-cli --progress --config webpack.config.js",
"watch": "NODE_ENV=development ./node_modules/.bin/webpack-cli --progress --watch --config webpack.config.js",
"lint": "ESLINT_USE_FLAT_CONFIG=false ./node_modules/.bin/eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"format": "./node_modules/.bin/prettier src --write"
},
"dependencies": {
"@babel/core": ">=7.12.0 <8.0.0",
"@formatjs/intl-segmenter": "^12.0.8",
"@nextcloud/router": "^3.0.1",
"@nextcloud/vue": "^9.0.0-alpha.8",
"node-polyfill-webpack-plugin": "^4.1.0",
"pinia": "^3.0.1",
"postcss": "^7.0.0 || ^8.0.1",
"vue": "^3.5.13",
"vuedraggable": "^4.1.0"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": ">=16.0.0"
},
"devDependencies": {
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@nextcloud/axios": "^2.5.1",
"@nextcloud/browserslist-config": "^3.0.1",
"@nextcloud/event-bus": "^3.3.1",
"@nextcloud/initial-state": "^2.2.0",
"@nextcloud/l10n": "^3.2.0",
"babel-loader": "^9.1.3",
"css-loader": "^7.1.2",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-vue": "^9.32.0",
"file-loader": "^6.2.0",
"mini-css-extract-plugin": "^2.9.1",
"postcss-loader": "^8.1.1",
"prettier": "3.4.2",
"sass": "^1.78.0",
"sass-loader": "^16.0.1",
"source-map-loader": "^5.0.0",
"style-loader": "^4.0.0",
"vue-loader": "^17.4.2",
"vue-router": "^4.4.5",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-notifier": "^1.15.0"
}
"license": "agpl",
"private": true,
"scripts": {
"build": "NODE_ENV=production webpack --progress --hide-modules --config webpack.js",
"dev": "NODE_ENV=development webpack --progress --config webpack.js",
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.js",
"lint": "eslint --ext .js,.vue src",
"lint:fix": "eslint --ext .js,.vue src --fix",
"stylelint": "stylelint src",
"stylelint:fix": "stylelint src --fix"
},
"dependencies": {
"@nextcloud/axios": "^1.3.2",
"@nextcloud/vue": "^1.4.0",
"axios": "^0.19.2",
"trim": "0.0.1",
"vue": "^2.6.11"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": ">=10.0.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.9.0",
"@nextcloud/browserslist-config": "^1.0.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"css-loader": "^3.4.2",
"eslint": "^5.16.0",
"eslint-config-nextcloud": "0.1.1",
"eslint-config-standard": "^12.0.0",
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-nextcloud": "^0.3.0",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^5.2.3",
"file-loader": "^6.0.0",
"node-sass": "^4.13.1",
"sass-loader": "^8.0.2",
"stylelint": "^8.4.0",
"stylelint-config-recommended-scss": "^3.3.0",
"stylelint-scss": "^3.16.0",
"stylelint-webpack-plugin": "^0.10.5",
"url-loader": "^4.0.0",
"vue-loader": "^15.9.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11",
"webpack-merge": "^4.2.2",
"webpack-node-externals": "^1.7.2"
}
}

View file

@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Before After
Before After

50
src/SideMenu.js Normal file
View file

@ -0,0 +1,50 @@
/**
* @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/>.
*/
import Vue from 'vue'
import SideMenu from './SideMenu.vue'
import SideMenuBig from './SideMenuBig.vue'
// Vue.prototype.t = t
Vue.prototype.OC = OC
// Vue.prototype.OC = OCP
const mountSideMenuComponent = () => {
const sideMenuContainer = document.querySelector('#side-menu')
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')
$('body').trigger('side-menu.ready')
} else {
window.setTimeout(mountSideMenuComponent, 50)
}
}
mountSideMenuComponent()

135
src/SideMenu.vue Normal file
View file

@ -0,0 +1,135 @@
<!--
@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">
<div class="side-menu-header">
<button class="side-menu-opener"></button>
<div v-if="logo" class="side-menu-logo">
<img v-bind:src="logo">
</div>
</div>
<ul class="side-menu-apps-list">
<li v-for="app in apps" v-bind:class="{'side-menu-app': true, 'active': app.active}">
<a v-bind:href="app.href" :target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined" v-bind:title="app.name">
<span class="side-menu-app-icon" v-html="app.icon"></span>
<span class="side-menu-app-text" v-html="app.name"></span>
</a>
</li>
</ul>
</div>
</template>
<script>
import trim from 'trim'
export default {
name: 'SideMenu',
data() {
return {
apps: [],
logo: null,
forceLightIcon: false,
targetBlank: false,
}
},
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')
var parent = element.parentNode
if (!parent) {
continue
}
var dataId = parent.getAttribute('data-id')
dataId = dataId !== null ? dataId : ''
if (!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() {
jQuery('body').trigger('side-menu.apps', [apps])
}, 1000)
})(this.apps)
},
retrieveLogo() {
const ncLogo = document.querySelector('#nextcloud .logo')
if (ncLogo) {
const url = window.getComputedStyle(ncLogo, null)
.getPropertyValue('background-image')
.replace('url("', '')
.replace('")', '')
if (url && url !== 'none') {
this.logo = url
}
}
},
},
mounted() {
this.retrieveApps()
this.retrieveLogo()
this.forceLightIcon = document.querySelector('#side-menu-container').getAttribute('data-forcelighticon') == 1;
this.targetBlankApps = document.querySelector('#side-menu-container').getAttribute('data-targetblankapps').split(',');
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)
}
}
}
</script>

89
src/SideMenuBig.vue Normal file
View file

@ -0,0 +1,89 @@
<!--
@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" :target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined" 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,
targetBlank: false,
}
},
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.targetBlankApps = document.querySelector('#side-menu-container').getAttribute('data-targetblankapps').split(',');
this.retrieveApps()
this.retrieveActiveApp()
}
}
</script>

View file

@ -8,27 +8,125 @@
*
* 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
* 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/>.
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import './scss/admin.scss'
let elements = []
import '@formatjs/intl-segmenter/polyfill.js'
const selector = '#side-menu-message';
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { waitContainer } from './lib/dom.js'
const userConfig = (name, value, callbacks) => {
const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet')
import AdminSettings from './pages/AdminSettings'
$.post(url, {name: name, value: value}, callbacks.success)
.fail(callbacks.error)
}
waitContainer('#side-menu-admin-settings').then((selector) => {
const pinia = createPinia()
const app = createApp(AdminSettings)
app.use(pinia)
app.mixin({ methods: { t, n } })
app.mount(selector)
})
const appConfig = (name, value, callbacks) => {
OCP.AppConfig.setValue('side_menu', name, value, callbacks)
}
const saveSettings = (key) => {
const element = elements.get(key)
let value
let name
if (jQuery(element).is('[data-checkbox]')) {
name = jQuery(element).attr('data-name')
const inputs = jQuery('input[name="' + name + '[]"]:checked')
value = []
inputs.each((i, v) => {
value.push(v.value)
})
value = JSON.stringify(value)
} else {
name = jQuery(element).attr('name')
value = jQuery(element).val()
}
const size = elements.length
if (name === 'cache') {
++value
}
const callbacks = {
success: () => {
OC.msg.finishedSuccess(
selector,
t('side_menu', (key + 1) + '/' + size)
)
if (key < size - 1) {
saveSettings(++key)
} else {
OC.msg.finishedSuccess(selector, t('side_menu', 'Saved'))
}
},
error: () => {
OC.msg.finishedError(selector, t('side_menu', 'Error while saving "' + element + '"'))
}
}
if (jQuery(element).is('[data-personal]')) {
userConfig(name, value, callbacks)
} else {
appConfig(name, value, callbacks)
}
}
const elementToggler = (element) => {
jQuery(element).toggle()
}
jQuery(document).ready(() => {
elements = jQuery('.side-menu-setting')
jQuery('#side-menu-save').on('click', (event) => {
event.preventDefault()
OC.msg.startSaving(selector)
saveSettings(0)
});
jQuery('.side-menu-display').on('click', (event) => {
var target = jQuery(event.target)
jQuery('.side-menu-display').removeClass('is-active')
target.addClass('is-active')
jQuery('#side-menu-always-displayed').val(target.attr('data-alwaysdiplayed'))
jQuery('#side-menu-big-menu').val(target.attr('data-bigmenu'))
})
jQuery('.side-menu-setting-live').on('change', (event) => {
var target = jQuery(event.target)
var name = target.attr('name')
var value = target.val()
if (name === 'opener') {
var 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;
}
document.documentElement.style.setProperty('--side-menu-' + name, value)
})
jQuery('.side-menu-toggler').on('click', (event) => {
var target = jQuery(event.target)
var element = target.attr('data-target')
elementToggler(element)
})
});

View file

@ -1,29 +0,0 @@
<!--
@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 class="cm-search">
<input
v-model="model"
type="text"
:placeholder="t('side_menu', 'Search')"
/>
</div>
</template>
<script setup>
const model = defineModel({ type: String })
</script>

View file

@ -1,29 +0,0 @@
<!--
@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>
<button
class="cm-opener cm-closer"
:arial-label="t('side_menu', 'Close the menu')"
@click="$emit('click')"
>
<span>{{ t('side_menu', 'Close the menu') }}</span>
</button>
</template>
<script setup>
defineEmits(['click'])
</script>

View file

@ -1,52 +0,0 @@
<!--
@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 :class="classes">
<a
v-if="link !== null"
:href="link"
>
<img
:src="image"
alt="Logo"
/>
</a>
<img
v-else
:src="image"
alt="Logo"
/>
</div>
</template>
<script setup>
const { image, link, classes } = defineProps({
image: {
type: String,
required: true,
},
link: {
type: String,
required: false,
default: null,
},
classes: {
type: Object,
required: true,
},
})
</script>

View file

@ -1,29 +0,0 @@
<!--
@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>
<button
class="cm-opener"
:arial-label="label"
@click="$emit('click')"
>
<span>{{ t('side_menu', 'Toggle the menu') }}</span>
</button>
</template>
<script setup>
defineEmits(['click'])
</script>

View file

@ -1,49 +0,0 @@
<!--
@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 class="cm-loader">
<div
class="cm-loader-bar"
:style="createStyle(width)"
></div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
const width = ref(0)
const createStyle = (size) => {
return {
width: `${size}%`,
}
}
let interval = null
onMounted(() => {
window.addEventListener('beforeunload', () => {
interval = setInterval(() => {
width.value = Math.min(width.value + 0.2, 100)
if (width.value === 100) {
clearInterval(interval)
window.setTimeout(() => (width.value = 0), 2000)
}
}, 25)
})
})
</script>

View file

@ -1,49 +0,0 @@
<!--
@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 class="cm-setting">
<a :href="href">
<!--
{{ label }}
-->
<span class="avatardiv avatardiv-shown">
<img
:src="avatar"
:alt="label"
/>
</span>
</a>
</div>
</template>
<script setup>
const { label, href, avatar } = defineProps({
label: {
type: String,
required: true,
},
href: {
type: String,
required: true,
},
avatar: {
type: String,
required: true,
},
})
</script>

View file

@ -1,58 +0,0 @@
<!--
@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>
<li :class="classes">
<a
:href="href"
:target="target"
:title="label"
>
<img
class="cm-app-icon"
:src="icon"
:alt="label"
/>
<span class="cm-app-text">{{ label }}</span>
</a>
</li>
</template>
<script setup>
const { label, icon, href, classes, target } = defineProps({
label: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
href: {
type: String,
required: true,
},
classes: {
type: Object,
required: true,
},
target: {
type: String,
required: false,
default: null,
},
})
</script>

View file

@ -1,58 +0,0 @@
<!--
@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>
<li :class="classes">
<a
:href="href"
:target="target"
:title="label"
>
<img
class="cm-app-icon"
:src="icon"
:alt="label"
/>
<span class="cm-app-text">{{ label }}</span>
</a>
</li>
</template>
<script setup>
const { label, icon, href, classes, target } = defineProps({
label: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
href: {
type: String,
required: true,
},
classes: {
type: Object,
required: true,
},
target: {
type: String,
required: false,
default: null,
},
})
</script>

View file

@ -1,103 +0,0 @@
<template>
<div class="cm-settings-btn cm-settings-btn--save">
<NcButton
variant="success"
@click="save"
>
<template v-if="!loading">
{{ t('side_menu', 'Save') }}
</template>
<NcLoadingIcon v-else />
</NcButton>
<div
v-if="error"
id="error"
>
{{ error }}
</div>
</div>
</template>
<script setup>
import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
import { ref } from 'vue'
import { waitPasswordConfirmation } from '../../lib/setting.js'
const loading = ref(false)
const error = ref(null)
const { config } = defineProps({
config: {
type: Object,
required: true,
},
})
const filterConfig = (value) => {
const result = {}
for (let key in value) {
if (['cache-categories', 'cache', 'langs', 'enabled'].includes(key) === false) {
result[key] = value[key]
}
}
return result
}
const save = async () => {
const data = filterConfig(config)
const size = Object.keys(data).length
let counter = 0
loading.value = true
error.value = null
const update = () => {
++counter
if (counter === size) {
loading.value = false
if (!error.value) {
location.reload()
}
}
}
waitPasswordConfirmation()
.then(() => {
for (let key in data) {
let value = data[key]
if (Array.isArray(value) || typeof value === 'object') {
value = JSON.stringify(value)
} else if (typeof value === 'boolean') {
value = value ? '1' : '0'
}
OCP.AppConfig.setValue('side_menu', key, value.toString(), {
success() {
update()
},
error() {
error.value = `Error while saving ${key}`
update()
},
})
}
})
.catch(() => {
counter = 0
loading.value = false
error.value = null
})
}
</script>
<style scoped>
#error {
padding-top: 10px;
color: red;
}
</style>

View file

@ -1,18 +0,0 @@
<template>
<a
:href="href"
rel="noopener"
target="_blank"
>
<slot></slot>
</a>
</template>
<script setup>
defineProps({
href: {
type: String,
required: true,
},
})
</script>

View file

@ -1,35 +0,0 @@
<!--
@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>
<h2>{{ t('side_menu', label) }}</h2>
</template>
<script setup>
const { label } = defineProps({
label: {
type: String,
required: true,
},
})
</script>
<style scoped>
h2 {
font-size: 1.3em;
margin: 0 0 12px 0;
}
</style>

View file

@ -1,40 +0,0 @@
<!--
@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
class="cm-settings-item"
:class="{ 'cm-settings-item--disabled': disabled }"
>
<slot></slot>
</div>
</template>
<script setup>
const { disabled } = defineProps({
disabled: {
type: Boolean,
required: false,
default: false,
},
})
</script>
<style scoped>
.disabled {
display: block;
}
</style>

View file

@ -1,71 +0,0 @@
<!--
@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
class="cm-settings-item-label"
:class="{
'cm-settings-item-label--short': short,
'cm-settings-item-label--top': top,
'cm-settings-item-label--middle': middle,
}"
>
{{ t('side_menu', label) }}
<template v-if="help">
<br />
<em>{{ t('side_menu', help) }}</em>
</template>
<template v-if="help2">
<br />
<em>{{ t('side_menu', help2) }}</em>
</template>
</div>
</template>
<script setup>
const { short, label } = defineProps({
short: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: true,
},
middle: {
type: Boolean,
required: false,
default: false,
},
top: {
type: Boolean,
required: false,
default: true,
},
help: {
type: [String, null],
required: false,
default: null,
},
help2: {
type: [String, null],
required: false,
default: null,
},
})
</script>

View file

@ -1,34 +0,0 @@
<!--
@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
class="side-menu-setting-form"
:class="{ 'side-menu-setting-form-long': long }"
>
<slot></slot>
</div>
</template>
<script setup>
const { long } = defineProps({
long: {
type: Boolean,
required: false,
default: false,
},
})
</script>

View file

@ -1,34 +0,0 @@
<!--
@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
class="cm-settings-section"
:class="{ 'cm-settings-section--hidden': hidden }"
>
<slot></slot>
</div>
</template>
<script setup>
defineProps({
hidden: {
type: Boolean,
required: false,
default: false,
},
})
</script>

View file

@ -1,99 +0,0 @@
<template>
<div class="cm-settings-btn cm-settings-btn--save">
<NcButton
variant="success"
@click="save"
>
<template v-if="!loading">
{{ t('side_menu', 'Save') }}
</template>
<NcLoadingIcon v-else />
</NcButton>
<div
v-if="error"
id="error"
></div>
</div>
</template>
<script setup>
import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
import { ref } from 'vue'
const loading = ref(false)
const error = ref(null)
const { config } = defineProps({
config: {
type: Object,
required: true,
},
})
const filterConfig = (value) => {
const result = {}
for (let key in value) {
result[key] = value[key]
}
return result
}
const save = async () => {
const data = filterConfig(config)
const size = Object.keys(data).length
const url = OC.generateUrl('/apps/side_menu/user/valueSet')
let counter = 0
loading.value = true
error.value = null
const update = () => {
++counter
if (counter === size) {
loading.value = false
if (!error.value) {
location.reload()
}
}
}
for (let key in data) {
let value = data[key]
let formData = []
if (Array.isArray(value) || typeof value === 'object') {
value = JSON.stringify(value)
} else if (typeof value === 'boolean') {
value = value ? '1' : '0'
}
formData.push('name=' + encodeURIComponent(key))
formData.push('value=' + encodeURIComponent(value.toString()))
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.join('&'),
})
.then(update)
.catch(() => {
error.value = `Error while saving ${key}`
update()
})
}
}
</script>
<style scoped>
#error {
padding-top: 10px;
color: red;
}
</style>

View file

@ -1,292 +0,0 @@
<!--
@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 class="cm-settings-form-appcategory">
<NcButton
aria-label="t('side_menu', 'Customize')"
variant="primary"
@click="openModal"
>
{{ t('side_menu', 'Customize') }}
</NcButton>
<NcModal
v-if="modal"
class="cm-settings-form-appcategory-modal"
@close="closeModal"
>
<div class="modal__content">
<div class="menu">
<NcButton
aria-label="t('side_menu', 'Categories')"
:variant="section === 'cats' ? 'primary' : 'secondary'"
@click="setSection('cats')"
>
{{ t('side_menu', 'Categories') }}
</NcButton>
<NcButton
aria-label="t('side_menu', 'Applications')"
:variant="section === 'apps' ? 'primary' : 'secondary'"
@click="setSection('apps')"
>
{{ t('side_menu', 'Applications') }}
</NcButton>
</div>
<div v-if="section === 'cats'">
<table
v-if="!newCustomCategory && editCustomCategoryKey === null"
width="100%"
>
<tbody>
<tr
v-for="(item, key) in categoriesCustom"
:key="key"
>
<td>{{ item[langs[0]] }}</td>
<td width="50px">
<NcActions>
<NcActionButton
icon="icon-edit"
@click="editCustomCategory(key)"
></NcActionButton>
<NcActionButton
icon="icon-delete"
@click="removeCustomCategory(key)"
></NcActionButton>
</NcActions>
</td>
</tr>
</tbody>
</table>
<div
v-else
class="form"
>
<template v-if="newCustomCategory">
<NcTextField
v-for="lang in langs"
:key="lang"
v-model="newCustomCategory[lang]"
:label="lang"
/>
</template>
<template v-if="editCustomCategoryKey !== null">
<NcTextField
v-for="lang in langs"
:key="lang"
v-model="categoriesCustom[editCustomCategoryKey][lang]"
:label="lang"
/>
</template>
</div>
</div>
<div v-if="section === 'apps'">
<table width="100%">
<tbody>
<tr
v-for="item in apps"
:key="item.id"
>
<td>
<img
:src="item.icon"
:alt="item.name"
/>
{{ item.name }}
</td>
<td width="50%">
<FormSelect
v-model="appsCategoriesCustom[item.id]"
:options="getOptions(categoriesCustom)"
:required="false"
/>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal__footer">
<template v-if="section === 'cats'">
<template v-if="newCustomCategory">
<NcActions>
<NcActionButton
icon="icon-close"
@click="cancelCustomCategory"
></NcActionButton>
</NcActions>
<NcActions>
<NcActionButton
icon="icon-checkmark"
@click="saveCustomCategory"
></NcActionButton>
</NcActions>
</template>
<template v-if="editCustomCategoryKey !== null">
<NcActions>
<NcActionButton
icon="icon-close"
@click="cancelCustomCategory"
></NcActionButton>
</NcActions>
</template>
<template v-else>
<NcActions>
<NcActionButton
v-if="!newCustomCategory && editCustomCategoryKey === null"
icon="icon-add"
@click="addCustomCategory"
></NcActionButton>
<NcActionButton
v-if="editCustomCategoryKey !== null"
icon="icon-checkmark"
@click="saveCustomCategory"
></NcActionButton>
</NcActions>
</template>
</template>
<NcButton
variant="primary"
class="btn-close"
@click="closeModal"
>
{{ t('side_menu', 'Close') }}
</NcButton>
</div>
</div>
</NcModal>
</div>
</template>
<script setup>
import { NcButton, NcModal, NcActions, NcActionButton, NcTextField } from '@nextcloud/vue'
import { useNavStore } from '../../../store/nav.js'
import { ref, onMounted, watch } from 'vue'
import FormSelect from './FormSelect'
const emit = defineEmits(['update:categoriesCustom', 'update:appsCategoriesCustom'])
const { categoriesCustom, appsCategoriesCustom, langs } = defineProps({
categoriesCustom: {
type: Array,
required: true,
},
appsCategoriesCustom: {
type: [Object, Array],
required: true,
},
langs: {
type: Array,
required: true,
},
})
const navStore = useNavStore()
const modal = ref(false)
const apps = ref([])
const categories = ref([])
const section = ref('apps')
const newCustomCategory = ref(null)
const editCustomCategoryKey = ref(null)
const openModal = () => {
modal.value = true
}
const closeModal = () => {
modal.value = false
}
const setSection = (value) => {
section.value = value
}
const addCustomCategory = () => {
let data = {
id: 'cat' + Math.random().toString().replace('0.', ''),
}
langs.forEach((lang) => {
data[lang] = ''
})
newCustomCategory.value = data
}
const cancelCustomCategory = () => {
newCustomCategory.value = null
editCustomCategoryKey.value = null
}
const saveCustomCategory = () => {
const data = categoriesCustom
if (editCustomCategoryKey.value === null) {
data.push({ ...newCustomCategory.value })
}
emit('update:categoriesCustom', data)
newCustomCategory.value = null
editCustomCategoryKey.value = null
}
const removeCustomCategory = (key) => {
const data = categoriesCustom
delete data[key]
emit('update:categoriesCustom', Object.values(data))
}
const editCustomCategory = (key) => {
editCustomCategoryKey.value = key
}
const getOptions = (custom) => {
const data = []
custom.forEach((item) => {
data.push({ id: item.id, label: item[langs[0]] })
})
categories.value.forEach((item) => {
data.push({ id: item.categoryId, label: item.name !== '' ? item.name : t('side_menu', 'Other') })
})
data.sort((a, b) => (a.label < b.label ? -1 : 1))
return data
}
onMounted(async () => {
apps.value = await navStore.getApps()
categories.value = await navStore.getCategories()
let value = {}
apps.value.forEach((app) => {
if (!appsCategoriesCustom[app.id]) {
value[app.id] = null
} else {
value[app.id] = appsCategoriesCustom[app.id]
}
})
emit('update:appsCategoriesCustom', value)
})
</script>

View file

@ -1,82 +0,0 @@
<!--
@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 class="cm-settings-form-apppicker">
<NcButton
aria-label="t('side_menu', 'Select apps')"
variant="primary"
@click="openModal"
>
{{ t('side_menu', 'Select apps') }} ({{ model.length }})
</NcButton>
<NcModal
v-if="modal"
size="small"
class="cm-settings-form-apppicker-modal"
@close="closeModal"
>
<div class="modal__content">
<NcCheckboxRadioSwitch
v-for="(item, key) in apps"
:key="key"
v-model="model"
name="value"
:value="item.id"
>
<img
:src="item.icon"
:alt="item.name"
/>
{{ item.name }}
</NcCheckboxRadioSwitch>
<div class="modal__footer">
<NcButton
variant="primary"
@click="closeModal"
>
{{ t('side_menu', 'Close') }}
</NcButton>
</div>
</div>
</NcModal>
</div>
</template>
<script setup>
import { NcButton, NcModal, NcCheckboxRadioSwitch } from '@nextcloud/vue'
import { useNavStore } from '../../../store/nav.js'
import { ref, onMounted } from 'vue'
const model = defineModel({ type: Array })
const navStore = useNavStore()
const modal = ref(false)
const apps = ref([])
const openModal = () => {
modal.value = true
}
const closeModal = () => {
modal.value = false
}
onMounted(async () => {
apps.value = await navStore.getCoreApps()
})
</script>

View file

@ -1,116 +0,0 @@
<!--
@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 class="cm-settings-form-appsort">
<NcButton
aria-label="t('side_menu', 'Sort')"
variant="primary"
@click="openModal"
>
{{ t('side_menu', 'Sort') }}
</NcButton>
<NcModal
v-if="modal"
size="small"
class="cm-settings-form-appsort-modal"
@close="closeModal"
>
<div class="modal__content">
<draggable
v-model="apps"
item-key="id"
@end="update"
>
<template #item="{ element }">
<div class="cm-settings-form-draggable">
<span class="cm-settings-form-arrow"></span>
{{ element.name }}
</div>
</template>
</draggable>
<div class="modal__footer">
<NcButton
variant="primary"
@click="closeModal"
>
{{ t('side_menu', 'Close') }}
</NcButton>
</div>
</div>
</NcModal>
</div>
</template>
<script setup>
import { NcButton, NcModal } from '@nextcloud/vue'
import { useNavStore } from '../../../store/nav.js'
import { ref, onMounted } from 'vue'
import draggable from 'vuedraggable'
const model = defineModel({ type: Array })
const emit = defineEmits(['update:modelValue'])
const navStore = useNavStore()
const modal = ref(false)
const apps = ref([])
const openModal = () => {
modal.value = true
}
const closeModal = () => {
modal.value = false
}
const setApps = (items) => {
apps.value = []
model.value.forEach((id) => {
items.forEach((app) => {
if (app.id === id) {
apps.value.push(app)
}
})
})
items.forEach((app) => {
if (!apps.value.find((element) => element.id === app.id)) {
apps.value.push(app)
}
})
}
const update = () => {
const value = []
apps.value.forEach((app) => {
value.push(app.id)
})
emit('update:modelValue', value)
}
onMounted(async () => {
const items = await navStore.getCoreApps()
window.setTimeout(() => {
setApps(items)
}, 500)
})
</script>

View file

@ -1,119 +0,0 @@
<!--
@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 class="cm-settings-form-catsort">
<NcButton
aria-label="t('side_menu', 'Sort')"
variant="primary"
@click="openModal"
>
{{ t('side_menu', 'Sort') }}
</NcButton>
<NcModal
v-if="modal"
size="small"
class="cm-settings-form-catsort-modal"
@close="closeModal"
>
<div class="modal__content">
<draggable
v-model="apps"
item-key="categoryId"
@end="update"
>
<template #item="{ element }">
<div
v-if="element.name !== ''"
class="cm-settings-form-draggable"
>
<span class="cm-settings-form-arrow"></span>
{{ element.name }}
</div>
</template>
</draggable>
<div class="modal__footer">
<NcButton
variant="primary"
@click="closeModal"
>
{{ t('side_menu', 'Close') }}
</NcButton>
</div>
</div>
</NcModal>
</div>
</template>
<script setup>
import { NcButton, NcModal } from '@nextcloud/vue'
import { useNavStore } from '../../../store/nav.js'
import { ref, onMounted } from 'vue'
import draggable from 'vuedraggable'
const model = defineModel({ type: Array })
const emit = defineEmits(['update:modelValue'])
const navStore = useNavStore()
const modal = ref(false)
const apps = ref([])
const openModal = () => {
modal.value = true
}
const closeModal = () => {
modal.value = false
}
const setApps = (items) => {
apps.value = []
model.value.forEach((id) => {
items.forEach((app) => {
if (app.categoryId === id) {
apps.value.push(app)
}
})
})
items.forEach((app) => {
if (!apps.value.find((element) => element.categoryId === app.categoryId)) {
apps.value.push(app)
}
})
}
const update = () => {
const value = []
apps.value.forEach((app) => {
value.push(app.categoryId)
})
emit('update:modelValue', value)
}
onMounted(async () => {
const items = await navStore.getCategories()
window.setTimeout(() => {
setApps(items)
}, 500)
})
</script>

View file

@ -1,34 +0,0 @@
<!--
@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>
<NcColorPicker
v-model="model"
:advancedFields="true"
class="cm-settings-form-colorpicker"
>
<div
:style="{ 'background-color': model }"
class="cm-settings-form-colorpicker-value"
/>
</NcColorPicker>
</template>
<script setup>
import { NcColorPicker } from '@nextcloud/vue'
const model = defineModel({ type: String })
</script>

View file

@ -1,85 +0,0 @@
<template>
<div class="cm-settings-form-displaypicker">
<div class="cm-settings-button-inline">
<NcButton
:variant="is(false, false, false) ? 'primary' : 'seconday'"
@click="update(false, false, false)"
>
{{ t('side_menu', 'Default') }}
</NcButton>
<NcButton
:variant="is(true, false, false) ? 'primary' : 'seconday'"
@click="update(true, false, false)"
>
{{ t('side_menu', 'Always displayed') }}
</NcButton>
<NcButton
:variant="is(false, true, false) ? 'primary' : 'seconday'"
@click="update(false, true, false)"
>
{{ t('side_menu', 'Big menu') }}
</NcButton>
<NcButton
:variant="is(false, false, true) ? 'primary' : 'seconday'"
@click="update(false, false, true)"
>
{{ t('side_menu', 'With categories') }}
</NcButton>
</div>
<p>
<img
v-if="is(false, false, false)"
:src="DefaultImg"
/>
<img
v-if="is(true, false, false)"
:src="AlwaysDisplayedImg"
/>
<img
v-if="is(false, true, false)"
class="side-menu-display"
:src="TopWideImg"
/>
<img
v-if="is(false, false, true)"
:src="SideMenuWithCategoriesImg"
/>
</p>
</div>
</template>
<script setup>
import { NcButton } from '@nextcloud/vue'
import AlwaysDisplayedImg from '../../../../img/admin/layout-always-displayed.svg'
import TopWideImg from '../../../../img/admin/layout-big-menu.svg'
import SideMenuWithCategoriesImg from '../../../../img/admin/layout-side-menu-with-categories.svg'
import DefaultImg from '../../../../img/admin/layout-default.svg'
const emit = defineEmits(['update:alwaysDisplayed', 'update:topWideMenu', 'update:sideMenuWithCategories'])
const { alwaysDisplayed, topWideMenu, sideMenuWithCategories } = defineProps({
alwaysDisplayed: {
type: Boolean,
required: true,
},
topWideMenu: {
type: Boolean,
required: true,
},
sideMenuWithCategories: {
type: Boolean,
required: true,
},
})
const update = (isAlwayDisplayed, isTopWideMenu, isSideMenuWithCategories) => {
emit('update:alwaysDisplayed', isAlwayDisplayed)
emit('update:topWideMenu', isTopWideMenu)
emit('update:sideMenuWithCategories', isSideMenuWithCategories)
}
const is = (isAlwayDisplayed, isTopWideMenu, isSideMenuWithCategories) => {
return isAlwayDisplayed === alwaysDisplayed && isTopWideMenu === topWideMenu && isSideMenuWithCategories === sideMenuWithCategories
}
</script>

View file

@ -1,37 +0,0 @@
<!--
@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>
<FormSelect
v-model="model"
class="cm-settings-form-opener"
:options="options"
/>
</template>
<script setup>
import FormSelect from './FormSelect'
const model = defineModel({ type: String })
const options = [
{ id: 'side-menu-opener', label: 'Default' },
{ id: 'side-menu-opener-dark', label: 'Default (dark)' },
{ id: 'side-menu-opener-hamburger', label: 'Hamburger' },
{ id: 'side-menu-opener-hamburger-dark', label: 'Hamburger (dark)' },
{ id: 'side-menu-opener-hamburger-2', label: 'Hamburger 2' },
{ id: 'side-menu-opener-hamburger-2-dark', label: 'Hamburger 2 (dark)' },
]
</script>

View file

@ -1,65 +0,0 @@
<!--
@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 class="cm-settings-form-range">
<em
v-if="prepend"
class="cm-settings-form-range-prepend"
>{{ t('side_menu', prepend) }}</em
>
<input
v-model="model"
type="range"
:min="min"
:max="max"
/>
<em
v-if="append"
class="cm-settings-form-range-append"
>{{ t('side_menu', append) }}</em
>
</div>
</template>
<script setup>
const model = defineModel({ type: Number })
defineProps({
prepend: {
type: [String, null],
required: false,
default: null,
},
append: {
type: [String, null],
required: false,
default: null,
},
min: {
type: Number,
required: false,
default: 0,
},
max: {
type: Number,
required: false,
default: 100,
},
})
</script>

View file

@ -1,78 +0,0 @@
<!--
@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 class="cm-settings-form-select">
<template v-if="!expanded">
<select
v-if="!expanded"
v-model="model"
:multiple="multiple"
>
<option
v-if="!required"
:value="null"
></option>
<option
v-for="option in options"
:key="option.id"
:value="option.id"
>
{{ t('side_menu', option.label) }}
</option>
</select>
</template>
<template v-else>
<NcCheckboxRadioSwitch
v-for="option in options"
:key="option.id"
v-model="model"
:value="option.id"
:type="multiple ? 'checkbox' : 'radio'"
name="value"
>
{{ t('side_menu', option.label) }}
</NcCheckboxRadioSwitch>
</template>
</div>
</template>
<script setup>
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
const model = defineModel({ type: [Number, String, Array, null] })
const { options, expanded } = defineProps({
options: {
type: Array,
required: true,
},
required: {
type: Boolean,
required: false,
default: true,
},
expanded: {
type: Boolean,
required: false,
default: false,
},
multiple: {
type: Boolean,
required: false,
default: false,
},
})
</script>

View file

@ -1,35 +0,0 @@
<!--
@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>
<FormSelect
v-model="model"
class="cm-settings-form-size"
:options="options"
/>
</template>
<script setup>
import FormSelect from './FormSelect'
const model = defineModel({ type: String })
const options = [
{ id: 'hidden', label: 'Hidden' },
{ id: 'small', label: 'Small' },
{ id: 'normal', label: 'Normal' },
{ id: 'big', label: 'Big' },
]
</script>

View file

@ -1,29 +0,0 @@
<!--
@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>
<NcCheckboxRadioSwitch
v-model="model"
class="cm-settings-form-yesno"
type="switch"
/>
</template>
<script setup>
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
const model = defineModel({ type: Boolean })
</script>

View file

@ -1,13 +0,0 @@
import FormRange from './FormRange'
import FormColorPicker from './FormColorPicker'
import FormOpener from './FormOpener'
import FormSelect from './FormSelect'
import FormYesNo from './FormYesNo'
import FormSize from './FormSize'
import FormAppPicker from './FormAppPicker'
import FormAppSort from './FormAppSort'
import FormCatSort from './FormCatSort'
import FormDisplayPicker from './FormDisplayPicker'
import FormAppCategory from './FormAppCategory'
export { FormRange, FormColorPicker, FormOpener, FormSelect, FormYesNo, FormSize, FormAppPicker, FormAppSort, FormCatSort, FormDisplayPicker, FormAppCategory }

View file

@ -1,10 +0,0 @@
import SettingsSection from './SettingsSection'
import SettingItem from './SettingItem'
import SettingLabel from './SettingLabel'
import SettingValue from './SettingValue'
import SectionTitle from './SectionTitle'
import ExternalLink from './ExternalLink'
import AdminSaveButton from './AdminSaveButton'
import UserSaveButton from './UserSaveButton'
export { SettingsSection, SettingItem, SettingLabel, SettingValue, SectionTitle, ExternalLink, AdminSaveButton, UserSaveButton }

View file

@ -1,111 +0,0 @@
'Custom menu': 'Uživatelsky určená nabídka'
'Enable the custom menu': 'Zapnout uživatelsky určenou nabídku'
'No': 'Ne'
'Yes': 'Ano'
'Menu': 'Nabídka'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Pro otevření/skrytí postranní nabídky použijte zkratku Ctrl+o („O“ jako otevřít). Pro pohyb po použijte klávesu tab key.'
'Top menu': 'Horní nabídka'
'Apps that not must be moved in the side menu': 'Aplikace, které nepřesouvat do postranní nabídky'
'If there is no selection then the global configuration is applied.': 'Pokud neexistuje žádný výběr, je uplatněno globální nastavení.'
'Experimental': 'Experimentální'
'Save': 'Uložit'
'You like this app and you want to support me?': 'Líbí se vám tato aplikace a chcete podpořit její vývoj?'
'Buy me a coffee ☕': 'Kupte mi kafe ☕'
'Hidden': 'Skryté'
'Small': 'Malé'
'Normal': 'Normální'
'Big': 'Velké'
'Colors': 'Barvy'
'Background color': 'Barva pozadí'
'Background color of current app': 'Barva pozadí stávající aplikace'
'Text color': 'Barva textu'
'Loader': 'Nástroj pro načítání'
'Icon': 'Ikona'
'Same color': 'Stejná barva'
'Opposite color': 'Doplňková barva'
'Transparent': 'Průhledné'
'Opaque': 'Neprůhledné'
'Opener': 'Tlačítko pro otevření'
'Default': 'Výchozí'
'Default (dark)': 'Výchozí (tmavé)'
'Hamburger': 'Hamburger'
'Hamburger (dark)': 'Hamburger (tmavé)'
'Hamburger 2': 'Hamburger 2'
'Hamburger 2 (dark)': 'Hamburger 2 (tmavé)'
'Before the logo': 'Před logem'
'After the logo': 'Za logem'
'Position': 'Pozice'
'Show only the opener (hidden logo)': 'Zobrazovat pouze otevírací tlačítko (logo skryto)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'Nezobrazovat postranní nabídku a její otevírací tlačítko pokud nejsou dostupné žádné aplikace (např. na veřejných stránkách).'
'Panel': 'Panel'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Otevřít nabídku při najetím ukazatelem na tlačítko nabídky (automaticky vypnuto pro dotykové obrazovky)'
'Display the big menu': 'Zobrazit velkou nabídku'
'Display the logo': 'Zobrazit logo'
'Icons and texts': 'Ikony a texty'
'Loader enabled': 'Načítání zapnuto'
'Tips': 'Tipy'
'Always displayed': 'Vždy zobrazeno'
'This is the automatic behavior when the menu is always displayed.': 'Toto je automatické chování, kdy je nabídka vždy zobrazena.'
'Not compatible with touch screens.': 'Nekompatibilní s dotykovými obrazovkami.'
'Big menu': 'Velká nabídka'
'Live preview': 'Živý náhled'
'Open apps in new tab': 'Otevírat aplikace v novém panelu'
'Use the global setting': 'Použít globální nastavení'
'Use my selection': 'Použít můj výběr'
'Show and hide the list of applications': 'Zobrazit/skrýt seznam aplikací'
'Use the avatar instead of the logo': 'Použít namísto loga profilový obrázek uživatele'
'You do not have permission to change the settings.': 'Nemáte oprávnění měnit nastavení.'
'Force this configuration to users': 'Vynutit uplatnění těchto nastavení uživatelům'
'Export the configuration': 'Exportovat nastavení'
'Purge the cache': 'Vyprázdnit mezipaměť'
'Show the link to settings': 'Zobrazit odkaz na nastavení'
'The menu is enabled by default for users': 'Nabídka je ve výchozím stavu pro uživatele zapnutá'
'Except when the configuration is forced.': 'S výjimkou, kdy je nastavení vynuceno.'
'Apps that should not be displayed in the menu': 'Aplikace, které by neměly být v nabídce zobrazeny'
'This feature is only compatible with the <code>big menu</code> display.': 'Tato funkce je kompatibilní pouze s <code>velkou nabídkou</code>.'
'The logo is a link to the default app': 'Logo je odkaz na výchozí aplikaci'
'Others': 'Ostatní'
'Categories': 'Kategorie'
'Customize sorting': 'Přizpůsobit si řazení'
'Order by': 'Řadit podle'
'Name': 'Název'
'Customed': 'Přizpůsobeno'
'Show and hide the list of categories': 'Zobrazit/skrýt seznam kategorií'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Tyto parametry jsou použity v případě, že je zapnutý (Breeze) tmavý motiv vzhledu.'
'Dark mode colors': 'Barvy tmavého režimu'
'With categories': 'S kategoriemi'
'Custom categories': 'Vlastní kategorie'
'Customize application categories': 'Přizpůsobte kategorie aplikací'
'Reset to default': 'Vrátit zpět na výchozí hodnoty'
'Hidden icon': 'Skrytá ikona'
'Small icon': 'Malá ikona'
'Normal icon': 'Normální ikona'
'Big icon': 'Velká ikona'
'Hidden text': 'Skrytý text'
'Small text': 'Malý text'
'Normal text': 'Normální text'
'Big text': 'Velký text'
'Applications': 'Aplikace'
'Applications kept in the top menu': 'Aplikace ponechané v horní nabídce'
'Applications kept in the top menu but also shown in side menu': 'Aplikace ponechané v horní nabídce ale také zobrazené v té boční'
'These applications must be selected in the previous option.': 'Tyto aplikace je třeba vybrat v předchozí volbě.'
'Hide labels on mouse over': 'Skrýt popisky při najetím ukazatele myši'
'Except the hovered app': 'S výjimkou nadnášené aplikace'
'Search': 'Hledat'
'Toggle the menu': 'Vyp/zap nabídku'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Zeptejte se vývojáře'
'New request': 'Nový požadavek'
'Report a bug': 'Nahlásit chybu'
'Show the configuration': 'Zobrazit nastavení'
'Configuration:': 'Configuration:'
'Done!': 'Hotovo!'
'Copy': 'Zkopírovat'
'Need help': 'Potřebuji pomoc'
'I would like a new feature': 'Rád bych novou funkci v aplikaci'
'Something went wrong': 'Něco se pokazilo'
'Select apps': 'Vyberte aplikace'
'Sort': 'Seřadit'
'Customize': 'Přizpůsobit'
'Custom': 'Custom'
'Close': 'Zavřít'

View file

@ -1,111 +1,58 @@
'Custom menu': 'Benutzerdefiniertes Menü'
'Enable the custom menu': 'Benutzerdefiniertes Menü aktivieren'
'No': 'Nein'
'Yes': 'Ja'
'Menu': 'Menü'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Verwende die Tastenkombination <span class="keyboard-key">Strg</span>+o, um das Seitenmenü ein- und auszublenden. Verwende tab key zum Navigieren.'
'Top menu': 'Obere Navigationsleiste'
'Apps that not must be moved in the side menu': 'Apps, die nicht ins Seitenmenü verschoben werden sollen'
'If there is no selection then the global configuration is applied.': 'Wenn keine Auswahl vorhanden ist, wird die globale Konfiguration angewendet.'
'Experimental': 'Experimentell'
'Save': 'Speichern'
'You like this app and you want to support me?': 'Du magst diese App und möchtest mich unterstützen?'
'Buy me a coffee ☕': 'Gib mir einen Kaffee aus ☕'
'Hidden': 'Ausblenden'
'Small': 'Klein'
'Normal': 'Normal'
'Big': 'Groß'
'Colors': 'Farben'
'Background color': 'Hintergrundfarbe'
'Background color of current app': 'Hintergrundfarbe der aktuellen App'
'Text color': 'Textfarbe'
'Loader': 'Fortschrittsbalken'
'Icon': 'Symbol'
'Same color': 'Selbe Farbe'
'Opposite color': 'Gegenfarbe'
'Transparent': 'Transparent'
'Opaque': 'Nicht transparent'
'Opener': 'Menü-Symbol'
'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 das Menü-Symbol anzeigen (Logo wird ausgeblendet)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'Zeige das Seitenmenü und das Menü-Symbol nicht an, wenn keine App vorhanden ist (z.B. bei öffentlichen Seiten).'
'Panel': 'Panel'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Öffne das Menü, wenn die Maus über das Menü-Symbol bewegt wird (auf Touchscreens automatisch deaktiviert)'
'Display the big menu': 'Großes Menü anzeigen'
'Display the logo': 'Logo anzeigen'
'Icons and texts': 'Symbole und Texte'
'Loader enabled': 'Fortschrittsbalken anzeigen'
'Tips': 'Tipps'
'Always displayed': 'Immer anzeigen'
'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.'
'Big menu': 'Großes Menü'
'Live preview': 'Live-Vorschau'
'Open apps in new tab': 'Öffne Apps in einem neuen Tab'
'Use the global setting': 'Verwende die globale Einstellung'
'Use my selection': 'Verwende meine Auswahl'
'Show and hide the list of applications': 'Ein- und Ausblenden der Appliste'
'Use the avatar instead of the logo': 'Avatar anstelle des Logos anzeigen'
'You do not have permission to change the settings.': 'Du hast keine Berechtigung, die Einstellungen dieser App zu ändern.'
'Force this configuration to users': 'Konfiguration für alle Benutzer erzwingen'
'Export the configuration': 'Konfiguration exportieren'
'Purge the cache': 'Cache leeren'
'Show the link to settings': 'Link zu den Einstellungen anzeigen'
'The menu is enabled by default for users': 'Das Menü ist standardmäßig für alle Benutzer aktiviert'
'Except when the configuration is forced.': 'Gilt nicht, wenn die Konfiguration erzwungen wird.'
'Apps that should not be displayed in the menu': 'Apps, die nicht im Menü angezeigt werden sollen'
'This feature is only compatible with the <code>big menu</code> display.': 'Kompatibel mit dem <code>großen Menü</code>.'
'The logo is a link to the default app': 'Das Logo ist ein Link zur Standard-App'
'Others': 'Andere'
'Categories': 'Kategorien'
'Customize sorting': 'Sortierung anpassen'
'Order by': 'Sortieren nach'
'Name': 'Name'
'Customed': 'Benutzerdefiniert'
'Show and hide the list of categories': 'Liste der Kategorien ein- und ausblenden'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Diese Optionen werden auf <code>Dark Theme</code> oder <code>Breeze Dark Theme</code> angewendet.'
'Dark mode colors': 'Farben für den dunklen Modus'
'With categories': 'Mit Kategorien'
'Custom categories': 'Benutzerdefinierte Kategorien'
'Customize application categories': 'App-Kategorien anpassen'
'Reset to default': 'Auf Standard zurücksetzen'
'Hidden icon': 'Verstecktes Symbol'
'Small icon': 'Kleines Symbol'
'Normal icon': 'Normales Symbol'
'Big icon': 'Großes Icon'
'Hidden text': 'Versteckter Text'
'Small text': 'Kleiner Text'
'Normal text': 'Normaler Text'
'Big text': 'Großer Text'
'Applications': 'Apps'
'Applications kept in the top menu': 'Apps in der oberen Navigationsleiste'
'Applications kept in the top menu but also shown in side menu': 'Apps in der oberen Navigationsleiste, die auch im Seitenmenü angezeigt werden sollen'
'These applications must be selected in the previous option.': 'Diese Apps müssen auch in der vorherigen Einstellung ausgewählt werden.'
'Hide labels on mouse over': 'Labels ausblenden, wenn sich die Maus darüber befindet (Hover)'
'Except the hovered app': 'Außer die markierte App'
'Search': 'Suche'
'Toggle the menu': 'Menü ein- und ausblenden'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Ask the developer'
'New request': 'New request'
'Report a bug': 'Report a bug'
'Show the configuration': 'Show the configuration'
'Configuration:': 'Configuration:'
'Done!': 'Done!'
'Copy': 'Copy'
'Need help': 'Need help'
'I would like a new feature': 'I would like a new feature'
'Something went wrong': 'Something went wrong'
'Select apps': 'Select apps'
'Sort': 'Sort'
'Customize': 'Customize'
'Custom': 'Custom'
'Close': 'Close'
"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.": "Das große Menü ist nicht mit AppOrder kompatibel."
"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."
"Big menu": "Große Speisekarte"
"Live preview": "Live Vorschau"
"Open apps in new tab": "Öffnen Sie Apps in einem neuen Tab"
"Use the global setting": "Verwenden Sie die globale Einstellung"
"Use my selection": "Verwenden Sie meine Auswahl"
"Show and hide the list of applications": "Ein- und Ausblenden der Anwendungsliste"

View file

@ -1,111 +0,0 @@
'Custom menu': 'Menú personalizado'
'Enable the custom menu': 'Activar el menú personalizado'
'No': 'No'
'Yes': 'Sí'
'Menu': 'Menú'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Usa la combinación de teclas Ctrl+o para activar y desactivar el menú lateral. Use tab key para navegar.'
'Top menu': 'Menu principal'
'Apps that not must be moved in the side menu': 'Aplicaciones que no se deben mover al menú lateral'
'If there is no selection then the global configuration is applied.': 'Si no hay selección, se aplica la configuración global.'
'Experimental': 'En pruebas'
'Save': 'Guardar'
'You like this app and you want to support me?': '¿Te gusta esta aplicación y quieres apoyarme?'
'Buy me a coffee ☕': 'Cómprame un café ☕'
'Hidden': 'Oculto'
'Small': 'Pequeño'
'Normal': 'Normal'
'Big': 'Grande'
'Hidden icon': 'Ocultar Icono'
'Small icon': 'Icono pequeño'
'Normal icon': 'Icono normal'
'Big icon': 'Icono grande'
'Hidden text': 'Texto oculto'
'Small text': 'Texto pequeño'
'Normal text': 'Texto normal'
'Big text': 'Texto grande'
'Colors': 'Colores'
'Background color': 'Color de fondo'
'Background color of current app': 'Color de fondo de la aplicación actual'
'Text color': 'Color del texto'
'Loader': 'Cargador'
'Icon': 'Icono'
'Same color': 'El mismo color'
'Opposite color': 'Color opuesto'
'Transparent': 'Transparente'
'Opaque': 'Opaco'
'Opener': 'Abrir'
'Default': 'Por defecto'
'Default (dark)': 'Por defecto (oscuro)'
'Hamburger': 'Hamburguesa'
'Hamburger (dark)': 'Hamburger (negro)'
'Hamburger 2': 'Hamburguesa 2'
'Hamburger 2 (dark)': 'Hamburger 2 (negro)'
'Before the logo': 'Antes del logotipo'
'After the logo': 'Después del logotipo'
'Position': 'Posición'
'Show only the opener (hidden logo)': 'Mostrar solo abrir (ocultar logotipo)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'No mostrar el menú lateral y el abridor si no hay aplicación (por ejemplo: páginas públicas).'
'Panel': 'Panel'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Abra el menú cuando el ratón esté sobre el icono (se desactiva automáticamente en las pantallas táctiles)'
'Display the big menu': 'Mostrar el menú grande'
'Display the logo': 'Mostrar el logotipo'
'Icons and texts': 'Iconos y textos'
'Loader enabled': 'Cargador activado'
'Tips': 'Consejos'
'Always displayed': 'Siempre se muestra'
'This is the automatic behavior when the menu is always displayed.': 'Este es el comportamiento automático cuando aún se muestra el menú.'
'Not compatible with touch screens.': 'No es compatible con las pantallas táctiles.'
'Big menu': 'Menú grande'
'Live preview': 'Previsualización en directo'
'Open apps in new tab': 'Abrir las aplicaciones en una nueva pestaña'
'Use the global setting': 'Utilizar la configuración global'
'Use my selection': 'Utilizar mi selección'
'Show and hide the list of applications': 'Mostrar y ocultar la lista de aplicaciones'
'Use the avatar instead of the logo': 'Utilizar un avatar en lugar de un logotipo'
'You do not have permission to change the settings.': 'No tienes permiso para cambiar la configuración.'
'Force this configuration to users': 'Forzar esta configuración a todos los usuarios'
'Export the configuration': 'Exportar la configuración'
'Purge the cache': 'Vaciar la caché'
'Show the link to settings': 'Mostrar un enlace a la configuración'
'The menu is enabled by default for users': 'El menú está activado por defecto para los usuarios'
'Except when the configuration is forced.': 'Excepto cuando la configuración es forzada.'
'Apps that should not be displayed in the menu': 'Aplicaciones que no deben aparecer en el menú'
'This feature is only compatible with the <code>big menu</code> display.': 'Esta función sólo es compatible con la pantalla del <code>menú grande</code>.'
'The logo is a link to the default app': 'El logotipo es un enlace a la aplicación por defecto'
'Others': 'Otros'
'Categories': 'Categorías'
'Customize sorting': 'Personalizar la clasificación'
'Order by': 'Ordenar por'
'Name': 'Nombre'
'Customed': 'Personalizado'
'Show and hide the list of categories': 'Mostrar y ocultar la lista de categorías'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Estos parámetros se utilizan cuando el tema oscuro o el tema oscuro de Breeze están activados.'
'Dark mode colors': 'Colores del modo oscuro'
'With categories': 'Con categorías'
'Custom categories': 'Categorías personalizadas'
'Customize application categories': 'Personalizar las categorías de las aplicaciones'
'Reset to default': 'Restablecer los valores por defecto'
'Applications': 'Aplicaciones'
'Applications kept in the top menu': 'Aplicaciones guardadas en el menú superior'
'Applications kept in the top menu but also shown in side menu': 'Las aplicaciones se mantienen en el menú superior pero también se muestran en el menú lateral'
'These applications must be selected in the previous option.': 'Estas aplicaciones deben ser seleccionadas en las opciones anteriores.'
'Hide labels on mouse over': 'Ocultar las etiquetas al pasar el ratón'
'Except the hovered app': 'Excepto la aplicación sobre la que se pasa el cursor'
'Search': 'Buscar'
'Toggle the menu': 'Alternar el menú'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Pregúntale al desarrollador'
'New request': 'Nueva solicitud'
'Report a bug': 'Informar de un fallo'
'Show the configuration': 'Mostrar los ajustes'
'Configuration:': 'Configuration:'
'Done!': '¡Realizado!'
'Copy': 'Copiar'
'Need help': 'Ayudame'
'I would like a new feature': 'Me gustaría una nueva función'
'Something went wrong': 'Algo salió mal'
'Select apps': 'Selecciona las aplicaciones'
'Sort': 'Ordenar'
'Customize': 'Personalizar'
'Custom': 'Custom'
'Close': 'Cerrar'

View file

@ -1,130 +1,58 @@
'Custom menu': 'Menu personnalisé'
'Enable the custom menu': 'Activer le menu personnalisé'
'No': 'Non'
'Yes': 'Oui'
'Menu': 'Menu'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Utiliser
le raccourcis clavier Ctrl+o pour ouvrir et fermer le menu latéral. Utiliser tab
key 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 coffee ☕': 'Offrez moi un café ☕'
'Hidden': 'Caché'
'Small': 'Petit'
'Normal': 'Normal'
'Big': 'Gros'
'Hidden icon': 'Icône masqué'
'Small icon': 'Petit icône'
'Normal icon': 'Icône normal'
'Big icon': 'Gros icône'
'Hidden text': 'Text masqué'
'Small text': 'Texte petit'
'Normal text': 'Texte normal'
'Big text': 'Gros texte'
'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'
'Display the logo': 'Afficher le logo'
'Icons and texts': 'Icônes et textes'
'Loader enabled': "Activation de l'indicateur de chargement"
'Tips': 'Astuces'
'Always displayed': '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.'
'Big menu': 'Menu large'
'Live preview': 'Aperçu en direct'
'Open apps in new tab': 'Ouvrir les applications dans un nouvel onglet'
'Use the global setting': 'Utiliser la configuration globale'
'Use my selection': 'Utiliser ma sélection'
'Show and hide the list of applications': 'Afficher et masquer la liste des applications'
'Use the avatar instead of the logo': "Utiliser l'avatar à la place du logo"
'You do not have permission to change the settings.': "Vous n'avez pas la permission
de changer les paramètres."
'Force this configuration to users': 'Forcer cette configuration aux utilisateurs'
'Export the configuration': 'Exporter la configuration'
'Purge the cache': 'Purger le cache'
'Show the link to settings': 'Afficher le lien vers les paramètres'
'The menu is enabled by default for users': 'Le menu est activé par défaut pour les
utilisateurs'
'Except when the configuration is forced.': 'Sauf lorsque la configuration est forcée.'
'Apps that should not be displayed in the menu': 'Applications qui ne doivent pas
être affichées dans le menu'
'This feature is only compatible with the <code>big menu</code> display.': "Compatible
avec l'affichage <code>Menu large</code>."
'The logo is a link to the default app': "Le logo est un lien vers l'application par
défaut"
'Others': 'Autres'
'Categories': 'Catégories'
'Customize sorting': 'Personnaliser le tri'
'Order by': 'Trier par'
'Name': 'Nom'
'Customed': 'Personnalisé'
'Show and hide the list of categories': 'Afficher et masquer la liste des catégories'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Ces
paramètres sont utilisés lorsque le thème sombre ou le thème Breeze Dark sont activés.'
'Dark mode colors': 'Couleurs du mode sombre'
'With categories': 'Avec les catégories'
'Custom categories': 'Catégories personnalisées'
'Customize application categories': 'Personnaliser les catégories des applications'
'Reset to default': 'Restaurer les valeurs par défaut'
'Applications': 'Applications'
'Applications kept in the top menu': 'Applications conservées dans le menu supérieur'
'Applications kept in the top menu but also shown in side menu': 'Applications conservées
dans le menu supérieur mais également affichées dans le menu latéral'
'These applications must be selected in the previous option.': "Ces applications doivent
également être sélectionnées dans l'option précédente."
'Hide labels on mouse over': 'Masquer le libellé des applications au passage de la
souris'
'Except the hovered app': "À l'exception de l'application survolée"
'Search': 'Rechercher'
'Toggle the menu': 'Basculer le menu'
'Open the documentation': 'Afficher la documentation'
'Ask the developer': 'Demander au(x) développeurs⋅euses'
'New request': 'Nouvelle requête'
'Report a bug': 'Rapporter un bug'
'Show the configuration': 'Afficher la configuration'
'Configuration:': 'Configuration :'
'Done!': 'Fait !'
'Copy': 'Copié'
'Need help': "Besoin d'aide"
'I would like a new feature': 'Je souhaiterais une fonctionnalité'
'Something went wrong': "Quelque chose s'est mal passé"
'Select apps': 'Selection des apps'
'Sort': 'Ordonner'
'Customize': 'Personnaliser'
'Custom': 'Personnalisé'
'Close': 'Fermer'
"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."
"Big menu": "Menu large"
"Live preview": "Aperçu en direct"
"Open apps in new tab": "Ouvrir les applications dans un nouvel onglet"
"Use the global setting": "Utiliser la configuration globale"
"Use my selection": "Utiliser ma sélection"
"Show and hide the list of applications": "Afficher et masquer la liste des applications"

View file

@ -1,111 +0,0 @@
'Custom menu': 'Menú personalizado'
'Enable the custom menu': 'Activar o menú personalizado'
'No': 'Non'
'Yes': 'Si'
'Menu': 'Menú'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Use o atallo Ctrl+o para abrir e agochar o menú lateral. Use a tecla Tab para navegar.'
'Top menu': 'Top menu'
'Apps that not must be moved in the side menu': 'As aplicacións que non deben moverse no menú lateral'
'If there is no selection then the global configuration is applied.': 'Se non hai selección, aplícase a configuración global.'
'Experimental': 'Experimental'
'Save': 'Gardar'
'You like this app and you want to support me?': 'Gústalle esta aplicación e quere axudarme?'
'Buy me a coffee ☕': 'Convídeme a un café ☕'
'Hidden': 'Agochado'
'Small': 'Pequeno'
'Normal': 'Normal'
'Big': 'Grande'
'Hidden icon': 'Icona agochada'
'Small icon': 'Icona pequena'
'Normal icon': 'Icona normal'
'Big icon': 'Icona grande'
'Hidden text': 'Texto agochado'
'Small text': 'Texto pequeno'
'Normal text': 'Texto normal'
'Big text': 'Texto grande'
'Colors': 'Cores'
'Background color': 'Cor do fondo'
'Background color of current app': 'Cor do fondo da aplicación actual'
'Text color': 'Cor do texto'
'Loader': 'Cargador'
'Icon': 'Icona'
'Same color': 'A mesma cor'
'Opposite color': 'A cor oposta'
'Transparent': 'Transparente'
'Opaque': 'Opaco'
'Opener': 'Abrir'
'Default': 'Predeterminado'
'Default (dark)': 'Predeterminado (escuro)'
'Hamburger': 'Hamburguesa'
'Hamburger (dark)': 'Hamburguesa (escuro)'
'Hamburger 2': 'Hamburguesa 2'
'Hamburger 2 (dark)': 'Hamburguesa 2 (escuro)'
'Before the logo': 'Antes do logotipo'
'After the logo': 'Após o logotipo'
'Position': 'Posición'
'Show only the opener (hidden logo)': 'Amosar só a icona de abrir (agochar o logotipo)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'Non amosar o menú lateral e a icona de abrir se non hai ningunha aplicación (por exemplo: páxinas públicas).'
'Panel': 'Panel'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Abre o menú cando o rato está sobre a icona de abrir (desactivado automaticamente nas pantallas táctiles)'
'Display the big menu': 'Amosar o menú en grande'
'Display the logo': 'Amosar o logotipo'
'Icons and texts': 'Iconas e textos'
'Loader enabled': 'Cargador activado'
'Tips': 'Consellos'
'Always displayed': 'Amosado sempre'
'This is the automatic behavior when the menu is always displayed.': 'Este é o comportamento automático cando se amosa sempre o menú.'
'Not compatible with touch screens.': 'Non é compatíbel coas pantallas táctiles.'
'Big menu': 'Menú grande'
'Live preview': 'Vista previa en directo'
'Open apps in new tab': 'Abrir as aplicacións nunha nova lapela'
'Use the global setting': 'Usar o axuste global'
'Use my selection': 'Usar a miña selección'
'Show and hide the list of applications': 'Amosar e agochar a lista de aplicacións'
'Use the avatar instead of the logo': 'Usar o avatar no canto do logotipo'
'You do not have permission to change the settings.': 'Non ten permiso para cambiar os axustes.'
'Force this configuration to users': 'Forzar esta configuración para os usuarios'
'Export the configuration': 'Exportar a configuración'
'Purge the cache': 'Limpar a caché'
'Show the link to settings': 'Amosar a ligazón aos axustes'
'The menu is enabled by default for users': 'De xeito predeterminado o menú está activado para os usuarios'
'Except when the configuration is forced.': 'Agás cando a configuración é forzada.'
'Apps that should not be displayed in the menu': 'Aplicacións que non deben amosarse no menú'
'This feature is only compatible with the <code>big menu</code> display.': 'Esta función só é compatíbel coa presentación do <code>menú grande</code>.'
'The logo is a link to the default app': 'O logotipo é unha ligazón á aplicación predeterminada'
'Others': 'Outros'
'Categories': 'Categorías'
'Customize sorting': 'Personalizar a ordenación'
'Order by': 'Ordenar por'
'Name': 'Nome'
'Customed': 'Personalizado'
'Show and hide the list of categories': 'Amosar e agochar a lista de categorías'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Estes parámetros úsanse cando o tema escuro ou o tema escuro de Breeze están activados.'
'Dark mode colors': 'Cores do modo escuro'
'With categories': 'Con categorías'
'Custom categories': 'Categorías personalizadas'
'Customize application categories': 'Personalizar as categorías das aplicacións'
'Reset to default': 'Restabelecer os valores predeterminados'
'Applications': 'Aplicacións'
'Applications kept in the top menu': 'As aplicacións mantéñense no menú superior'
'Applications kept in the top menu but also shown in side menu': 'As aplicacións mantéñense no menú superior mais tamén aparecen no menú lateral'
'These applications must be selected in the previous option.': 'Estas aplicacións deben ser seleccionadas na opción anterior.'
'Hide labels on mouse over': 'Agochar as etiquetas ao pasar o rato'
'Except the hovered app': 'Agás a aplicación que pasa o rato'
'Search': 'Buscar'
'Toggle the menu': 'Alternar o menú'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Preguntar ao desenvolvedor'
'New request': 'Nova solicitude'
'Report a bug': 'Informar dun fallo'
'Show the configuration': 'Amosar a configuración'
'Configuration:': 'Configuración:'
'Done!': 'Feito!'
'Copy': 'Copiar'
'Need help': 'Necesito axuda'
'I would like a new feature': 'Gustaríame unha nova característica'
'Something went wrong': 'Algo foi mal'
'Select apps': 'Seleccionar as aplicacións'
'Sort': 'Ordenar'
'Customize': 'Personalizar'
'Custom': 'Personalizado'
'Close': 'Pechar'

View file

@ -1,111 +0,0 @@
'Custom menu': 'Aangepast menu'
'Enable the custom menu': 'Het aangepaste menu inschakelen'
'No': 'Nee'
'Yes': 'Ja'
'Menu': 'Menu'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Gebruik de snelkoppeling Ctrl+o om het zijmenu te openen en te verbergen. Gebruik tab key om te navigeren.'
'Top menu': 'Bovenste menu'
'Apps that not must be moved in the side menu': 'Apps die niet moeten worden verplaatst in het zijmenu'
'If there is no selection then the global configuration is applied.': 'Als er geen keuze is, wordt de globale configuratie toegepast.'
'Experimental': 'Experimenteel'
'Save': 'Opslaan'
'You like this app and you want to support me?': 'Vind je deze app leuk en wil je me steunen?'
'Buy me a coffee ☕': 'Koop een koffie voor me ☕'
'Hidden': 'Verborgen'
'Small': 'Klein'
'Normal': 'Normaal'
'Big': 'Groot'
'Hidden icon': 'Verborgen icoon'
'Small icon': 'Klein icoon'
'Normal icon': 'Normaal icoon'
'Big icon': 'Groot icoon'
'Hidden text': 'Verborgen tekst'
'Small text': 'Kleine tekst'
'Normal text': 'Normale tekst'
'Big text': 'Grote tekst'
'Colors': 'Kleuren'
'Background color': 'Achtergrond kleur'
'Background color of current app': 'Achtergrondkleur van huidige app'
'Text color': 'Tekst kleur'
'Loader': 'Lader'
'Icon': 'Icoon'
'Same color': 'Zelfde kleur'
'Opposite color': 'Tegenovergestelde kleur'
'Transparent': 'Transparant'
'Opaque': 'Ondoorzichtig'
'Opener': 'Opener'
'Default': 'Standaard'
'Default (dark)': 'Standaard (donker)'
'Hamburger': 'Hamburger'
'Hamburger (dark)': 'Hamburger (donker)'
'Hamburger 2': 'Hamburger 2'
'Hamburger 2 (dark)': 'Hamburger 2 (donker)'
'Before the logo': 'Voor het logo'
'After the logo': 'Na het logo'
'Position': 'Positie'
'Show only the opener (hidden logo)': 'Toon alleen de opener (verborgen logo)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'Geef het zijmenu en de opener niet weer als er geen toepassing is (bijv. openbare pagina''s).'
'Panel': 'Paneel'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Open het menu wanneer de muis over de opener gaat (automatisch uitgeschakeld op aanraakschermen)'
'Display the big menu': 'Toon het grote menu'
'Display the logo': 'Toon het logo'
'Icons and texts': 'Iconen en teksten'
'Loader enabled': 'Lader ingeschakeld'
'Tips': 'Tips'
'Always displayed': 'Altijd weergegeven'
'This is the automatic behavior when the menu is always displayed.': 'Dit is het automatische gedrag wanneer het menu altijd wordt weergegeven.'
'Not compatible with touch screens.': 'Niet compatibel met aanraakschermen.'
'Big menu': 'Groot menu'
'Live preview': 'Live voorbeeld'
'Open apps in new tab': 'Open apps in nieuwe tab'
'Use the global setting': 'Gebruik de globale instellingen'
'Use my selection': 'Gebruik mijn selectie'
'Show and hide the list of applications': 'De lijst met toepassingen tonen en verbergen'
'Use the avatar instead of the logo': 'Gebruik avatar in plaats van het logo'
'You do not have permission to change the settings.': 'Je hebt geen toestemming om de instellingen te veranderen.'
'Force this configuration to users': 'Forceer deze configuratie aan gebruikers'
'Export the configuration': 'Exporteer de configuratie'
'Purge the cache': 'De cache wissen'
'Show the link to settings': 'Toon de link naar de instellingen'
'The menu is enabled by default for users': 'Het menu is standaard ingeschakeld voor gebruikers'
'Except when the configuration is forced.': 'Behalve als de configuratie geforceerd is.'
'Apps that should not be displayed in the menu': 'Apps die niet in het menu weergegeven mogen worden'
'This feature is only compatible with the <code>big menu</code> display.': 'Deze functie is alleen compatibel met het <code>grote menu</code> scherm.'
'The logo is a link to the default app': 'Het logo is een link naar de standaard app'
'Others': 'Overige'
'Categories': 'Categorieën'
'Customize sorting': 'Sortering aanpassen'
'Order by': 'Sorteer op'
'Name': 'Naam'
'Customed': 'Aangepast'
'Show and hide the list of categories': 'De lijst met categorieën tonen en verbergen'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Deze parameters worden gebruikt wanneer Dark theme of Breeze Dark Theme zijn ingeschakeld.'
'Dark mode colors': 'Donkere modus kleuren'
'With categories': 'Met categorieën'
'Custom categories': 'Aangepaste categorieën'
'Customize application categories': 'Toepassingscategorieën aanpassen'
'Reset to default': 'Terugzetten naar standaard'
'Applications': 'Applicaties'
'Applications kept in the top menu': 'Applicaties bewaard in het bovenste menu'
'Applications kept in the top menu but also shown in side menu': 'Applicaties blijven in het topmenu maar worden ook in het zijmenu getoond'
'These applications must be selected in the previous option.': 'Deze toepassingen moeten bij de vorige optie zijn geselecteerd.'
'Hide labels on mouse over': 'Hide labels on mouse over'
'Except the hovered app': 'Except the hovered app'
'Search': 'Search'
'Toggle the menu': 'Toggle the menu'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Ask the developer'
'New request': 'New request'
'Report a bug': 'Report a bug'
'Show the configuration': 'Show the configuration'
'Configuration:': 'Configuration:'
'Done!': 'Done!'
'Copy': 'Copy'
'Need help': 'Need help'
'I would like a new feature': 'I would like a new feature'
'Something went wrong': 'Something went wrong'
'Select apps': 'Select apps'
'Sort': 'Sort'
'Customize': 'Customize'
'Custom': 'Custom'
'Close': 'Close'

View file

@ -1,109 +0,0 @@
'Custom menu': 'Menú personalizado'
'Enable the custom menu': 'Activar o menu personalizado'
'No': 'Não'
'Yes': 'Sim'
'Menu': 'Menu'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Use o atalho Ctrl+o para exibir e para esconder o menu lateral. Use tab key para navegar.'
'Top menu': 'Menu superior'
'Apps that not must be moved in the side menu': 'Apps que não devem ser movidos para o menu lateral'
'If there is no selection then the global configuration is applied.': 'Se não houver seleção, a configuração global será aplicada.'
'Experimental': 'Experimental'
'Save': 'Salvar'
'You like this app and you want to support me?': 'Você gosta deste aplicativo e quer me apoiar?'
'Buy me a coffee ☕': 'Me pague um café ☕'
'Hidden': 'Oculto'
'Small': 'Pequeno'
'Normal': 'Normal'
'Big': 'Grande'
'Hidden icon': 'Ícone oculto'
'Small icon': 'Ícone pequeno'
'Normal icon': 'Ícone normal'
'Big icon': 'Ícone grance'
'Hidden text': 'Texto oculto'
'Small text': 'Texto pequeno'
'Normal text': 'Texto normal'
'Big text': 'Texto grande'
'Colors': 'Cores'
'Background color': 'Cor de fundo'
'Background color of current app': 'Cor de fundo do app atual'
'Text color': 'Cor do texto'
'Loader': 'Progresso'
'Icon': 'Ícone'
'Same color': 'Mesma cor'
'Opposite color': 'Cor oposta'
'Transparent': 'Transparente'
'Opaque': 'Opaco'
'Opener': 'Abrir'
'Default': 'Padrão'
'Default (dark)': 'Padrão (escuro)'
'Hamburger': 'Hamburger'
'Hamburger (dark)': 'Hamburger (escuro)'
'Hamburger 2': 'Hamburger 2'
'Hamburger 2 (dark)': 'Hamburger 2 (escuro)'
'Before the logo': 'Antes da logo'
'After the logo': 'Depois da logo'
'Position': 'Posição'
'Show only the opener (hidden logo)': 'Mostrar apenas o Abrir (ocultar logo)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'Não mostrar o menu lateral e o Abrir se não houver aplicação (p.ex. páginas públicas).'
'Panel': 'Painel'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Abrir o menu quando o mouse passar sobre o Abrir (desativado automaticamente em telas de toque)'
'Display the big menu': 'Mostrar o menu grande'
'Display the logo': 'Mostrar a logo'
'Icons and texts': 'Ícones e textos'
'Loader enabled': 'Progresso ativado'
'Tips': 'Dicas'
'Always displayed': 'Sempre visível'
'This is the automatic behavior when the menu is always displayed.': 'Este é o comportamento automático quando o menu está sempre visível.'
'Not compatible with touch screens.': 'Não compatível com telas de toque.'
'Big menu': 'Menu grande'
'Live preview': 'Visualização ao vivo'
'Open apps in new tab': 'Abrir apps em nova aba'
'Use the global setting': 'Usar configurações globais'
'Use my selection': 'Usar minha seleção'
'Show and hide the list of applications': 'Mostrar e ocultar a lista de aplicativos'
'Use the avatar instead of the logo': 'Use o avatar ao invés da logo'
'You do not have permission to change the settings.': 'Você não tem permissão para alterar as configurações.'
'Force this configuration to users': 'Forçar esta configuração para os usuários'
'Export the configuration': 'Exportar a configuração'
'Purge the cache': 'Limpar o cache'
'Show the link to settings': 'Mostrar o link para configurações'
'The menu is enabled by default for users': 'O menu é habilitado por padrão para os usuários'
'Except when the configuration is forced.': 'Exceto quando a configuração é forçada.'
'Apps that should not be displayed in the menu': 'Apps que não devem ser mostrados no menu'
'This feature is only compatible with the <code>big menu</code> display.': 'Este recurso só é compatível com a exibição do <code>menu grande</code>.'
'The logo is a link to the default app': 'A logo é um link para o app padrão'
'Others': 'Outros'
'Categories': 'Categorias'
'Customize sorting': 'Personalizar classificação'
'Order by': 'Ordenar por'
'Name': 'Nome'
'Customed': 'Personalizado'
'Show and hide the list of categories': 'Mostrar e esconder a lista de categorias'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Estes parâmetros são usados quando o tema escuro ou o tema Dark Breeze está ativo.'
'Dark mode colors': 'Cores do modo escuro'
'With categories': 'Com categorias'
'Custom categories': 'Categorias personalizadas'
'Customize application categories': 'Personalizar categorias de apps'
'Reset to default': 'Restaurar padrão'
'Applications': 'Aplicativos'
'Applications kept in the top menu': 'Aplicativos mantidos no menu superior'
'Applications kept in the top menu but also shown in side menu': 'Aplicativos mantidos no menu superior, mas também mostrados no menu lateral'
'These applications must be selected in the previous option.': 'Estes aplicativos devem ser selecionados na opção anterior.'
'Hide labels on mouse over': 'Ocultar descrição ao passar o mouse'
'Toggle the menu': 'Toggle the menu'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Ask the developer'
'New request': 'New request'
'Report a bug': 'Report a bug'
'Show the configuration': 'Show the configuration'
'Configuration:': 'Configuration:'
'Done!': 'Done!'
'Copy': 'Copy'
'Need help': 'Need help'
'I would like a new feature': 'I would like a new feature'
'Something went wrong': 'Something went wrong'
'Select apps': 'Select apps'
'Sort': 'Sort'
'Customize': 'Customize'
'Custom': 'Custom'
'Close': 'Close'

View file

@ -1,111 +0,0 @@
'Custom menu': 'Пользовательское меню'
'Enable the custom menu': 'Включить пользовательское меню'
'No': 'Нет'
'Yes': 'Да'
'Menu': 'Меню'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Используйте сочетание клавиш Ctrl+o, чтобы открыть или скрыть боковое меню. Используйте tab key для навигации.'
'Top menu': 'Верхнее меню'
'Apps that not must be moved in the side menu': 'Приложения не перемещаемые в боковое меню'
'If there is no selection then the global configuration is applied.': 'Если тут ничего не отмечено, применяются глобальные настройки.'
'Experimental': 'Экспериментальный'
'Save': 'Сохранить'
'You like this app and you want to support me?': 'Вам нравится приложение или вы хотите поддержать меня?'
'Buy me a coffee ☕': 'Купить мне чашку кофе ☕'
'Hidden': 'Скрыто'
'Small': 'Маленький'
'Normal': 'Средний'
'Big': 'Большой'
'Hidden icon': 'Без иконки'
'Small icon': 'Маленькая иконка'
'Normal icon': 'Средняя иконка'
'Big icon': 'Большая иконка'
'Hidden text': 'Без текста'
'Small text': 'Маленький текст'
'Normal text': 'Средний текст'
'Big text': 'Большой текст'
'Colors': 'Цвета'
'Background color': 'Цвет фона'
'Background color of current app': 'Цвет фона выбранного приложения'
'Text color': 'Цвет текста'
'Loader': 'Загрузчик'
'Icon': 'Иконка'
'Same color': 'Такой же цвет'
'Opposite color': 'Противоположный цвет'
'Transparent': 'Прозрачный'
'Opaque': 'Непрозрачный'
'Opener': 'Открывалка'
'Default': 'По умолчанию'
'Default (dark)': 'По умолчанию (тёмный)'
'Hamburger': 'Гамбургер'
'Hamburger (dark)': 'Гамбургер (тёмный)'
'Hamburger 2': 'Гамбургер 2'
'Hamburger 2 (dark)': 'Гамбургер 2 (тёмный)'
'Before the logo': 'Перед логотипом'
'After the logo': 'После логотипа'
'Position': 'Положение'
'Show only the opener (hidden logo)': 'Показать только открывающую кнопку (скрытый логотип)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'Не отображать боковое меню и открывалку, если нет доступного приложения (н.п. публичные страницы).'
'Panel': 'Панель'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Открывать меню при наведении мыши на открывалку (автоматически отключается на сенсорных экранах)'
'Display the big menu': 'Отобразить большое меню'
'Display the logo': 'Показать логотип'
'Icons and texts': 'Иконки и текст'
'Loader enabled': 'Загрузчик включен'
'Tips': 'Советы'
'Always displayed': 'Всегда отображается'
'This is the automatic behavior when the menu is always displayed.': 'Это автоматическое поведение, когда меню отображается всегда.'
'Not compatible with touch screens.': 'Не совместимо с сенсорными экранами.'
'Big menu': 'Большое меню'
'Live preview': 'Предпросмотр в реальном времени'
'Open apps in new tab': 'Открывать приложения в новой вкладке'
'Use the global setting': 'Использовать глобальные настройки'
'Use my selection': 'Использовать мои настройки'
'Show and hide the list of applications': 'Показать или скрыть список приложений'
'Use the avatar instead of the logo': 'Использовать аватар вместо логотипа'
'You do not have permission to change the settings.': 'У вас нет разрешения изменять настройки.'
'Force this configuration to users': 'Для обеспечения соблюдения этих настроек пользователями'
'Export the configuration': 'Экспортировать конфигурацию'
'Purge the cache': 'Очистить кэш'
'Show the link to settings': 'Показать ссылку на настройки'
'The menu is enabled by default for users': 'Это меню включено по умолчанию для пользователей'
'Except when the configuration is forced.': 'За исключением случаев, когда настройка принудительная.'
'Apps that should not be displayed in the menu': 'Ппрограммы, скрытые из меню'
'This feature is only compatible with the <code>big menu</code> display.': 'Эта возможность совместима только с отображением <code>большого меню</code>.'
'The logo is a link to the default app': 'Логотип открывает приложение по умолчанию'
'Others': 'Прочие'
'Categories': 'Категории'
'Customize sorting': 'Настроить сортировку'
'Order by': 'В порядке'
'Name': 'Название'
'Customed': 'Пользовательское'
'Show and hide the list of categories': 'Показать или скрыть список категорий'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'Эти настройки используются темами Тёмная и Тёмная Breeze.'
'Dark mode colors': 'Цвета тёмной темы'
'With categories': 'С категориями'
'Custom categories': 'Пользовательские категории'
'Customize application categories': 'Изменить категории приложений'
'Reset to default': 'Сбросить к значениям по умолчанию'
'Applications': 'Приложения'
'Applications kept in the top menu': 'Приложения, хранящиеся в верхнем меню'
'Applications kept in the top menu but also shown in side menu': 'Приложения хранящиеся в верхнем меню, но также отображающиеся в боковом меню'
'These applications must be selected in the previous option.': 'Эти приложения необходимо выбрать в предыдущем выборе.'
'Hide labels on mouse over': 'Скрыть название при наведении мыши'
'Except the hovered app': 'Кроме приложения на котором сейчас'
'Search': 'Поиск'
'Toggle the menu': 'Переключить меню'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Спросить разработчика'
'New request': 'Новый запрос'
'Report a bug': 'Пожаловаться на ошибку'
'Show the configuration': 'Показать конфигурацию'
'Configuration:': 'Configuration:'
'Done!': 'Готово!'
'Copy': 'Копировать'
'Need help': 'Нужна помощь'
'I would like a new feature': 'Я хочу новую возможность'
'Something went wrong': 'Что-то пошло не так'
'Select apps': 'Выберете приложения'
'Sort': 'Сортировать'
'Customize': 'Приспособить'
'Custom': 'Custom'
'Close': 'Закрыть'

View file

@ -1,109 +0,0 @@
'Custom menu': 'Custom menu'
'Enable the custom menu': 'Enable the custom menu'
'No': 'No'
'Yes': 'Yes'
'Menu': 'Menu'
'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.': 'Use the shortcut Ctrl+o to open and to hide the side menu. Use tab key to navigate.'
'Top menu': 'Top menu'
'Apps that not must be moved in the side menu': 'Apps that not must be moved in the side menu'
'If there is no selection then the global configuration is applied.': 'If there is no selection then the global configuration is applied.'
'Experimental': 'Experimental'
'Save': 'Save'
'You like this app and you want to support me?': 'You like this app and you want to support me?'
'Buy me a coffee ☕': 'Buy me a coffee ☕'
'Hidden': 'Hidden'
'Small': 'Small'
'Normal': 'Normal'
'Big': 'Big'
'Hidden icon': 'Hidden icon'
'Small icon': 'Small icon'
'Normal icon': 'Normal icon'
'Big icon': 'Big icon'
'Hidden text': 'Hidden text'
'Small text': 'Small text'
'Normal text': 'Normal text'
'Big text': 'Big text'
'Colors': 'Colors'
'Background color': 'Background color'
'Background color of current app': 'Background color of current app'
'Text color': 'Text color'
'Loader': 'Loader'
'Icon': 'Icon'
'Same color': 'Same color'
'Opposite color': 'Opposite color'
'Transparent': 'Transparent'
'Opaque': 'Opaque'
'Opener': 'Opener'
'Default': 'Default'
'Default (dark)': 'Default (dark)'
'Hamburger': 'Hamburger'
'Hamburger (dark)': 'Hamburger (dark)'
'Hamburger 2': 'Hamburger 2'
'Hamburger 2 (dark)': 'Hamburger 2 (dark)'
'Before the logo': 'Before the logo'
'After the logo': 'After the logo'
'Position': 'Position'
'Show only the opener (hidden logo)': 'Show only the opener (hidden logo)'
'Do not display the side menu and the opener if there is no application (eg: public pages).': 'Do not display the side menu and the opener if there is no application (eg: public pages).'
'Panel': 'Panel'
'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)': 'Open the menu when the mouse is hover the opener (automatically disabled on touch screens)'
'Display the big menu': 'Display the big menu'
'Display the logo': 'Display the logo'
'Icons and texts': 'Icons and texts'
'Loader enabled': 'Loader enabled'
'Tips': 'Tips'
'Always displayed': 'Always displayed'
'This is the automatic behavior when the menu is always displayed.': 'This is the automatic behavior when the menu is always displayed.'
'Not compatible with touch screens.': 'Not compatible with touch screens.'
'Big menu': 'Big menu'
'Live preview': 'Live preview'
'Open apps in new tab': 'Open apps in new tab'
'Use the global setting': 'Use the global setting'
'Use my selection': 'Use my selection'
'Show and hide the list of applications': 'Show and hide the list of applications'
'Use the avatar instead of the logo': 'Use the avatar instead of the logo'
'You do not have permission to change the settings.': 'You do not have permission to change the settings.'
'Force this configuration to users': 'Force this configuration to users'
'Export the configuration': 'Export the configuration'
'Purge the cache': 'Purge the cache'
'Show the link to settings': 'Show the link to settings'
'The menu is enabled by default for users': 'The menu is enabled by default for users'
'Except when the configuration is forced.': 'Except when the configuration is forced.'
'Apps that should not be displayed in the menu': 'Apps that should not be displayed in the menu'
'This feature is only compatible with the <code>big menu</code> display.': 'This feature is only compatible with the <code>big menu</code> display.'
'The logo is a link to the default app': 'The logo is a link to the default app'
'Others': 'Others'
'Categories': 'Categories'
'Customize sorting': 'Customize sorting'
'Order by': 'Order by'
'Name': 'Name'
'Customed': 'Customed'
'Show and hide the list of categories': 'Show and hide the list of categories'
'This parameters are used when Dark theme or Breeze Dark Theme are enabled.': 'This parameters are used when Dark theme or Breeze Dark Theme are enabled.'
'Dark mode colors': 'Dark mode colors'
'With categories': 'With categories'
'Custom categories': 'Custom categories'
'Customize application categories': 'Customize application categories'
'Reset to default': 'Reset to default'
'Applications': 'Applications'
'Applications kept in the top menu': 'Applications kept in the top menu'
'Applications kept in the top menu but also shown in side menu': 'Applications kept in the top menu but also shown in side menu'
'These applications must be selected in the previous option.': 'These applications must be selected in the previous option.'
'Hide labels on mouse over': 'Hide labels on mouse over'
'Toggle the menu': 'Prepnite ponuku'
'Open the documentation': 'Open the documentation'
'Ask the developer': 'Ask the developer'
'New request': 'New request'
'Report a bug': 'Report a bug'
'Show the configuration': 'Show the configuration'
'Configuration:': 'Configuration:'
'Done!': 'Done!'
'Copy': 'Copy'
'Need help': 'Need help'
'I would like a new feature': 'I would like a new feature'
'Something went wrong': 'Something went wrong'
'Select apps': 'Select apps'
'Sort': 'Sort'
'Customize': 'Customize'
'Custom': 'Custom'
'Close': 'Close'

Some files were not shown because too many files have changed in this diff Show more