Compare commits

..

64 Commits

Author SHA1 Message Date
Simon Vieille 077191cf47 Merge pull request 'develop' (#508) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/508
2026-07-18 16:29:20 +02:00
Simon Vieille 746585b1c7 Merge pull request 'develop' (#504) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/504
2026-07-17 18:59:27 +02:00
Simon Vieille f2a9a11c08 Merge pull request 'develop' (#501) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/501
2026-07-17 18:45:24 +02:00
Simon Vieille f8b36938a4 Merge pull request 'develop' (#491) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/491
2026-04-05 09:25:37 +02:00
Simon Vieille 479cac1f90 Merge pull request 'develop' (#481) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/481
2026-03-08 19:10:53 +01:00
Simon Vieille f17d088b81 Merge pull request 'develop' (#480) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/480
2026-03-08 19:07:17 +01:00
Simon Vieille f59e62bf0b Merge pull request 'develop' (#477) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/477
2026-03-01 13:18:57 +01:00
Simon Vieille b9f204b545 Merge pull request 'develop' (#474) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/474
2026-02-27 09:18:35 +01:00
Simon Vieille bed8d8ff21 Merge pull request 'develop' (#472) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/472
2026-02-27 09:17:06 +01:00
Simon Vieille 2e5404464e Merge pull request 'develop' (#457) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/457
2025-10-29 17:48:52 +01:00
Simon Vieille 28e08e51a3 Merge pull request 'develop' (#444) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/444
2025-09-28 19:50:12 +02:00
Simon Vieille 2b84ed37e8 Merge pull request 'release 5.1.1' (#433) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/433
2025-05-23 09:15:12 +02:00
Simon Vieille a4e459d8ed Merge pull request 'release v5.1.0' (#428) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/428
2025-04-30 13:01:55 +02:00
Simon Vieille 9fea794e2f Merge pull request 'release v5.0.3' (#423) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/423
2025-04-30 08:42:11 +02:00
Simon Vieille 55967ff81e Merge pull request 'release v5.0.2' (#420) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/420
2025-04-29 19:29:38 +02:00
Simon Vieille 12604b44fc Merge pull request 'fix(StandardMenu): appLimit must return a value > 0' (#412) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/412
2025-04-17 00:05:38 +02:00
Simon Vieille b1e19ac965 Merge pull request 'fix(build): remove css directory' (#408) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/408
2025-04-16 20:10:25 +02:00
Simon Vieille ea83cc050c Merge pull request 'fix(build): remove css directory' (#407) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/407
2025-04-16 20:04:19 +02:00
Simon Vieille 36750532f6 Merge pull request 'release v5.0.0' (#406) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/406
2025-04-16 19:56:55 +02:00
Simon Vieille cf9495de46 Merge pull request 'v4.1.1' (#400) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/400
2025-03-12 18:41:47 +01:00
Simon Vieille 076435dc8e Merge pull request 'update changelog' (#396) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/396
2025-03-10 19:17:35 +01:00
Simon Vieille fe5f0287c3 Merge pull request 'release v4.1.0' (#395) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/395
2025-03-10 19:15:37 +01:00
Simon Vieille eff18bb143 Merge pull request 'v4.0.1' (#372) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/372
2024-10-30 19:08:49 +01:00
Simon Vieille 29826d3c54 Merge pull request 'develop' (#364) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/364
2024-10-27 18:53:20 +01:00
Simon Vieille 695934c28b Merge pull request 'v4.0.0' (#363) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/363
2024-10-27 17:20:18 +01:00
Simon Vieille aa2246a1ec Merge pull request 'release v3.13.1' (#356) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/356
2024-07-10 19:58:09 +02:00
Simon Vieille da04cf4222 Merge pull request 'release v3.13.0' (#353) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/353
2024-07-05 13:02:19 +02:00
Simon Vieille 7b33271d00 Merge pull request 'ci: rollback tag check' (#346) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/346
2024-04-08 14:33:50 +02:00
Simon Vieille e4e054835d Merge pull request 'ci: fix signature step (volumes)' (#345) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/345
2024-04-08 14:14:59 +02:00
Simon Vieille eae8ce80fb Merge pull request 'release v3.12.0' (#344) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/344
2024-04-08 11:59:32 +02:00
Simon Vieille 972c69d880 Merge pull request 'release v3.11.8' (#336) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/336
2024-02-20 21:26:26 +01:00
Simon Vieille dd8561e691 Merge pull request 'release v3.11.7' (#331) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/331
2024-02-14 19:10:56 +01:00
Simon Vieille 61a4689eb2 Merge pull request 'release v3.11.6' (#328) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/328
2024-02-12 23:22:28 +01:00
Simon Vieille 4532dd345e Merge pull request 'update app info' (#317) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/317
2024-01-28 23:07:31 +01:00
Simon Vieille 6effdc6eed Merge pull request 'release 3.11.5' (#316) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/316
2024-01-28 23:06:25 +01:00
Simon Vieille 810d83880b Merge pull request 'release v3.11.4' (#313) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/313
2024-01-27 10:47:53 +01:00
Simon Vieille 25e70f276f Merge pull request 'fix appinfo version' (#308) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/308
2024-01-16 20:40:28 +01:00
Simon Vieille d06b6b9e31 Merge pull request 'release 3.11.3' (#307) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/307
2024-01-16 20:14:53 +01:00
Simon Vieille 5c218bff2a Merge pull request 'releave v3.11.2' (#300) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/300
2023-11-12 20:30:56 +01:00
Simon Vieille b0385c172b Merge pull request 'release v3.11.1' (#297) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/297
2023-11-12 17:03:21 +01:00
Simon Vieille 04ac7e5ed1 Merge pull request 'update changelogs' (#293) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/293
2023-11-05 18:46:46 +01:00
Simon Vieille 67743485cb Merge pull request 'release v3.11.0' (#292) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/292
2023-11-05 18:45:25 +01:00
Simon Vieille 0ce318d6ec Merge pull request 'fix ci syntax' (#281) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/281
2023-09-29 16:48:36 +02:00
Simon Vieille c26c5f26df Merge pull request 'release v3.10.3' (#276) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/276
2023-07-24 21:35:58 +02:00
Simon Vieille 6ce7d3223f Merge pull request 'release v3.10.2' (#272) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/272
2023-07-15 15:22:53 +02:00
Simon Vieille 6eb977dad5 Merge pull request 'release v3.10.1' (#271) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/271
2023-07-15 15:03:26 +02:00
Simon Vieille 5f307cd046 Merge pull request 'release v3.10.0' (#268) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/268
2023-07-13 22:30:27 +02:00
Simon Vieille d2421f90bf Merge pull request 'release v3.9.1' (#266) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/266
2023-06-29 13:04:13 +02:00
Simon Vieille b16da36507 Merge pull request 'release v3.9.0' (#263) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/263
2023-06-23 12:33:54 +02:00
Simon Vieille 29f0ead9cc Merge pull request 'release v3.8.0' (#255) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/255
2023-05-25 13:49:08 +02:00
Simon Vieille e1567b0689 Merge pull request 'v3.7.4' (#249) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/249
2023-04-16 18:05:46 +02:00
Simon Vieille aa139bc671 Merge pull request 'release v3.7.3' (#246) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/246
2023-04-14 21:13:10 +02:00
Simon Vieille b133ba3a97 Merge pull request 'release of v3.7.2' (#238) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/238
2023-03-27 18:47:15 +02:00
Simon Vieille aaf2bb55db Merge pull request 'release v3.7.1' (#232) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/232
2023-03-19 10:00:00 +01:00
Simon Vieille 749f25a231 Merge pull request 'add badge of downloads' (#228) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/228
2023-03-11 00:30:39 +01:00
Simon Vieille 04026f5c75 Merge pull request 'release v3.7.0' (#227) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/227
2023-03-09 16:57:33 +01:00
Simon Vieille 48b2b30406 Merge pull request 'update issue template' (#223) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/223
2023-02-20 14:06:23 +01:00
Simon Vieille 22e5445330 Merge pull request 'release v3.6.0' (#221) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/221
2023-02-17 11:53:27 +01:00
Simon Vieille 0544fd3765 Merge pull request 'doc' (#218) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/218
2023-02-12 16:56:39 +01:00
Simon Vieille 2635dd89ca Merge pull request 'Doc' (#217) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/217
2023-02-12 00:34:56 +01:00
Simon Vieille aa203bb406 Merge pull request 'release v3.5.2' (#213) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/213
2023-01-21 22:31:21 +01:00
Simon Vieille 368c59a2bd Merge pull request 'next release' (#200) from develop into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/200
2023-01-07 10:40:14 +01:00
Simon Vieille ab7b1e96fe Merge pull request 'add gitea ISSUE/FEATURE templates' (#202) from gitea into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/202
2022-12-29 22:45:50 +01:00
Simon Vieille f4322a8d71 Merge pull request 'add gitea ISSUE/FEATURE templates' (#201) from gitea into master
Reviewed-on: https://gitnet.fr/deblan/side_menu/pulls/201
2022-12-29 22:45:03 +01:00
28 changed files with 111 additions and 402 deletions
-27
View File
@@ -1,32 +1,5 @@
## [Unreleased] ## [Unreleased]
### Fixed
- fix(config): store the app store category cache lazily with only the names in the languages in use, refresh it weekly and when the settings are saved, and load it only when categories are used
- fix(admin): save all settings in one request so changes are not lost after the page reloads
- fix(config): remove the old category cache from the app config on upgrade
## 6.2.0
### Added
- feat(app): add compatibility with Nextcloud 35
### Fixed
- fix #510 #417: [Bug] Styling of the icon labels and overflow icon is not correct
## 6.1.0
### Added
- feat: add search toggle option (fix #509)
### Fixed
- fix #510: [Bug] Styling of the icon labels and overflow icon is not correct
- fix #462: [Bug] Burger menu in settings is hidden with "Always displayed" on mobile device
## 6.0.1 ## 6.0.1
### Added ### Added
-2
View File
@@ -49,8 +49,6 @@ 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 the shortcut `Ctrl`+`o` to open and to hide the side menu. Use `tab` to navigate.
The names of the app store categories are cached for a week, only in the languages that are in use on the instance (and English). If a user switches to a new language and sees English category names, save the settings on the administration page to refresh the cache right away.
### Use first top menu app as default app ### 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`: 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`:
+2 -7
View File
@@ -30,7 +30,7 @@ Notice
Because I believe in a free and decentralized Internet, [Gitnet](https://gitnet.fr) is **self-hosted at home**. 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/). In case of downtime, you can download **Custom Menu** from [here](https://kim.deblan.fr/~side_menu/).
]]></description> ]]></description>
<version>6.2.0</version> <version>6.0.1</version>
<licence>agpl</licence> <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.fr/">Simon Vieille</author>
<namespace>SideMenu</namespace> <namespace>SideMenu</namespace>
@@ -53,13 +53,8 @@ In case of downtime, you can download **Custom Menu** from [here](https://kim.de
<screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_default_menu.png]]></screenshot> <screenshot><![CDATA[https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc25_default_menu.png]]></screenshot>
<dependencies> <dependencies>
<php min-version="8.1" max-version="8.5" /> <php min-version="8.1" max-version="8.5" />
<nextcloud min-version="34" max-version="35"/> <nextcloud min-version="34" max-version="34"/>
</dependencies> </dependencies>
<repair-steps>
<post-migration>
<step>OCA\SideMenu\Migration\ClearCategoryCache</step>
</post-migration>
</repair-steps>
<settings> <settings>
<admin>OCA\SideMenu\Settings\Admin</admin> <admin>OCA\SideMenu\Settings\Admin</admin>
<admin-section>OCA\SideMenu\Settings\AdminSection</admin-section> <admin-section>OCA\SideMenu\Settings\AdminSection</admin-section>
+1 -9
View File
@@ -12,16 +12,12 @@ use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\CategoryRepository; use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\Color; use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy; use OCA\SideMenu\Service\ConfigProxy;
use OCA\SideMenu\Service\LangRepository;
use OCA\Theming\ThemingDefaults; use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
use OCP\ICacheFactory;
use OCP\IConfig; use OCP\IConfig;
use OCP\INavigationManager; use OCP\INavigationManager;
use OCP\IRequest; use OCP\IRequest;
@@ -60,11 +56,7 @@ class Application extends App implements IBootstrap
$c->get(ConfigProxy::class), $c->get(ConfigProxy::class),
$c->get(IConfig::class), $c->get(IConfig::class),
$c->get(IFactory::class), $c->get(IFactory::class),
$c->get(IUserSession::class), $c->get(IUserSession::class)
$c->get(IAppConfig::class),
$c->get(ITimeFactory::class),
$c->get(LangRepository::class),
$c->get(ICacheFactory::class)
); );
}); });
+30 -83
View File
@@ -20,14 +20,12 @@
namespace OCA\SideMenu\Controller; namespace OCA\SideMenu\Controller;
use OCA\SideMenu\AppInfo\Application; use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\Color; use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\ConfigProxy; use OCA\SideMenu\Service\ConfigProxy;
use OCA\SideMenu\Service\LangRepository; use OCA\SideMenu\Service\LangRepository;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute; use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\DataDownloadResponse; use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\RedirectResponse;
@@ -37,12 +35,6 @@ use OCP\IURLGenerator;
class AdminSettingController extends Controller class AdminSettingController extends Controller
{ {
/**
* Keys that are managed by the app or by Nextcloud itself ("enabled")
* and must not be written from the settings form.
*/
protected const UNSAVED_KEYS = ['cache', 'cache-categories', 'enabled'];
public function __construct( public function __construct(
$appName, $appName,
IRequest $request, IRequest $request,
@@ -51,7 +43,6 @@ class AdminSettingController extends Controller
protected IURLGenerator $urlGenerator, protected IURLGenerator $urlGenerator,
protected Color $color, protected Color $color,
protected LangRepository $langRepository, protected LangRepository $langRepository,
protected CategoryRepository $categoryRepository,
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
@@ -60,7 +51,7 @@ class AdminSettingController extends Controller
#[FrontpageRoute(verb: 'GET', url: '/admin/cache/remove')] #[FrontpageRoute(verb: 'GET', url: '/admin/cache/remove')]
public function removeCache(): RedirectResponse public function removeCache(): RedirectResponse
{ {
$this->categoryRepository->clearCache(); $this->config->setAppValue(Application::APP_ID, 'cache-categories', '[]');
return new RedirectResponse($this->urlGenerator->linkToRoute('settings.AdminSettings.index', [ return new RedirectResponse($this->urlGenerator->linkToRoute('settings.AdminSettings.index', [
'section' => Application::APP_ID, 'section' => Application::APP_ID,
@@ -106,7 +97,6 @@ class AdminSettingController extends Controller
'use-avatar', 'use-avatar',
'add-logo-link', 'add-logo-link',
'show-settings', 'show-settings',
'show-search-form',
'loader-enabled', 'loader-enabled',
'always-displayed', 'always-displayed',
'enabled', 'enabled',
@@ -140,84 +130,13 @@ class AdminSettingController extends Controller
'top-menu-mouse-over-hidden-label', 'top-menu-mouse-over-hidden-label',
]; ];
$defaults = $this->getDefaults(); $defaults = [
$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)) {
continue;
}
if (in_array($key, $booleans)) {
$config[$key] = $default === '1';
} elseif (in_array($key, $arrays)) {
$config[$key] = json_decode($default);
} elseif (in_array($key, $integers)) {
$config[$key] = (int) $default;
} else {
$config[$key] = $default;
}
}
$config['langs'] = $this->langRepository->getUsedLangs();
return new JSONResponse($config);
}
#[PasswordConfirmationRequired]
#[FrontpageRoute(verb: 'POST', url: '/admin/config')]
public function saveConfiguration(array $config): JSONResponse
{
$allowed = array_diff(array_keys($this->getDefaults()), self::UNSAVED_KEYS);
foreach ($config as $key => $value) {
if (!in_array($key, $allowed, true)) {
continue;
}
if (is_array($value)) {
$value = json_encode($value);
} elseif (is_bool($value)) {
$value = $value ? '1' : '0';
}
$this->config->setAppValue(Application::APP_ID, $key, (string) $value);
}
// New stylesheet URL, and category names that match the new settings
$this->config->setAppValue(Application::APP_ID, 'cache', (string) time());
$this->categoryRepository->clearCache();
return new JSONResponse(['status' => 'ok']);
}
protected function getDefaults(): array
{
return [
'opener-only' => '0', 'opener-only' => '0',
'opener-hover' => '0', 'opener-hover' => '0',
'display-logo' => '1', 'display-logo' => '1',
'use-avatar' => '0', 'use-avatar' => '0',
'add-logo-link' => '1', 'add-logo-link' => '1',
'show-settings' => '0', 'show-settings' => '0',
'show-search-form' => '1',
'loader-enabled' => '1', 'loader-enabled' => '1',
'always-displayed' => '0', 'always-displayed' => '0',
'enabled' => '1', 'enabled' => '1',
@@ -267,5 +186,33 @@ class AdminSettingController extends Controller
'categories-order-type' => 'default', '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);
} }
} }
-1
View File
@@ -135,7 +135,6 @@ class JsController extends Controller
'force-light-icon' => $this->config->getAppValueBool('force-light-icon', '0'), 'force-light-icon' => $this->config->getAppValueBool('force-light-icon', '0'),
'display-logo' => $this->config->getAppValueBool('display-logo', '1'), 'display-logo' => $this->config->getAppValueBool('display-logo', '1'),
'use-avatar' => $this->config->getAppValueBool('use-avatar', '0'), 'use-avatar' => $this->config->getAppValueBool('use-avatar', '0'),
'show-search-form' => $this->config->getAppValueBool('show-search-form', '1'),
'hide-when-no-apps' => $this->config->getAppValueBool('hide-when-no-apps', '0'), 'hide-when-no-apps' => $this->config->getAppValueBool('hide-when-no-apps', '0'),
'loader-enabled' => $this->config->getAppValueBool('loader-enabled', '1'), 'loader-enabled' => $this->config->getAppValueBool('loader-enabled', '1'),
'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'), 'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'),
-46
View File
@@ -1,46 +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\Migration;
use OCA\SideMenu\Service\CategoryRepository;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
/**
* Earlier versions stored the category cache as a regular app config
* value, which Nextcloud loads on every request. Clear it on upgrade so
* it does not linger on instances that never request the categories.
*/
class ClearCategoryCache implements IRepairStep
{
public function __construct(protected CategoryRepository $categoryRepository)
{
}
public function getName(): string
{
return 'Clear the category cache';
}
public function run(IOutput $output): void
{
$this->categoryRepository->clearCache();
}
}
+1 -2
View File
@@ -40,8 +40,7 @@ class AppRepository
{ {
$navigation = $this->navigationManager->getAll(); $navigation = $this->navigationManager->getAll();
$appCategoriesCustom = $this->config->getAppValueArray('apps-categories-custom', '[]'); $appCategoriesCustom = $this->config->getAppValueArray('apps-categories-custom', '[]');
// Only needed to check custom categories, skip loading them otherwise $categories = $this->categoryRepository->getOrderedCategories();
$categories = empty(array_filter($appCategoriesCustom)) ? [] : $this->categoryRepository->getOrderedCategories();
$apps = $this->ocApp->listAllApps(); $apps = $this->ocApp->listAllApps();
$visibleApps = []; $visibleApps = [];
+7 -122
View File
@@ -4,11 +4,6 @@ namespace OCA\SideMenu\Service;
use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\App\AppStore\Fetcher\CategoryFetcher;
use OCA\SideMenu\AppInfo\Application; use OCA\SideMenu\AppInfo\Application;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Exceptions\AppConfigTypeConflictException;
use OCP\IAppConfig;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IConfig; use OCP\IConfig;
use OCP\IUserSession; use OCP\IUserSession;
use OCP\L10N\IFactory; use OCP\L10N\IFactory;
@@ -20,23 +15,12 @@ use OCP\L10N\IFactory;
*/ */
class CategoryRepository class CategoryRepository
{ {
public const CACHE_KEY = 'cache-categories';
/**
* The app store categories rarely change, refresh them once a week.
*/
protected const CACHE_TTL = 604800;
public function __construct( public function __construct(
protected CategoryFetcher $categoryFetcher, protected CategoryFetcher $categoryFetcher,
protected ConfigProxy $config, protected ConfigProxy $config,
protected IConfig $iConfig, protected IConfig $iConfig,
protected IFactory $l10nFactory, protected IFactory $l10nFactory,
protected IUserSession $userSession, protected IUserSession $userSession,
protected IAppConfig $appConfig,
protected ITimeFactory $timeFactory,
protected LangRepository $langRepository,
protected ICacheFactory $cacheFactory,
) { ) {
} }
@@ -48,9 +32,15 @@ class CategoryRepository
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$type = $this->config->getAppValue('categories-order-type', 'default'); $type = $this->config->getAppValue('categories-order-type', 'default');
$order = $this->config->getAppValueArray('categories-order', '[]'); $order = $this->config->getAppValueArray('categories-order', '[]');
$categoriesLabels = $this->getCategories(); $categoriesLabels = $this->config->getAppValueArray('cache-categories', '[]');
$customCategories = $this->config->getAppValueArray('categories-custom', '[]'); $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) { foreach ($categoriesLabels as $k => $category) {
$categoriesLabels[$category['id']] = $category['translations'][$currentLanguage]['name'] ?? $categoriesLabels[$category['id']] = $category['translations'][$currentLanguage]['name'] ??
$category['translations']['en']['name']; $category['translations']['en']['name'];
@@ -95,109 +85,4 @@ class CategoryRepository
return $categoriesLabels; return $categoriesLabels;
} }
public function clearCache(): void
{
$this->getDistributedCache()?->remove(self::CACHE_KEY);
$this->appConfig->deleteKey(Application::APP_ID, self::CACHE_KEY);
}
/**
* The languages to keep in the cache: English as fallback, the
* default language and the languages of the users, as two-letter
* codes like getOrderedCategories() looks them up. A user who picks
* a new language sees English names until the cache is refreshed,
* which saving the settings does right away.
*/
protected function getCachedLangs(): array
{
$langs = $this->langRepository->getUsedLangs();
$langs[] = (string) $this->iConfig->getSystemValue('default_language', 'en');
$langs[] = (string) $this->iConfig->getSystemValue('force_language', 'en');
return array_values(array_unique(array_filter(array_map(
static fn (string $lang) => strtolower(substr($lang, 0, 2)),
array_merge(['en'], $langs)
))));
}
/**
* Returns the app store categories with only their names. They are
* cached in the distributed cache (e.g. Redis) when one is configured,
* otherwise in a lazy app config value, so they are not loaded on
* every request with the other settings.
*/
protected function getCategories(): array
{
$cache = $this->getDistributedCache();
if ($cache !== null) {
$categories = $cache->get(self::CACHE_KEY);
if (is_array($categories)) {
return $categories;
}
} else {
try {
$stored = $this->appConfig->getValueArray(Application::APP_ID, self::CACHE_KEY, [], lazy: true);
} catch (AppConfigTypeConflictException) {
// Stored with another type, e.g. set by hand; refreshed below
$stored = [];
}
if (isset($stored['expires'], $stored['categories']) && $stored['expires'] > $this->timeFactory->getTime()) {
return $stored['categories'];
}
}
$categories = $this->fetchCategories();
// Don't cache an empty list, e.g. when the app store is unreachable
if (empty($categories)) {
return $categories;
}
$this->clearCache();
if ($cache !== null) {
$cache->set(self::CACHE_KEY, $categories, self::CACHE_TTL);
} else {
$this->appConfig->setValueArray(Application::APP_ID, self::CACHE_KEY, [
'expires' => $this->timeFactory->getTime() + self::CACHE_TTL,
'categories' => $categories,
], lazy: true);
}
return $categories;
}
protected function fetchCategories(): array
{
$langs = $this->getCachedLangs();
$categories = [];
foreach ($this->categoryFetcher->get() as $category) {
$names = [];
foreach ($category['translations'] ?? [] as $lang => $translation) {
if (in_array($lang, $langs, true)) {
$names[$lang] = ['name' => $translation['name'] ?? ''];
}
}
$categories[] = [
'id' => $category['id'],
'translations' => $names,
];
}
return $categories;
}
protected function getDistributedCache(): ?ICache
{
return $this->cacheFactory->isAvailable()
? $this->cacheFactory->createDistributed(Application::APP_ID)
: null;
}
} }
+39 -18
View File
@@ -20,9 +20,6 @@
</template> </template>
<script setup> <script setup>
import axios from '@nextcloud/axios'
import { translate as t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { NcButton, NcLoadingIcon } from '@nextcloud/vue' import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
import { ref } from 'vue' import { ref } from 'vue'
import { waitPasswordConfirmation } from '../../lib/setting.js' import { waitPasswordConfirmation } from '../../lib/setting.js'
@@ -49,28 +46,52 @@ const filterConfig = (value) => {
} }
const save = async () => { const save = async () => {
const data = filterConfig(config)
const size = Object.keys(data).length
let counter = 0
loading.value = true loading.value = true
error.value = null error.value = null
try { const update = () => {
await waitPasswordConfirmation() ++counter
} catch {
if (counter === size) {
loading.value = false loading.value = false
return
if (!error.value) {
location.reload()
}
}
} }
try { waitPasswordConfirmation()
// One request for all settings: separate requests per setting could .then(() => {
// leave a half-saved configuration in Nextcloud's config cache when for (let key in data) {
// the page reloads. let value = data[key]
await axios.post(generateUrl('/apps/side_menu/admin/config'), {
config: filterConfig(config), if (Array.isArray(value) || typeof value === 'object') {
}) value = JSON.stringify(value)
location.reload() } else if (typeof value === 'boolean') {
} catch { value = value ? '1' : '0'
error.value = t('side_menu', 'Error while saving the settings')
loading.value = false
} }
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> </script>
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Přizpůsobit' 'Customize': 'Přizpůsobit'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Zavřít' 'Close': 'Zavřít'
'Show the search form': 'Show the search form'
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Customize' 'Customize': 'Customize'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Close' 'Close': 'Close'
'Show the search form': 'Show the search form'
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Personalizar' 'Customize': 'Personalizar'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Cerrar' 'Close': 'Cerrar'
'Show the search form': 'Show the search form'
-1
View File
@@ -128,4 +128,3 @@
'Customize': 'Personnaliser' 'Customize': 'Personnaliser'
'Custom': 'Personnalisé' 'Custom': 'Personnalisé'
'Close': 'Fermer' 'Close': 'Fermer'
'Show the search form': 'Afficher le formulaire de recherche'
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Personalizar' 'Customize': 'Personalizar'
'Custom': 'Personalizado' 'Custom': 'Personalizado'
'Close': 'Pechar' 'Close': 'Pechar'
'Show the search form': 'Show the search form'
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Customize' 'Customize': 'Customize'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Close' 'Close': 'Close'
'Show the search form': 'Show the search form'
-1
View File
@@ -107,4 +107,3 @@
'Customize': 'Customize' 'Customize': 'Customize'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Close' 'Close': 'Close'
'Show the search form': 'Show the search form'
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Приспособить' 'Customize': 'Приспособить'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Закрыть' 'Close': 'Закрыть'
'Show the search form': 'Show the search form'
-1
View File
@@ -107,4 +107,3 @@
'Customize': 'Customize' 'Customize': 'Customize'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Close' 'Close': 'Close'
'Show the search form': 'Show the search form'
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Anpassa' 'Customize': 'Anpassa'
'Custom': 'Anpassad' 'Custom': 'Anpassad'
'Close': 'Stäng' 'Close': 'Stäng'
'Show the search form': 'Show the search form'
-1
View File
@@ -109,4 +109,3 @@
'Customize': 'Customize' 'Customize': 'Customize'
'Custom': 'Custom' 'Custom': 'Custom'
'Close': 'Close' 'Close': 'Close'
'Show the search form': 'Show the search form'
-1
View File
@@ -109,4 +109,3 @@
'Customize': '自定义' 'Customize': '自定义'
'Custom': '自定义' 'Custom': '自定义'
'Close': '关闭' 'Close': '关闭'
'Show the search form': 'Show the search form'
+1 -3
View File
@@ -27,7 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:label="settings.name" :label="settings.name"
:avatar="settings.avatar" :avatar="settings.avatar"
/> />
<AppSearch v-if="showSearchForm" v-model="search" /> <AppSearch v-model="search" />
<OpenerButton <OpenerButton
v-if="!openerHover" v-if="!openerHover"
@click="$emit('close')" @click="$emit('close')"
@@ -102,7 +102,6 @@ const activeApp = ref(null)
const targetBlankApps = ref([]) const targetBlankApps = ref([])
const settings = ref(null) const settings = ref(null)
const search = ref('') const search = ref('')
const showSearchForm = ref(true)
const openerHover = ref(false) const openerHover = ref(false)
const menu = useTemplateRef('menu') const menu = useTemplateRef('menu')
const isTouchDevice = window.matchMedia('(pointer: coarse)').matches const isTouchDevice = window.matchMedia('(pointer: coarse)').matches
@@ -121,7 +120,6 @@ onMounted(async () => {
targetBlankApps.value = config['target-blank-apps'] targetBlankApps.value = config['target-blank-apps']
settings.value = config['settings'] settings.value = config['settings']
showSearchForm.value = config['show-search-form']
openerHover.value = config['opener-hover'] && !isTouchDevice openerHover.value = config['opener-hover'] && !isTouchDevice
items.value = await navStore.getCategories() items.value = await navStore.getCategories()
+1 -3
View File
@@ -30,7 +30,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:avatar="settings.avatar" :avatar="settings.avatar"
/> />
<AppSearch <AppSearch
v-if="showSearchForm && open" v-if="open"
v-model="search" v-model="search"
/> />
<OpenerButton <OpenerButton
@@ -103,7 +103,6 @@ const openerHover = ref(false)
const alwaysDisplayed = ref(false) const alwaysDisplayed = ref(false)
const displayLogo = ref(false) const displayLogo = ref(false)
const useAvatarAsLogo = ref(false) const useAvatarAsLogo = ref(false)
const showSearchForm = ref(true)
const search = ref('') const search = ref('')
const apps = ref([]) const apps = ref([])
const menu = useTemplateRef('menu') const menu = useTemplateRef('menu')
@@ -152,7 +151,6 @@ onMounted(async () => {
alwaysDisplayed.value = config['always-displayed'] alwaysDisplayed.value = config['always-displayed']
targetBlankApps.value = config['target-blank-apps'] targetBlankApps.value = config['target-blank-apps']
forceLightIcon.value = config['force-light-icon'] forceLightIcon.value = config['force-light-icon']
showSearchForm.value = config['show-search-form']
avatar.value = config['avatar'] avatar.value = config['avatar']
logo.value = config['logo'] logo.value = config['logo']
+1 -3
View File
@@ -31,7 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:label="settings.name" :label="settings.name"
:avatar="settings.avatar" :avatar="settings.avatar"
/> />
<AppSearch v-if="showSearchForm" v-model="search" /> <AppSearch v-model="search" />
<OpenerButton <OpenerButton
v-if="!openerHover" v-if="!openerHover"
@click="$emit('close')" @click="$emit('close')"
@@ -107,7 +107,6 @@ const activeApp = ref(null)
const targetBlankApps = ref([]) const targetBlankApps = ref([])
const settings = ref(null) const settings = ref(null)
const search = ref('') const search = ref('')
const showSearchForm = ref(true)
const openerHover = ref(false) const openerHover = ref(false)
const menu = useTemplateRef('menu') const menu = useTemplateRef('menu')
const isTouchDevice = window.matchMedia('(pointer: coarse)').matches const isTouchDevice = window.matchMedia('(pointer: coarse)').matches
@@ -126,7 +125,6 @@ onMounted(async () => {
targetBlankApps.value = config['target-blank-apps'] targetBlankApps.value = config['target-blank-apps']
settings.value = config['settings'] settings.value = config['settings']
showSearchForm.value = config['show-search-form']
openerHover.value = config['opener-hover'] && !isTouchDevice openerHover.value = config['opener-hover'] && !isTouchDevice
items.value = await navStore.getCategories() items.value = await navStore.getCategories()
-15
View File
@@ -29,8 +29,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/> />
</NcAppNavigation> </NcAppNavigation>
<NcAppContent class="cm-settings cm-settings--nav"> <NcAppContent class="cm-settings cm-settings--nav">
<button @click="toggleNav" class="cm-settings-nav-toggle">{{ trans('Toggle menu') }}</button>
<SettingsSection :hidden="section !== 'panel'"> <SettingsSection :hidden="section !== 'panel'">
<SectionTitle label="Panel" /> <SectionTitle label="Panel" />
@@ -82,15 +80,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<FormYesNo v-model="config['show-settings']" /> <FormYesNo v-model="config['show-settings']" />
</SettingValue> </SettingValue>
</SettingItem> </SettingItem>
<SettingItem :disabled="config['always-displayed']">
<SettingLabel
label="Show the search form"
:middle="true"
/>
<SettingValue>
<FormYesNo v-model="config['show-search-form']" />
</SettingValue>
</SettingItem>
<SettingItem> <SettingItem>
<SettingLabel <SettingLabel
label="Icons" label="Icons"
@@ -630,10 +619,6 @@ const filterConfig = (value) => {
return result return result
} }
const toggleNav = () => {
document.querySelector('.cm-settings-nav .app-navigation-toggle-wrapper > button').click()
}
onMounted(async () => { onMounted(async () => {
config.value = await configStore.getAppConfig() config.value = await configStore.getAppConfig()
+5 -16
View File
@@ -15,25 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
.cm-settings-nav {
padding-top: 30px !important;
}
.cm-settings-nav-toggle {
margin-top: 40px !important;
margin-left: 15px !important;
}
.cm-settings-nav .app-navigation-toggle-wrapper > button {
display: none;
}
.cm-settings { .cm-settings {
&-nav { &--nav {
max-width: 50vw !important; padding-top: 30px;
}
&-nav {
.app-navigation__content { .app-navigation__content {
padding: 20px 10px 20px 10px; padding: 20px;
} }
.app-navigation-entry-icon { .app-navigation-entry-icon {
+10 -20
View File
@@ -287,6 +287,14 @@
} }
} }
.cm-standardmenu {
visibility: hidden;
&.show {
visibility: visible;
}
}
.cm-always-displayed { .cm-always-displayed {
body { body {
width: calc(100% - 50px) !important; width: calc(100% - 50px) !important;
@@ -418,25 +426,11 @@
$header-icon-size: 20px; $header-icon-size: 20px;
.cm-standardmenu { .cm-standardmenu {
visibility: hidden;
width: 100%; width: 100%;
display: flex; display: flex;
flex-shrink: 1; flex-shrink: 1;
flex-wrap: wrap; flex-wrap: wrap;
&.show {
visibility: visible;
}
svg {
color: var(--color-background-plain-text) !important;
}
.app-menu-entry--label {
color: var(--color-background-plain-text) !important;
filter: var(--background-image-invert-if-bright, var(--primary-invert-if-bright)) !important;
}
.app-menu-main { .app-menu-main {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
@@ -496,7 +490,7 @@ $header-icon-size: 20px;
color: var(--color-primary-text); color: var(--color-primary-text);
text-align: center; text-align: center;
left: 50%; left: 50%;
top: 55%; top: 45%;
display: block; display: block;
min-width: 100%; min-width: 100%;
transform: translateX(-50%); transform: translateX(-50%);
@@ -568,10 +562,7 @@ $header-icon-size: 20px;
} }
} }
&-app-menu-more { .app-menu-more .button-vue--vue-tertiary {
padding-top: 9px !important;
.button-vue--vue-tertiary {
opacity: 0.7; opacity: 0.7;
margin: 8px 3px 3px 3px; margin: 8px 3px 3px 3px;
filter: var(--background-image-invert-if-bright, var(--primary-invert-if-bright)); filter: var(--background-image-invert-if-bright, var(--primary-invert-if-bright));
@@ -590,7 +581,6 @@ $header-icon-size: 20px;
outline: none !important; outline: none !important;
} }
} }
}
&-app-menu-popover-entry { &-app-menu-popover-entry {
.app-icon { .app-icon {