diff --git a/apps/theming/lib/ConfigLexicon.php b/apps/theming/lib/ConfigLexicon.php
index 6c91d7dfad30b..f9b03b018aff2 100644
--- a/apps/theming/lib/ConfigLexicon.php
+++ b/apps/theming/lib/ConfigLexicon.php
@@ -24,6 +24,8 @@ class ConfigLexicon implements ILexicon {
/** The cache buster index */
public const CACHE_BUSTER = 'cachebuster';
public const USER_THEMING_DISABLED = 'disable-user-theming';
+ public const TOAST_TIMEOUT = 'toast_timeout';
+ public const TOAST_TIMEOUT_DEFAULT = 7000;
/** Name of the software running on this instance (usually "Nextcloud") */
public const PRODUCT_NAME = 'productName';
@@ -114,6 +116,13 @@ public function getAppConfigs(): array {
#[\Override]
public function getUserConfigs(): array {
- return [];
+ return [
+ new Entry(
+ self::TOAST_TIMEOUT,
+ ValueType::INT,
+ defaultRaw: self::TOAST_TIMEOUT_DEFAULT,
+ definition: 'How long toast notifications remain visible in milliseconds.',
+ ),
+ ];
}
}
diff --git a/apps/theming/lib/Listener/BeforePreferenceListener.php b/apps/theming/lib/Listener/BeforePreferenceListener.php
index d823df1ab33c2..d964a7f884b05 100644
--- a/apps/theming/lib/Listener/BeforePreferenceListener.php
+++ b/apps/theming/lib/Listener/BeforePreferenceListener.php
@@ -10,6 +10,7 @@
namespace OCA\Theming\Listener;
use OCA\Theming\AppInfo\Application;
+use OCA\Theming\ConfigLexicon;
use OCP\App\IAppManager;
use OCP\Config\BeforePreferenceDeletedEvent;
use OCP\Config\BeforePreferenceSetEvent;
@@ -22,7 +23,15 @@ class BeforePreferenceListener implements IEventListener {
/**
* @var string[]
*/
- private const ALLOWED_KEYS = ['force_enable_blur_filter', 'shortcuts_disabled', 'primary_color'];
+ private const ALLOWED_KEYS = ['force_enable_blur_filter', 'shortcuts_disabled', 'primary_color', ConfigLexicon::TOAST_TIMEOUT];
+
+ /**
+ * Allowed toast timeout values in milliseconds.
+ * Default (7000) is represented by deleting the preference.
+ *
+ * @var int[]
+ */
+ public const array TOAST_TIMEOUT_VALUES = [15000, 30000, -1];
public function __construct(
private IAppManager $appManager,
@@ -62,6 +71,10 @@ private function handleThemingValues(BeforePreferenceSetEvent|BeforePreferenceDe
case 'primary_color':
$event->setValid(preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $event->getConfigValue()) === 1);
break;
+ case ConfigLexicon::TOAST_TIMEOUT:
+ $value = filter_var($event->getConfigValue(), FILTER_VALIDATE_INT);
+ $event->setValid($value !== false && in_array($value, self::TOAST_TIMEOUT_VALUES, true));
+ break;
default:
$event->setValid(false);
}
diff --git a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
index 3a62d8ebdf32b..02c2b5038dd08 100644
--- a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
+++ b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
@@ -10,12 +10,14 @@
namespace OCA\Theming\Listener;
use OCA\Theming\AppInfo\Application;
+use OCA\Theming\ConfigLexicon;
use OCA\Theming\Service\JSDataService;
use OCA\Theming\Service\ThemeInjectionService;
use OCP\AppFramework\Http\Events\BeforeLoginTemplateRenderedEvent;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
+use OCP\Config\IUserConfig;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IConfig;
@@ -32,6 +34,7 @@ public function __construct(
private ThemeInjectionService $themeInjectionService,
private IUserSession $userSession,
private IConfig $config,
+ private IUserConfig $userConfig,
) {
}
@@ -51,6 +54,17 @@ public function handle(Event $event): void {
}
return false;
});
+
+ $this->initialState->provideLazyInitialState('toastTimeout', function (): int {
+ if ($this->userSession->getUser()) {
+ $uid = $this->userSession->getUser()->getUID();
+ $value = $this->userConfig->getValueInt($uid, Application::APP_ID, ConfigLexicon::TOAST_TIMEOUT, ConfigLexicon::TOAST_TIMEOUT_DEFAULT);
+ if (in_array($value, BeforePreferenceListener::TOAST_TIMEOUT_VALUES, true) || $value === ConfigLexicon::TOAST_TIMEOUT_DEFAULT) {
+ return $value;
+ }
+ }
+ return ConfigLexicon::TOAST_TIMEOUT_DEFAULT;
+ });
}
$this->themeInjectionService->injectHeaders();
diff --git a/apps/theming/src/components/UserSectionToastTimeout.vue b/apps/theming/src/components/UserSectionToastTimeout.vue
new file mode 100644
index 0000000000000..81481bc078931
--- /dev/null
+++ b/apps/theming/src/components/UserSectionToastTimeout.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/theming/src/theming.ts b/apps/theming/src/theming.ts
index 4678efe37a319..4187da73a9e56 100644
--- a/apps/theming/src/theming.ts
+++ b/apps/theming/src/theming.ts
@@ -3,6 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+import { setToastTimeout, TOAST_DEFAULT_TIMEOUT } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
window.OCA.Theming = loadState('theming', 'data')
+
+const toastTimeout = loadState('theming', 'toastTimeout', TOAST_DEFAULT_TIMEOUT)
+if (typeof toastTimeout === 'number') {
+ setToastTimeout(toastTimeout)
+}
diff --git a/apps/theming/src/views/UserTheming.vue b/apps/theming/src/views/UserTheming.vue
index 6ecee83202e7a..7bb7128547e0d 100644
--- a/apps/theming/src/views/UserTheming.vue
+++ b/apps/theming/src/views/UserTheming.vue
@@ -49,6 +49,7 @@
+
@@ -68,6 +69,7 @@ import UserSectionAppMenu from '../components/UserSectionAppMenu.vue'
import UserSectionBackground from '../components/UserSectionBackground.vue'
import UserSectionHotkeys from '../components/UserSectionHotkeys.vue'
import UserSectionPrimaryColor from '../components/UserSectionPrimaryColor.vue'
+import UserSectionToastTimeout from '../components/UserSectionToastTimeout.vue'
import { refreshStyles } from '../utils/refreshStyles.js'
const isUserThemingDisabled = loadState('theming', 'isUserThemingDisabled')