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

[4.1] Keyboard Shortcuts Plugin Setup #7

Merged

Conversation

Krshivam25
Copy link
Collaborator

@Krshivam25 Krshivam25 commented Jun 14, 2021

The purpose of this PR is to commit all the changes and commits made. Admin and Mentors can review the changes and provide their valuable feedback.
Documents: https://docs.google.com/document/d/1a2GtsQA-iqAkJcjt5zupyEbJBDBvHfv-kz96L2lZ2_w/edit?usp=sharing

Result After PR: All the features get enabled with keyboard via keyboard shortcuts.
Screenshot (299)

Example: Buttons can be operated with keyboard shortcuts.

  1. Save = alt + s
  2. Save & Close = alt + w
  3. Save & New = alt + n
  4. Cancel = alt + q
  5. Help = alt + x
  6. Save as Copy = alt + shift + c
  7. New = alt + n ( Same as Save & New)

Temporary File for Shortcuts Suggestion: https://docs.google.com/document/d/1l-ypFwa_c9Se-jDKZLs0ncK1PRcMPXfJI9i0Uc2Nebw/edit?usp=sharing

Main components to develop :

  • Articles*
  • Categories*
  • Menus*
  • Users*
  • Tags
  • Contacts
  • Banners
  • News Feed
screen-capture.5.mp4

Summary of changes:

  1. System Plugin is added.
  2. Core features of Joomla! can be managed by the core plugin.
  3. Individual components can then have additional component-specific commands.

