Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add global setting to disable keyboad shortcuts #34081

Merged
merged 3 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/theming/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
namespace OCA\Theming\AppInfo;

use OCA\Theming\Capabilities;
use OCA\Theming\Listener\BeforePreferenceListener;
use OCA\Theming\Listener\BeforeTemplateRenderedListener;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\Config\BeforePreferenceDeletedEvent;
use OCP\Config\BeforePreferenceSetEvent;

class Application extends App implements IBootstrap {
public const APP_ID = 'theming';
Expand All @@ -42,6 +45,8 @@ public function __construct() {
public function register(IRegistrationContext $context): void {
$context->registerCapability(Capabilities::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerEventListener(BeforePreferenceSetEvent::class, BeforePreferenceListener::class);
$context->registerEventListener(BeforePreferenceDeletedEvent::class, BeforePreferenceListener::class);
}

public function boot(IBootContext $context): void {
Expand Down
56 changes: 56 additions & 0 deletions apps/theming/lib/Listener/BeforePreferenceListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @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\Theming\Listener;

use OCA\Theming\AppInfo\Application;
use OCP\Config\BeforePreferenceDeletedEvent;
use OCP\Config\BeforePreferenceSetEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;

class BeforePreferenceListener implements IEventListener {
public function handle(Event $event): void {
if (!$event instanceof BeforePreferenceSetEvent
&& !$event instanceof BeforePreferenceDeletedEvent) {
return;
}

if ($event->getAppId() !== Application::APP_ID) {
return;
}

if ($event->getConfigKey() !== 'shortcuts_disabled') {
return;
}

if ($event instanceof BeforePreferenceSetEvent) {
$event->setValid($event->getConfigValue() === 'yes');
return;
}

$event->setValid(true);
}
}
13 changes: 13 additions & 0 deletions apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\Service\JSDataService;
use OCA\Theming\Service\ThemeInjectionService;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
Expand Down Expand Up @@ -64,6 +66,17 @@ public function handle(Event $event): void {
fn () => $this->container->get(JSDataService::class),
);

/** @var BeforeTemplateRenderedEvent $event */
if ($event->getResponse()->getRenderAs() === TemplateResponse::RENDER_AS_USER) {
$this->initialState->provideLazyInitialState('shortcutsDisabled', function () {
if ($this->userSession->getUser()) {
$uid = $this->userSession->getUser()->getUID();

Check notice

Code scanning / Psalm

PossiblyNullReference

Cannot call method getUID on possibly null value
return $this->config->getUserValue($uid, Application::APP_ID, 'shortcuts_disabled', 'no') === 'yes';
}
return false;
});
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
}

$this->themeInjectionService->injectHeaders();

$user = $this->userSession->getUser();
Expand Down
51 changes: 50 additions & 1 deletion apps/theming/src/UserThemes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

<template>
<section>
<NcSettingsSection class="theming" :title="t('theming', 'Appearance and accessibility')">
<NcSettingsSection :title="t('theming', 'Appearance and accessibility')"
:limit-width="false"
class="theming">
<p v-html="description" />
<p v-html="descriptionDetail" />

Expand All @@ -48,6 +50,18 @@
@change="changeFont" />
</div>
</NcSettingsSection>

<NcSettingsSection :title="t('theming', 'Keyboard shortcuts')">
<p>{{ t('theming', 'In some cases keyboard shortcuts can interfer with accessibility tools. In order to allow focusing on your tool correctly you can disable all keyboard shortcuts here. This will also disable all available shortcuts in apps.') }}</p>
<NcCheckboxRadioSwitch class="theming__preview-toggle"
:checked.sync="shortcutsDisabled"
name="shortcuts_disabled"
type="switch"
@change="changeShortcutsDisabled">
{{ t('theming', 'Disable all keyboard shortcuts') }}
</NcCheckboxRadioSwitch>
</NcSettingsSection>

<NcSettingsSection :title="t('theming', 'Background')"
class="background">
<p>{{ t('theming', 'Set a custom background') }}</p>
Expand All @@ -63,6 +77,7 @@
import { generateOcsUrl, imagePath } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection'

import BackgroundSettings from './components/BackgroundSettings.vue'
Expand All @@ -72,6 +87,7 @@ import { getBackgroundUrl } from '../src/helpers/getBackgroundUrl.js'

const availableThemes = loadState('theming', 'themes', [])
const enforceTheme = loadState('theming', 'enforceTheme', '')
const shortcutsDisabled = loadState('theming', 'shortcutsDisabled', false)

const background = loadState('theming', 'background')
const backgroundVersion = loadState('theming', 'backgroundVersion')
Expand All @@ -84,6 +100,7 @@ export default {
name: 'UserThemes',
components: {
ItemPreview,
NcCheckboxRadioSwitch,
NcSettingsSection,
BackgroundSettings,
},
Expand All @@ -92,6 +109,7 @@ export default {
return {
availableThemes,
enforceTheme,
shortcutsDisabled,
background,
themingDefaultBackground,
}
Expand Down Expand Up @@ -151,9 +169,17 @@ export default {
return '<a target="_blank" href="https://nextcloud.com/design" rel="noreferrer nofollow">'
},
},

mounted() {
this.updateGlobalStyles()
},

watch: {
shortcutsDisabled(newState) {
this.changeShortcutsDisabled(newState)
},
},

methods: {
updateBackground(data) {
this.background = (data.type === 'custom' || data.type === 'default') ? data.type : data.value
Expand Down Expand Up @@ -212,6 +238,29 @@ export default {
this.selectItem(enabled, id)
},

async changeShortcutsDisabled(newState) {
if (newState) {
await axios({
url: generateOcsUrl('apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', {
appId: 'theming',
configKey: 'shortcuts_disabled',
}),
data: {
configValue: 'yes',
},
method: 'POST',
})
} else {
await axios({
url: generateOcsUrl('apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', {
appId: 'theming',
configKey: 'shortcuts_disabled',
}),
method: 'DELETE',
})
}
},

updateBodyAttributes() {
const enabledThemesIDs = this.themes.filter(theme => theme.enabled === true).map(theme => theme.id)
const enabledFontsIDs = this.fonts.filter(font => font.enabled === true).map(font => font.id)
Expand Down
32 changes: 32 additions & 0 deletions core/src/OCP/accessibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license AGPL-3.0-or-later
*
* 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 { loadState } from '@nextcloud/initial-state'

export default {
/**
* @return {boolean} Whether the user opted-out of shortcuts so that they should not be registered
*/
disableKeyboardShortcuts() {
return loadState('theming', 'shortcutsDisabled', false)
},
}
2 changes: 2 additions & 0 deletions core/src/OCP/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ import * as AppConfig from './appconfig'
import * as Comments from './comments'
import * as WhatsNew from './whatsnew'

import Accessibility from './accessibility'
import Collaboration from './collaboration'
import Loader from './loader'
import Toast from './toast'

/** @namespace OCP */
export default {
Accessibility,
AppConfig,
Collaboration,
Comments,
Expand Down
5 changes: 5 additions & 0 deletions core/src/components/HeaderMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default {
handler: this.closeMenu,
middleware: this.clickOutsideMiddleware,
},
shortcutsDisabled: OCP.Accessibility.disableKeyboardShortcuts(),
}
},

Expand Down Expand Up @@ -144,6 +145,10 @@ export default {
},

onKeyDown(event) {
if (this.shortcutsDisabled) {
return
}

// If opened and escape pressed, close
if (event.key === 'Escape' && this.opened) {
event.preventDefault()
Expand Down
4 changes: 4 additions & 0 deletions core/src/views/UnifiedSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ export default {
},

mounted() {
if (OCP.Accessibility.disableKeyboardShortcuts()) {
return
}

document.addEventListener('keydown', (event) => {
// if not already opened, allows us to trigger default browser on second keydown
if (event.ctrlKey && event.key === 'f' && !this.open) {
Expand Down
4 changes: 2 additions & 2 deletions dist/core-main.js

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions dist/core-main.js.LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -427,10 +427,9 @@
*/

/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Joas Schilling <coding@schilljs.com>
*
* @license AGPL-3.0-or-later
*
Expand Down
2 changes: 1 addition & 1 deletion dist/core-main.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/core-unified-search.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/core-unified-search.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/theming-theming-settings.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/theming-theming-settings.js.map

Large diffs are not rendered by default.