diff --git a/.gitea/issue_template/ISSUE_TEMPLATE.yml b/.gitea/issue_template/ISSUE_TEMPLATE.yml index 96e6c28..8486a5f 100644 --- a/.gitea/issue_template/ISSUE_TEMPLATE.yml +++ b/.gitea/issue_template/ISSUE_TEMPLATE.yml @@ -26,7 +26,7 @@ body: 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/tips/#export-the-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: | ``` { diff --git a/.gitea/issue_template/QUESTION_TEMPLATE.yml b/.gitea/issue_template/QUESTION_TEMPLATE.yml new file mode 100644 index 0000000..41301ae --- /dev/null +++ b/.gitea/issue_template/QUESTION_TEMPLATE.yml @@ -0,0 +1,30 @@ +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 diff --git a/.gitea/issue_template/config.yml b/.gitea/issue_template/config.yml new file mode 100644 index 0000000..d065f39 --- /dev/null +++ b/.gitea/issue_template/config.yml @@ -0,0 +1,8 @@ +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 diff --git a/.woodpecker.yml b/.woodpecker.yml deleted file mode 100644 index 61563c0..0000000 --- a/.woodpecker.yml +++ /dev/null @@ -1,103 +0,0 @@ -steps: - "Verify tag and app version": - image: alpine - commands: - - TAG=${CI_COMMIT_TAG/v//} - - grep "$TAG" appinfo/info.xml - when: - event: [tag] - - "Install dependencies": - image: node:16 - pull: true - commands: - - npm i - when: - event: [tag, push, pull_request, manual] - branch: [master, develop, feature/*, fix/*, bugfix/*, translations] - - "Check dependencies": - image: gitnet.fr/deblan/osv-detector:v0.10 - commands: - - osv-detector package-lock.json - failure: ignore - - "Build JS": - image: node:16 - commands: - - npm run build - when: - event: [tag, push, pull_request, manual] - branch: [master, develop, feature/*, fix/*, bugfix/*, translations] - - "Build translations": - image: deblan/php:8.0 - commands: - - php bin/generate_l10n.php - when: - event: [tag, push, pull_request, manual] - branch: [master, develop, feature/*, fix/*, bugfix/*, translations] - - "Create signature": - image: nextcloud:25 - secrets: [app_certificate, app_public_certificate] - environment: - SQLITE_DATABASE: /var/www/data/data.db - NEXTCLOUD_ADMIN_USER: admin - NEXTCLOUD_ADMIN_PASSWORD: admin - commands: - - 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 css 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/ - when: - event: [tag] - - # check-code-quality: - # image: sonarsource/sonar-scanner-cli - # secrets: [sonar_token, sonar_host, sonar_project] - # commands: - # - sonar-scanner - # -Dsonar.projectKey=$SONAR_PROJECT - # -Dsonar.sources=. - # -Dsonar.host.url=$SONAR_HOST - # -Dsonar.pullrequest.key=$CI_COMMIT_PULL_REQUEST - # -Dsonar.pullrequest.branch=$CI_COMMIT_SOURCE_BRANCH - # -Dsonar.pullrequest.base=$CI_COMMIT_TARGET_BRANCH - # failure: ignore - # when: - # event: [pull_request] - - "Create package": - image: deblan/php:8.0 - volumes: - - /var/www/html/artifacts:/var/www/html/artifacts - secrets: [app_certificate] - commands: - - 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 "" 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 - when: - event: [tag] - - "Push release": - image: plugins/gitea-release - volumes: - - /var/www/html/artifacts:/var/www/html/artifacts - 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//}/* - when: - event: [tag] diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml new file mode 100644 index 0000000..38526c5 --- /dev/null +++ b/.woodpecker/.build.yml @@ -0,0 +1,22 @@ +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 diff --git a/.woodpecker/.publish.yml b/.woodpecker/.publish.yml new file mode 100644 index 0000000..d369dcf --- /dev/null +++ b/.woodpecker/.publish.yml @@ -0,0 +1,66 @@ +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 "$TAG" 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 css 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 "" 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//}/* diff --git a/.woodpecker/.security.yml b/.woodpecker/.security.yml new file mode 100644 index 0000000..50612c6 --- /dev/null +++ b/.woodpecker/.security.yml @@ -0,0 +1,17 @@ +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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b18851..7c39226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ ## [Unreleased] +## 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 diff --git a/appinfo/info.xml b/appinfo/info.xml index c293e56..beb19ab 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -1,6 +1,5 @@ - - + + side_menu Custom menu Modify the display of the menu. @@ -17,7 +16,7 @@ You can report a bug or request a feature by opening an issue. Requirements: -* PHP >= 8.0 +* PHP >= 8.1 * App `theming` enabled If you like this application and if you want to support the development: @@ -32,9 +31,9 @@ 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/). ]]> - 3.11.7 + 4.1.0 agpl - Simon Vieille + Simon Vieille SideMenu https://deblan.gitnet.page/side_menu_doc/ @@ -42,20 +41,20 @@ In case of downtime, you can download **Custom Menu** from [here](https://kim.de customization https://gitnet.fr/deblan/side_menu - https://matrix.to/#/!TFPucDATKODpHNVAtu:neutralnetwork.org?via=neutralnetwork.org + https://gitnet.fr/deblan/side_menu/issues https://gitnet.fr/deblan/side_menu - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/admin_settings.png - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/n19_big_menu.png - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc18_menu_always_displayed.png - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_big_menu.png - https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_default_menu.png + + + + + + + + - - + + OCA\SideMenu\Settings\Admin diff --git a/appinfo/routes.php b/appinfo/routes.php deleted file mode 100644 index bdc3fba..0000000 --- a/appinfo/routes.php +++ /dev/null @@ -1,31 +0,0 @@ -. - */ - -return [ - 'routes' => [ - ['name' => 'App#index', 'url' => '/', 'verb' => 'GET'], - ['name' => 'Css#stylesheet', 'url' => '/css/stylesheet', 'verb' => 'GET'], - ['name' => 'Js#script', 'url' => '/js/script', 'verb' => 'GET'], - ['name' => 'Js#config', 'url' => '/js/config', 'verb' => 'GET'], - ['name' => 'Nav#items', 'url' => '/nav/items', 'verb' => 'GET'], - ['name' => 'PersonalSetting#valueSet', 'url' => '/personalSetting/valueSet', 'verb' => 'POST'], - ['name' => 'AdminSetting#removeCache', 'url' => '/admin/cache/remove', 'verb' => 'GET'], - ['name' => 'AdminSetting#exportConfiguration', 'url' => '/admin/config/export', 'verb' => 'GET'], - ], -]; diff --git a/bin/import_config.php b/bin/import_config.php index f2fbd14..db5857d 100644 --- a/bin/import_config.php +++ b/bin/import_config.php @@ -1,21 +1,75 @@ prepare('UPDATE oc_appconfig SET configvalue=:value WHERE configkey=:key and appid=:appId'); + exit($code); +} -foreach ($config as $key => $value) { +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, diff --git a/css/sideMenu.css b/css/sideMenu.css index 8a63202..66a1a8c 100644 --- a/css/sideMenu.css +++ b/css/sideMenu.css @@ -89,6 +89,10 @@ .side-menu-opener span { position: relative; left: 50px; + display: block; + width: 1px; + height: 1px; + overflow: hidden; } .side-menu-opener:active, .side-menu-opener:focus { @@ -221,6 +225,11 @@ .side-menu-category-title { padding-left: 10px; color: var(--side-menu-text-color, #fff); + font-weight: bold; + font-size: 20px; + margin-bottom: 12px; + line-height: 30px; + margin-top: 0; } .side-menu-loader { @@ -293,8 +302,9 @@ display: inline; } -.side-menu-always-displayed .app-navigation--close { - transform: translateX(calc(-100% + 50px)); +.side-menu-always-displayed .app-navigation-toggle-wrapper { + right: 0 !important; + margin-left: 0 !important; } #side-menu.side-menu-with-categories { diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index b812fd7..306d0ea 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -3,6 +3,7 @@ namespace OCA\SideMenu\AppInfo; use OC; +use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\User\User; use OCA\SideMenu\Service\AppRepository; @@ -12,7 +13,11 @@ 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; @@ -26,6 +31,7 @@ class Application extends App implements IBootstrap public const APP_ID = 'side_menu'; public const APP_NAME = 'Custom menu'; + /** * @var OC\AllConfig */ @@ -41,14 +47,55 @@ class Application extends App implements IBootstrap */ protected $user; - /** - * {@inheritdoc} - */ public function __construct(array $urlParams = []) { parent::__construct(self::APP_ID, $urlParams); } + public function register(IRegistrationContext $context): void + { + $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), + ); + }); + } + + public function boot(IBootContext $context): void + { + $this->config = \OC::$server->getConfig(); + $this->cspnm = \OC::$server->getContentSecurityPolicyNonceManager(); + $this->user = \OC::$server[IUserSession::class]->getUser(); + + if (!$this->isEnabled()) { + return; + } + + $this->addAssets(); + } + protected function isEnabled(): bool { $enabled = true; @@ -97,38 +144,10 @@ class Application extends App implements IBootstrap $cache = $this->config->getAppValue(self::APP_ID, 'cache', '0'); foreach ($assets as $value) { - $route = OC::$server->getURLGenerator()->linkToRoute($value['route'], ['v' => $cache]); + $route = \OC::$server->getURLGenerator()->linkToRoute($value['route'], ['v' => $cache]); $value['attr'][$value['route_attr']] = $route; Util::addHeader($value['type'], $value['attr'], ''); } } - - public function register(IRegistrationContext $context): void - { - $context->registerService('AppRepository', function () { - return new AppRepository(); - }); - - $context->registerService('CategoryRepository', function () { - return new CategoryRepository(); - }); - - $context->registerService('ConfigProxy', function () { - return new ConfigProxy(); - }); - } - - public function boot(IBootContext $context): void - { - $this->config = OC::$server->getConfig(); - $this->cspnm = OC::$server->getContentSecurityPolicyNonceManager(); - $this->user = OC::$server[IUserSession::class]->getUser(); - - if (!$this->isEnabled()) { - return; - } - - $this->addAssets(); - } } diff --git a/lib/Controller/AdminSettingController.php b/lib/Controller/AdminSettingController.php index aa11d9a..279a34c 100644 --- a/lib/Controller/AdminSettingController.php +++ b/lib/Controller/AdminSettingController.php @@ -1,4 +1,5 @@ config = $config; - $this->urlGenerator = $urlGenerator; } - /** - * @NoCSRFRequired - * - * @return RedirectResponse - */ - public function removeCache() + #[NoCSRFRequired] + #[FrontpageRoute(verb: 'GET', url: '/admin/cache/remove')] + public function removeCache(): RedirectResponse { $this->config->setAppValue(Application::APP_ID, 'cache-categories', '[]'); @@ -61,12 +51,9 @@ class AdminSettingController extends Controller ]).'#more'); } - /** - * @NoCSRFRequired - * - * @return Response - */ - public function exportConfiguration() + #[NoCSRFRequired] + #[FrontpageRoute(verb: 'GET', url: '/admin/config/export')] + public function exportConfiguration(): DataDownloadResponse { $keys = $this->config->getAppKeys(Application::APP_ID); $appConfig = []; diff --git a/lib/Controller/AppController.php b/lib/Controller/AppController.php index 588911f..f8cb357 100644 --- a/lib/Controller/AppController.php +++ b/lib/Controller/AppController.php @@ -1,4 +1,5 @@ appRepository = $appRepository; - $this->urlGenerator = $urlGenerator; - $this->config = $config; } - /** - * @NoAdminRequired - * @NoCSRFRequired - */ + #[NoCSRFRequired] + #[NoAdminRequired] + #[FrontpageRoute(verb: 'GET', url: '/')] public function index(): RedirectResponse { - $user = OC::$server[IUserSession::class]->getUser(); + $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'); @@ -87,7 +75,7 @@ class AppController extends Controller protected function redirectToApp($app, bool $isHref = false): RedirectResponse { if (!$isHref) { - $isIgnoreFrontController = true === OC::$server->getConfig()->getSystemValue( + $isIgnoreFrontController = true === \OC::$server->getConfig()->getSystemValue( 'htaccess.IgnoreFrontController', false ); diff --git a/lib/Controller/CssController.php b/lib/Controller/CssController.php index 06f7ae8..e2c9e27 100644 --- a/lib/Controller/CssController.php +++ b/lib/Controller/CssController.php @@ -1,4 +1,5 @@ user = OC::$server[IUserSession::class]->getUser(); - $this->config = $config; - $this->theming = $theming; - $this->color = $color; + $this->user = \OC::$server[IUserSession::class]->getUser(); } - /** - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - * - * @return Response - */ - public function stylesheet() + #[NoCSRFRequired] + #[NoAdminRequired] + #[PublicPage] + #[FrontpageRoute(verb: 'GET', url: '/css/stylesheet')] + public function stylesheet(): TemplateResponse { $response = new TemplateResponse(Application::APP_ID, 'css/stylesheet', $this->getConfig(), 'blank'); $response->addHeader('Content-Type', 'text/css'); @@ -107,15 +84,15 @@ class CssController extends Controller $isDarkThemeUserEnabled = 'dark' === $this->config->getUserValue($this->user, 'theme', '', 'accessibility'); $isBreezeDarkUserEnabled = $this->config->getUserValue($this->user, 'theme_enabled', '', 'breezedark'); - $isBreezeDarkUserEnabled = '1' === $isBreezeDarkUserEnabled || - ($isBreezeDarkGlobalEnabled && '' === $isBreezeDarkUserEnabled); + $isBreezeDarkUserEnabled = '1' === $isBreezeDarkUserEnabled + || ($isBreezeDarkGlobalEnabled && '' === $isBreezeDarkUserEnabled); } else { $isDarkThemeUserEnabled = false; $isBreezeDarkUserEnabled = false; } - $isDarkMode = ($isAccessibilityAppEnabled && $isDarkThemeUserEnabled) || - ($isBreezeDarkAppEnabled && $isBreezeDarkUserEnabled); + $isDarkMode = ($isAccessibilityAppEnabled && $isDarkThemeUserEnabled) + || ($isBreezeDarkAppEnabled && $isBreezeDarkUserEnabled); $primaryColor = $this->theming->getColorPrimary(); $lightenPrimaryColor = $this->color->adjustBrightness($primaryColor, 0.2); diff --git a/lib/Controller/JsController.php b/lib/Controller/JsController.php index 13d803e..8f06345 100644 --- a/lib/Controller/JsController.php +++ b/lib/Controller/JsController.php @@ -1,4 +1,5 @@ themingDefaults = $themingDefaults; - $this->user = OC::$server[IUserSession::class]->getUser(); + $this->user = \OC::$server[IUserSession::class]->getUser(); $this->config = $config; $this->l10nFactory = $l10nFactory; } - /** - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - */ + #[NoCSRFRequired] + #[NoAdminRequired] + #[PublicPage] + #[FrontpageRoute(verb: 'GET', url: '/js/script')] public function script(): TemplateResponse { $response = new TemplateResponse(Application::APP_ID, 'js/script', $this->getConfig(), 'blank'); @@ -81,11 +66,10 @@ class JsController extends Controller return $response; } - /** - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - */ + #[NoCSRFRequired] + #[NoAdminRequired] + #[PublicPage] + #[FrontpageRoute(verb: 'GET', url: '/js/config')] public function config(): JSONResponse { return new JSONResponse($this->getConfig()); @@ -127,10 +111,10 @@ class JsController extends Controller $targetBlankApps = $userTargetBlankApps; } - $isAvatarSet = OC::$server->getAvatarManager()->getAvatar($this->user->getUid())->exists(); + $isAvatarSet = \OC::$server->getAvatarManager()->getAvatar($this->user->getUid())->exists(); if ($useAvatar && $isAvatarSet) { - $avatar = OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [ + $avatar = \OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [ 'userId' => $this->user->getUid(), 'size' => 128, 'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0), @@ -138,13 +122,13 @@ class JsController extends Controller } if ($this->config->getAppValueBool('show-settings', '0')) { - $settingsNav = OC::$server->getNavigationManager()->getAll('settings'); + $settingsNav = \OC::$server->getNavigationManager()->getAll('settings'); if (isset($settingsNav['settings'])) { $settings = [ 'href' => $settingsNav['settings']['href'], 'name' => $settingsNav['settings']['name'], - 'avatar' => OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [ + 'avatar' => \OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [ 'userId' => $this->user->getUid(), 'size' => 32, 'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0), @@ -154,7 +138,7 @@ class JsController extends Controller } } - $indexUrl = OC::$server->getURLGenerator()->linkTo('', 'index.php'); + $indexUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php'); return [ 'opener-position' => $this->config->getAppValue('opener-position', 'before'), diff --git a/lib/Controller/NavController.php b/lib/Controller/NavController.php index 50efc4c..1d90849 100644 --- a/lib/Controller/NavController.php +++ b/lib/Controller/NavController.php @@ -1,4 +1,5 @@ config = $config; - $this->appRepository = $appRepository; - $this->categoryRepository = $categoryRepository; - $this->l10nFactory = $l10nFactory; - $this->router = $router; } - /** - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - * - * @return JSONResponse - */ - public function items() + #[NoCSRFRequired] + #[NoAdminRequired] + #[PublicPage] + #[FrontpageRoute(verb: 'GET', url: '/nav/items')] + public function items(): JSONResponse { - $user = OC::$server[IUserSession::class]->getUser(); + $user = \OC::$server[IUserSession::class]->getUser(); $items = []; if (!$user) { @@ -189,11 +158,11 @@ class NavController extends Controller usort($items, function ($a, $b) use ($categoriesLabels) { foreach ($categoriesLabels as $key => $value) { - if ($a['categoryId'] === 'other') { + if ('other' === $a['categoryId']) { return -1; } - if ($b['categoryId'] === 'other') { + if ('other' === $b['categoryId']) { return 1; } diff --git a/lib/Controller/PersonalSettingController.php b/lib/Controller/PersonalSettingController.php index fc30c35..482a7c6 100644 --- a/lib/Controller/PersonalSettingController.php +++ b/lib/Controller/PersonalSettingController.php @@ -1,4 +1,5 @@ config = $config; - $this->configProxy = $configProxy; - $this->userSession = $userSession; } - /** - * @NoAdminRequired - * @NoCSRFRequired - * - * @param mixed $name - * @param mixed $value - * - * @return Response - */ - public function valueSet($name, $value) + #[NoCSRFRequired] + #[NoAdminRequired] + #[FrontpageRoute(verb: 'POST', url: '/personalSetting/valueSet')] + public function valueSet($name, $value): array { $doSave = false; $user = $this->userSession->getUser(); diff --git a/lib/Service/AppRepository.php b/lib/Service/AppRepository.php index ec10a36..3c64f7d 100644 --- a/lib/Service/AppRepository.php +++ b/lib/Service/AppRepository.php @@ -3,7 +3,12 @@ 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 OCP\L10N\IFactory; /** @@ -13,51 +18,25 @@ use OCP\L10N\IFactory; */ class AppRepository { - /** - * @var \OC_App - */ - protected $ocApp; - - /** - * @var IFactory - */ - protected $l10nFactory; - - /** - * @var ConfigProxy - */ - protected $config; - - /** - * @var CategoryRepository - */ - protected $categoryRepository; - - /** - * @var INavigationManager - */ - protected $navigationManager; - public function __construct( - \OC_App $ocApp, - INavigationManager $navigationManager, - IFactory $l10nFactory, - ConfigProxy $config, - CategoryRepository $categoryRepository + protected \OC_App $ocApp, + protected INavigationManager $navigationManager, + protected IFactory $l10nFactory, + protected ConfigProxy $config, + protected CategoryRepository $categoryRepository, + protected IEventDispatcher $dispatcher, + protected IUserSession $userSession, ) { - $this->ocApp = $ocApp; - $this->l10nFactory = $l10nFactory; - $this->config = $config; - $this->navigationManager = $navigationManager; - $this->categoryRepository = $categoryRepository; + $this->dispatcher->dispatchTyped(new BeforeTemplateRenderedEvent( + $this->userSession->isLoggedIn(), + new TemplateResponse(Application::APP_NAME, '') + )); } /** * Retrieves visibles apps. - * - * @return array */ - public function getVisibleApps() + public function getVisibleApps(): array { $navigation = $this->navigationManager->getAll(); $appCategoriesCustom = $this->config->getAppValueArray('apps-categories-custom', '[]'); @@ -90,6 +69,14 @@ class AppRepository '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'], @@ -110,7 +97,7 @@ class AppRepository return $visibleApps; } - public function getAppName($app) + public function getAppName($app): string { return $this->config->getAppValue( 'app.navigation.name', @@ -119,7 +106,7 @@ class AppRepository ); } - public function getOrderedApps(?User $user = null) + public function getOrderedApps(?User $user = null): array { $apps = $this->getVisibleApps(); $orders = $this->config->getAppValueArray('apps-order', '[]'); diff --git a/lib/Service/CategoryRepository.php b/lib/Service/CategoryRepository.php index c146609..81e44e2 100644 --- a/lib/Service/CategoryRepository.php +++ b/lib/Service/CategoryRepository.php @@ -15,51 +15,18 @@ use OCP\L10N\IFactory; */ class CategoryRepository { - /** - * @var CategoryFetcher - */ - protected $categoryFetcher; - - /** - * @var IFactory - */ - protected $l10nFactory; - - /** - * @var ConfigProxy - */ - protected $config; - - /** - * @var IConfig - */ - protected $iConfig; - - /** - * @var IUserSession - */ - protected $userSession; - public function __construct( - CategoryFetcher $categoryFetcher, - ConfigProxy $config, - IConfig $iConfig, - IFactory $l10nFactory, - IUserSession $userSession - ) { - $this->categoryFetcher = $categoryFetcher; - $this->l10nFactory = $l10nFactory; - $this->config = $config; - $this->iConfig = $iConfig; - $this->userSession = $userSession; - } + protected CategoryFetcher $categoryFetcher, + protected ConfigProxy $config, + protected IConfig $iConfig, + protected IFactory $l10nFactory, + protected IUserSession $userSession + ) {} /** * Retrieves categories. - * - * @return array */ - public function getOrderedCategories() + public function getOrderedCategories(): array { $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); $type = $this->config->getAppValue('categories-order-type', 'default'); diff --git a/lib/Service/ConfigProxy.php b/lib/Service/ConfigProxy.php index d162dfd..6745851 100644 --- a/lib/Service/ConfigProxy.php +++ b/lib/Service/ConfigProxy.php @@ -13,12 +13,7 @@ use OCP\IConfig; */ class ConfigProxy { - /** - * @var IConfig - */ - protected $config; - - public function __construct(IConfig $config) + public function __construct(protected IConfig $config) { $this->config = $config; } diff --git a/lib/Service/LangRepository.php b/lib/Service/LangRepository.php index 3c379f0..1cde40a 100644 --- a/lib/Service/LangRepository.php +++ b/lib/Service/LangRepository.php @@ -11,12 +11,7 @@ use OCP\IDBConnection; */ class LangRepository { - /** - * @var IDBConnection - */ - protected $db; - - public function __construct(IDBConnection $db) + public function __construct(protected IDBConnection $db) { $this->db = $db; } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 86ddac4..7796f72 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -1,4 +1,5 @@ l = $l; - $this->logger = $logger; - $this->config = $config; - $this->appRepository = $appRepository; - $this->categoryRepository = $categoryRepository; - $this->theming = $theming; - $this->color = $color; - $this->langRepository = $langRepository; - } + protected IL10N $l, + protected ConfigProxy $config, + protected AppRepository $appRepository, + protected CategoryRepository $categoryRepository, + protected ThemingDefaults $theming, + protected Color $color, + protected LangRepository $langRepository + ) {} /** * @return TemplateResponse diff --git a/lib/Settings/AdminSection.php b/lib/Settings/AdminSection.php index 8629e8a..67c8fcc 100644 --- a/lib/Settings/AdminSection.php +++ b/lib/Settings/AdminSection.php @@ -1,4 +1,5 @@ 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; } - /** - * 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 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'); diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php index 57112d0..2aebec8 100644 --- a/lib/Settings/Personal.php +++ b/lib/Settings/Personal.php @@ -1,4 +1,5 @@ l = $l; - $this->logger = $logger; - $this->config = $config; - $this->userSession = $userSession; - $this->appRepository = $appRepository; - } + protected IL10N $l, + protected ConfigProxy $config, + protected IUserSession $userSession, + protected AppRepository $appRepository + ) {} /** * @return TemplateResponse diff --git a/lib/Settings/PersonalSection.php b/lib/Settings/PersonalSection.php index 58a773f..efe180c 100644 --- a/lib/Settings/PersonalSection.php +++ b/lib/Settings/PersonalSection.php @@ -1,4 +1,5 @@ url = $url; - $this->l = $l; - $this->configProxy = $configProxy; - } - - /** - * 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')) { @@ -63,12 +42,6 @@ class PersonalSection implements IIconSection return Application::APP_ID; } - /** - * 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')) { @@ -78,13 +51,6 @@ class PersonalSection implements IIconSection return $this->l->t(Application::APP_NAME); } - /** - * @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')) { @@ -94,9 +60,6 @@ class PersonalSection implements IIconSection return 70; } - /** - * {@inheritdoc} - */ public function getIcon() { return $this->url->imagePath(Application::APP_ID, 'icon.svg'); diff --git a/package.json b/package.json index 9298a83..f002d50 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,15 @@ "stylelint:fix": "./node_modules/.bin/stylelint src --fix" }, "dependencies": { + "@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.1.0", + "@nextcloud/vue": "^8.19.0", + "@vueuse/core": "^11.1.0", "axios": "^1.6.7", - "trim": "^1.0.1", - "vue": "^2.6.11" + "trim": "^1.0.1" }, "browserslist": [ "extends @nextcloud/browserslist-config" @@ -22,42 +28,46 @@ "node": ">=16.0.0" }, "devDependencies": { - "@babel/core": "^7.9.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/preset-env": "^7.9.0", - "@nextcloud/axios": "^2.3.0", - "@nextcloud/browserslist-config": "^2.3.0", - "@nextcloud/eslint-config": "^8.1.2", - "@nextcloud/initial-state": "^2.0.0", - "@nextcloud/l10n": "^2.1.0", - "@nextcloud/vue": "^7.12.1", - "babel-eslint": "^10.1.0", - "babel-loader": "^8.1.0", - "css-loader": "^6.10.0", - "eslint": "^8.0.0", - "eslint-config-standard": "^17.0.0", - "eslint-import-resolver-webpack": "^0.12.1", - "eslint-plugin-import": "^2.20.0", - "eslint-plugin-nextcloud": "^0.3.0", - "eslint-plugin-node": "^10.0.0", - "eslint-plugin-promise": "^6.0.0", - "eslint-plugin-standard": "^4.0.1", - "eslint-plugin-vue": "^9.0.0", - "eslint-webpack-plugin": "^3.0.0", - "file-loader": "^6.0.0", - "sass": "^1.49.9", - "sass-loader": "^13.0.2", - "stylelint": "^14.0.0", - "stylelint-config-recommended-scss": "^7.0.0", - "stylelint-scss": "^4.0.0", - "stylelint-webpack-plugin": "^3.3.0", - "url-loader": "^4.0.0", - "vue-loader": "^15", - "vue-style-loader": "^4.1.3", - "vue-template-compiler": "^2.7.13", - "webpack": "^5.0.0", - "webpack-cli": "^4.0.0", - "webpack-merge": "^4.2.2", - "webpack-node-externals": "^1.7.2" + "@babel/node": "^7.25.7", + "@babel/plugin-transform-private-methods": "^7.25.7", + "@babel/preset-typescript": "^7.24.7", + "@cypress/vue2": "^2.1.1", + "@cypress/webpack-preprocessor": "^6.0.2", + "@nextcloud/babel-config": "^1.2.0", + "@nextcloud/eslint-config": "^8.4.1", + "@nextcloud/stylelint-config": "^3.0.1", + "@nextcloud/typings": "^1.9.1", + "@nextcloud/webpack-vue-config": "^6.0.1", + "@simplewebauthn/types": "^10.0.0", + "@types/dockerode": "^3.3.29", + "@types/wait-on": "^5.3.4", + "@vue/tsconfig": "^0.5.1", + "babel-loader": "^9.2.1", + "babel-loader-exclude-node-modules-except": "^1.2.1", + "babel-plugin-module-resolver": "^5.0.2", + "colord": "^2.9.3", + "eslint-plugin-cypress": "^3.5.0", + "eslint-plugin-es": "^4.1.0", + "exports-loader": "^5.0.0", + "file-loader": "^6.2.0", + "handlebars-loader": "^1.7.3", + "jasmine-core": "~2.5.2", + "jasmine-sinon": "^0.4.0", + "jsdoc": "^4.0.2", + "raw-loader": "^4.0.2", + "sass": "^1.79.3", + "stylelint": "^16.9.0", + "stylelint-use-logical": "^2.1.2", + "ts-loader": "^9.5.0", + "ts-node": "^10.9.1", + "tslib": "^2.7.0", + "typescript": "^5.6.2", + "vue-loader": "^15.9.8", + "vue-template-compiler": "^2.7.16", + "wait-on": "^8.0.1", + "webpack": "^5.94.0", + "webpack-cli": "^5.0.2", + "webpack-merge": "^6.0.1", + "workbox-webpack-plugin": "^7.1.0" } } diff --git a/src/AdminCategoriesCustom.vue b/src/AdminCategoriesCustom.vue index 2a84749..21de363 100644 --- a/src/AdminCategoriesCustom.vue +++ b/src/AdminCategoriesCustom.vue @@ -84,9 +84,9 @@ along with this program. If not, see .