console.log(e);
// On Press CTRL + S
if (
(window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)

Common code put in common place

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update it. Thank you

@anuragteapot
Copy link
Member

@Krshivam25 List all the shortcuts and keymap you are going to implement here

@Krshivam25
Copy link
Collaborator Author

@Krshivam25 List all the shortcuts and keymap you are going to implement here
Added more Keyboard Shortcuts .

@anuragteapot
Copy link
Member

@Krshivam25

I will try to implement in a smart way like:

image

You know everything going to have similar info like:

  1. Element selector
  2. Action type -> Click
  3. Action key -> meta+s

So, why not make a common function which will automatically map register all the events;

LIke:

// Optional callback function
Joomla.addShortcut('meta+alt+o, 'joomla-toolbar-button button.button-options');
this code will register the action automatically and listen for it;

From database you going to have a mapping like:

[{ actionButton:'meta+alt+o', selector:'joomla-toolbar-button button.button-options' }]

a list of mapping

mapping.forEach((map)=>{ Joomla.addShortcut ... so on })

@anuragteapot
Copy link
Member

@Krshivam25 I will suggest you implement the Joomla.addShortcut function here

Inject your function to the global window object of Joomla like others

build/media_source/system/js/

The function should take the following properties like

selector -> joomla-toolbar-button button.button-apply
event name -> onClick
shouldPrevent event or not -> boolean
A call back function

Every time you will run Joomla.addShortcut from anywhere in Joomla it will register your event.

It will maintain event data in global window object or somewhere else data structure window.Joomla.shoutcutsEvent=[]

This will help other third-party developers to use this API in their extensions directly

I guess this is enough information


// Then map shortcuts
window.Joomla.shortcutsEvent.map(( {actionButton, selector} ) => {
let splitArr = actionButton.split();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actionButton.split(); -> actionButton.split('+');

@HLeithner
Copy link
Member

2 things which are needed for the plugin

Add the needed SQL Statements to the update scripts at https://github.com/joomla-projects/gsoc21_accessibility/tree/4.1-dev/administrator/components/com_admin/sql/updates

And the needed SQL Statements for the installation sql scripts https://github.com/joomla-projects/gsoc21_accessibility/tree/4.1-dev/installation/sql

plugins/system/shortcut/shortcut.php Outdated Show resolved Hide resolved
build/media_source/plg_system_shortcut/index.html Outdated Show resolved Hide resolved
plugins/system/shortcut/index.html Outdated Show resolved Hide resolved
build/media_source/plg_system_shortcut/shortcut.js Outdated Show resolved Hide resolved
@dgrammatiko
Copy link
Contributor

Please rename the js file to '.es6.js' and make sure you read the read me file in the build directory (you are skipping cs amongst other things)

@brianteeman
Copy link
Contributor

For reference you might find a previous attempt at this plugin useful joomla/joomla-cms#24152

@brianteeman
Copy link
Contributor

Also for reference and my experience creating joomla/joomla-cms#24152 the hotkeys you have chosen will not work with firefox as they are already being used

@dgrammatiko
Copy link
Contributor

Some more help here:

class JoomlaShortcuts {
  constructor() {
    if (!Joomla) {
      throw new Error('Joomla API is not properly initialised');
    }

    const defaultOptions = {
      apply: {
        keyEvent: 'meta+alt+s',
        selector: 'joomla-toolbar-button button.button-apply'
      },
      new :{
        keyEvent: 'meta+alt+n',
        selector: 'joomla-toolbar-button button.button-new'
      },
      save :{
        keyEvent: 'meta+alt+w',
        selector: 'joomla-toolbar-button button.button-save'
      },
      saveNew: {
        keyEvent: 'meta+shift+alt+w',
        selector: 'joomla-toolbar-button button.button-save-new'
      },
      help :{
        keyEvent: 'meta+alt+h',
        selector: 'joomla-toolbar-button button.button-help'
      },
      cancel :{
        keyEvent: 'meta+alt+q',
        selector: 'joomla-toolbar-button button.button-cancel'
      },
      copy: {
        keyEvent: 'meta+shift+alt+c',
        selector: 'joomla-toolbar-button button.button-button-copy'
      }
    };

    const phpOptions = Joomla.getOptions('joomla-shortcut-keys', {});
    let options;
    try {
      options = JSON.parse(phpOptions);
      if (options === false) {
        options = [];
      }
    } catch (e) {
      options = [];
    }

    this.options = [ ...defaultOptions, ...options ];

    // Bindings
    this.execCommand = this.execCommand.bind(this);
    this.handleKeyPressEvent = this.handleKeyPressEvent.bind(this);

    document.addEventListener('keydown', this.handleKeyPressEvent, false);

    try {
      tinyMCE.activeEditor.on('keydown', function (e) {
        handleKeyPressEvent(e);
      });
    } catch (e) {}
  }

  execCommand(event, selector, prevent) {
    let actionBtn = document.querySelector(selector);
    if (actionBtn) {
      if (prevent) {
        event.preventDefault();
      }
      actionBtn.click();
    }
  }

  handleKeyPressEvent(e) {
    this.options.map(({ actionButton, selector }) => {
      let splitArr = actionButton.split('+');
      // meta+shift+alt+c => [meta, shift, alt, c]
      let lastKey = actionButton.charAt(actionButton.length -1);
      if (e.key.toLowerCase() == lastKey) {
        this.execCommand(e, selector);
      }
    });

    if (navigator.platform.match('Mac') ? e.metaKey : e.altKey) {
      // On Press ALT + S
      let keyChar = e.key.toLowerCase();

      Object.values(this.options).map((option) => {
        if (keyChar === option.keyEvent.slice(-1))
          this.execCommand(e, option.selector);
      });
    }
  }
}

new JoomlaShortcuts();

@dgrammatiko
Copy link
Contributor

dgrammatiko commented Jun 20, 2021

BTW from an architecture point of view I would introduce a new PHP event fired on the toolbar button rendering stage and tight the plugin to that event. The point is that the key events will essentially execute the same logic of that button so things are getting simpler also on the PHP side (you cannot ctrl+S on a page without an apply button, etc). @HLeithner I guess this is up to you how you want the integration to happen

@HLeithner
Copy link
Member

@Krshivam25 can you sync this to the 4.1-dev branch please then drone should hopefully work

@anuragteapot
Copy link
Member

@dgrammatiko Hi, glad you are here :) We need some help in javascript approach

@Krshivam25 Krshivam25 requested a review from rdeutz as a code owner June 21, 2021 08:54
@brianteeman
Copy link
Contributor

Please review and fix all the code style issues reported by drone.
Please review and correct all the comments in the code as some are clearly wrong while others are copy pasted from other plugins
Please review how the web asset manager works and test and implement

@HLeithner
Copy link
Member

I activated our CI system for this repo yesterday, you know have the checks at the bottom with a link to the result. for example here: https://ci.joomla.org/joomla-projects/gsoc21_accessibility/9/1/6

please check the phpcs part and fix all errors reported.

thanks

@dgrammatiko
Copy link
Contributor

@Krshivam25 may I give you some clear steps here to help you finish your task sooner?

  • Create a new local branch, ignoring all the work here
  • In the file administrator/templates/atum/index.php you have to experiment with the Joomla\CMS\Factory::getDocument()->addScriptOptions() in order to create a data structure like
{
      apply: {
        keyEvent: 'meta+alt+s',
        selector: 'joomla-toolbar-button button.button-apply'
      },
      new :{
        keyEvent: 'meta+alt+n',
        selector: 'joomla-toolbar-button button.button-new'
      },
      save :{
        keyEvent: 'meta+alt+w',
        selector: 'joomla-toolbar-button button.button-save'
      },
      saveNew: {
        keyEvent: 'meta+shift+alt+w',
        selector: 'joomla-toolbar-button button.button-save-new'
      },
      help :{
        keyEvent: 'meta+alt+h',
        selector: 'joomla-toolbar-button button.button-help'
      },
      cancel :{
        keyEvent: 'meta+alt+q',
        selector: 'joomla-toolbar-button button.button-cancel'
      },
      copy: {
        keyEvent: 'meta+shift+alt+c',
        selector: 'joomla-toolbar-button button.button-button-copy'
      }
    }
  • You can always check your code in the client side by running Joomla.getOptions('yourKey')

  • The PHP code should never override existing data, so you have to come up with a merging strategy

  • Once you accomplish that part you have to figure out where the PHP code needs to be called, (hint find the Toolbar buttons code) and put the code there (always checking that the data structure is correct) and also make sure that this part of the code executes only when the plugin is enabled

  • Once you have all that you need to start coding the JS part based on the code that I provided earlier

Hope this gives you a better guideline

@Krshivam25
Copy link
Collaborator Author

Some more help here:

  • Please use a class in the js
  • Please use the Joomla $document->addScriptOptions() functionality to pass data from PHP to JS
  • Familiarise with the web assets https://docs.joomla.org/J4.x:Web_Assets
  • add the script with a type => module and also add the ES5 version (check joomla/joomla-cms#32315 for a how to)
  • Don't hardcode things as much as possible
class JoomlaShortcuts {
  constructor() {
    if (!Joomla) {
      throw new Error('Joomla API is not properly initialised');
    }

    const defaultOptions = {
      apply: {
        keyEvent: 'meta+alt+s',
        selector: 'joomla-toolbar-button button.button-apply'
      },
      new :{
        keyEvent: 'meta+alt+n',
        selector: 'joomla-toolbar-button button.button-new'
      },
      save :{
        keyEvent: 'meta+alt+w',
        selector: 'joomla-toolbar-button button.button-save'
      },
      saveNew: {
        keyEvent: 'meta+shift+alt+w',
        selector: 'joomla-toolbar-button button.button-save-new'
      },
      help :{
        keyEvent: 'meta+alt+h',
        selector: 'joomla-toolbar-button button.button-help'
      },
      cancel :{
        keyEvent: 'meta+alt+q',
        selector: 'joomla-toolbar-button button.button-cancel'
      },
      copy: {
        keyEvent: 'meta+shift+alt+c',
        selector: 'joomla-toolbar-button button.button-button-copy'
      }
    };

    const phpOptions = Joomla.getOptions('joomla-shortcut-keys', {});
    let options;
    try {
      options = JSON.parse(phpOptions);
      if (options === false) {
        options = [];
      }
    } catch (e) {
      options = [];
    }

    this.options = [ ...defaultOptions, ...options ];

    // Bindings
    this.execCommand = this.execCommand.bind(this);
    this.handleKeyPressEvent = this.handleKeyPressEvent.bind(this);

    document.addEventListener('keydown', this.handleKeyPressEvent, false);

    try {
      tinyMCE.activeEditor.on('keydown', function (e) {
        handleKeyPressEvent(e);
      });
    } catch (e) {}
  }

  execCommand(event, selector, prevent) {
    let actionBtn = document.querySelector(selector);
    if (actionBtn) {
      if (prevent) {
        event.preventDefault();
      }
      actionBtn.click();
    }
  }

  handleKeyPressEvent(e) {
    this.options.map(({ actionButton, selector }) => {
      let splitArr = actionButton.split('+');
      // meta+shift+alt+c => [meta, shift, alt, c]
      let lastKey = actionButton.charAt(actionButton.length -1);
      if (e.key.toLowerCase() == lastKey) {
        this.execCommand(e, selector);
      }
    });

    if (navigator.platform.match('Mac') ? e.metaKey : e.altKey) {
      // On Press ALT + S
      let keyChar = e.key.toLowerCase();

      Object.values(this.options).map((option) => {
        if (keyChar === option.keyEvent.slice(-1))
          this.execCommand(e, option.selector);
      });
    }
  }
}

new JoomlaShortcuts();

Instead of setting in the PHP pass the data from PHP to client-side using the API. This what it means right? @dgrammatiko

@dgrammatiko
Copy link
Contributor

@Krshivam25 please read my comment #7 (comment) the first 3 bullets explain how you can pass data from PHP to JS. You have to familiarise yourself with this concept in order to control the behaviour of the javascript without hardcoding things.

 Added $document->addScriptOptions()  created a data structure for all the keyboard-shortcuts
@dgrammatiko
Copy link
Contributor

@Krshivam25 You correctly pass the data from PHP to JS
Screenshot 2021-06-25 at 16 08 28

Since you are defining the object I would suggest you to be more verbatim, eg
instead of:

[
    'button_apply' => [
		'keyEvent' => 'meta+alt+s',
		'selector' => 'joomla-toolbar-button button.button-apply'
    ]
]

you can be more specific:

[
    'button_apply' => [
		'keyEvent' => 's',
                'hasMeta' => true,
                'hasAlt'    => true,
                'hasShift'  => false,
		'selector' => 'joomla-toolbar-button button.button-apply'
    ]
]

This way in your JS you can simply check against the object instead of splitting the string to discover the possible states of alt,meta,shift.

@dgrammatiko
Copy link
Contributor

dgrammatiko commented Jun 25, 2021

OK some more help:
Use this in the php side:

Factory::getDocument()->addScriptOptions(
	'joomla-shortcut-keys',
	[
	'button_apply' => [
		'keyEvent' => 's',
		'hasShift' => false,
		'hasAlt' => true,
		'hasControl' => true,
		'selector' => 'joomla-toolbar-button button.button-apply'
	],
	'button_new' => [
		'keyEvent' => 'n',
		'hasShift' => false,
		'hasAlt' => true,
		'hasControl' => true,
		'selector' => 'joomla-toolbar-button button.button-new'
	],
	'button_save' => [
		'keyEvent' => 'w',
		'hasShift' => false,
		'hasAlt' => true,
		'hasControl' => true,
		'selector' => 'joomla-toolbar-button button.button-save'
	],
	'button_saveNew' => [
		'keyEvent' => 'w',
		'hasShift' => true,
		'hasAlt' => true,
		'hasControl' => true,
		'selector' => 'joomla-toolbar-button button.button-save-new'
	],
	'button_help' => [
		'keyEvent' => 'x',
		'hasShift' => false,
		'hasAlt' => true,
		'hasControl' => true,
		'selector' => 'joomla-toolbar-button button.button-help'
	],
	'button_cancel' => [
		'keyEvent' => 'q',
		'hasShift' => false,
		'hasAlt' => true,
		'hasControl' => true,
		'selector' => 'joomla-toolbar-button button.button-cancel'
	],
	'button_copy' => [
		'keyEvent' => 'c',
		'hasShift' => true,
		'hasAlt' => true,
		'hasControl' => true,
		'selector' => 'joomla-toolbar-button button.button-button-copy'
	]
]
);

And this in the JS:

class JoomlaShortcuts {
  constructor() {
    if (!Joomla) {
      throw new Error('Joomla API is not properly initialised');
    }

    const phpOptions = Joomla.getOptions('joomla-shortcut-keys', {});

    if (!Object.keys(phpOptions).length) {
      return;
    }

    this.options = phpOptions;

    // Toolbar buttons are Custom Element Childs, so we have to wait for them to initialize
    customElements.whenDefined('joomla-toolbar-button').then(
      Object.values(this.options).map((option) => {
        option.element = document.querySelector(option.selector);
      })
    );

    // Bindings
    this.execCommand = this.execCommand.bind(this);
    this.handleKeyPressEvent = this.handleKeyPressEvent.bind(this);

    document.addEventListener('keypress', this.handleKeyPressEvent, false);

    try {
      // eslint-disable-next-line
      tinyMCE.activeEditor.on('keydown', function (e) {
        handleKeyPressEvent(e);
      });
    } catch (e) {}
  }

  execCommand(event, element, prevent) {
    if (prevent) {
      event.preventDefault();
    }
    element.click();
  }

  handleKeyPressEvent(e) {
    Object.values(this.options).map((option) => {
      if (
        option.keyEvent=== e.key.toLowerCase()
        && option.hasShift === e.shiftKey
        && option.hasControl === e.ctrlKey
        && option.hasAlt === (e.metaKey || e.altKey) ? true : false
        && option.element
      ) {
        this.execCommand(e, option.element);
      }
    });
  }
}

new JoomlaShortcuts();

You still have to enable keys per each toolbar button


if ($this->app->isClient('administrator'))
{
$wa = $this->app->getDocument()->getWebAssetManager();
Copy link
Contributor

@dgrammatiko dgrammatiko Jun 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you really need to study the Web Assets: https://docs.joomla.org/J4.x:Web_Assets

$this->app->getDocument()->addScriptOptions('system-shortcut', $shortcut);
$this->app->getDocument()->getWebAssetManager()->registerAndUseScript('shortcut', 'plg_system_shortcut/shortcut.js', [], ['type' => 'module'], ['core']);

@bembelimen bembelimen merged commit 9011e23 into joomla-projects:keyboard-shortcut May 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants