From 6e793413a03d88452f2840ebd28fa58d8243585b Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 7 Dec 2023 12:14:06 +0200 Subject: [PATCH 01/40] added `aria-expanded` attribute to dropdown buttons --- www/common/common-interface.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index bdfc3141e..2a2d6e867 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1470,6 +1470,7 @@ define([ hides the drawer content. Used for toolbar buttons at the moment. */ UI.createDrawer = function ($button, $content) { + $button.attr('aria-expanded', false); $button.click(function () { var topPos = $button[0].getBoundingClientRect().bottom; $content.toggle(); @@ -1481,6 +1482,17 @@ define([ $content.css('max-height', Math.floor(wh - topPos - 1)+'px'); } }); + + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.attributeName === "style") { + var isVisible = $content.is(':visible'); + $button.attr('aria-expanded', isVisible); + } + }); + }); + observer.observe($content[0], { attributes: true }); + var onBlur = function (e) { if (e.relatedTarget) { var $relatedTarget = $(e.relatedTarget); From 181b3a6ff3553f4d8594288dc0943cda38f4ed9c Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 7 Dec 2023 13:07:25 +0200 Subject: [PATCH 02/40] changed `File` dropdown to be of type `ul` --- www/common/toolbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 745271cfc..15110daa9 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -152,7 +152,7 @@ MessengerUI, Messages, Pages) { h('i.fa.fa-file-o'), h('span.cp-button-name', Messages.toolbar_file) ])).appendTo($file).hide(); - var $drawerContent = $(h('div.'+ DRAWER_CLS, {tabindex: 1})).hide(); + var $drawerContent = $(h('ul.'+ DRAWER_CLS, {tabindex: 1})).hide(); UI.createDrawer($drawer, $drawerContent); } From 6f286581885baacd22c608486ec899c2290843c0 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 7 Dec 2023 13:11:42 +0200 Subject: [PATCH 03/40] changed `Theme` dropdown to be of type `ul` --- www/code/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/code/inner.js b/www/code/inner.js index 375423e02..1e6e601cb 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -86,7 +86,7 @@ define([ h('i.cptools.cptools-palette'), h('span.cp-button-name', Messages.toolbar_theme) ])); - var $content = $(h('div.cp-toolbar-drawer-content', { + var $content = $(h('ul.cp-toolbar-drawer-content', { tabindex: 1 })).hide(); From 03527bb78144b246154467b840684d51b6e35e2c Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 5 Jan 2024 14:35:00 +0200 Subject: [PATCH 04/40] `File` dropdown is accessible through up/down keys #1284 --- www/common/common-interface.js | 49 ++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 20c745777..a65e6192d 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1477,12 +1477,57 @@ define([ $button.removeClass('cp-toolbar-button-active'); if ($content.is(':visible')) { $button.addClass('cp-toolbar-button-active'); - $content.focus(); + $content.find(':first').focus(); var wh = $(window).height(); - $content.css('max-height', Math.floor(wh - topPos - 1)+'px'); + $content.css('max-height', Math.floor(wh - topPos - 1)+'px') + $content.children().addClass('active-element'); + } else { + $content.children().removeClass('active-element'); } }); + + var currentIndex = 0; + + $(document).keydown(function(e) { + if ($content.is(':visible')) { + var activeElements = $content.find('.active-element'); + switch (e.which) { + case 38: // Up + if (currentIndex > 0) { + currentIndex--; + } else { + currentIndex = activeElements.length - 1; + } + activeElements.eq(currentIndex).focus(); + break; + + case 40: // Down + if (currentIndex < activeElements.length - 1) { + currentIndex++; + } else { + currentIndex = 0; + } + activeElements.eq(currentIndex).focus(); + break; + + case 27: // Escape + $content.hide(); + $content.blur(); + break; + + case 9: // Tab + e.preventDefault(); + break; + + default: + return; + } + e.preventDefault(); + } + }); + + var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.attributeName === "style") { From dacb0578246f9663ab0760b776099ea808402916 Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 5 Jan 2024 16:49:49 +0200 Subject: [PATCH 05/40] dropdowns close when they are not in focus #1284 --- www/common/common-interface.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index a65e6192d..fa190b943 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1471,6 +1471,7 @@ define([ */ UI.createDrawer = function ($button, $content) { $button.attr('aria-expanded', false); + $button.click(function () { var topPos = $button[0].getBoundingClientRect().bottom; $content.toggle(); @@ -1480,7 +1481,7 @@ define([ $content.find(':first').focus(); var wh = $(window).height(); $content.css('max-height', Math.floor(wh - topPos - 1)+'px') - $content.children().addClass('active-element'); + $content.children(':visible').addClass('active-element'); } else { $content.children().removeClass('active-element'); } @@ -1557,6 +1558,14 @@ define([ $content.blur(); } }); + + $(document).click(function (e) { + var $target = $(e.target); + if (!$target.closest($content).length && !$target.closest($button).length) { + $content.hide(); + $button.removeClass('cp-toolbar-button-active'); + } + }); }; /* QR code generation is synchronous once the library is loaded From 34b13d13f22ed77814bac7bd5b49013c7abbc4a6 Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 5 Jan 2024 17:25:03 +0200 Subject: [PATCH 06/40] focus is set on the first available element #1284 --- www/common/common-interface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index fa190b943..1a662c4a5 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1478,10 +1478,10 @@ define([ $button.removeClass('cp-toolbar-button-active'); if ($content.is(':visible')) { $button.addClass('cp-toolbar-button-active'); - $content.find(':first').focus(); var wh = $(window).height(); $content.css('max-height', Math.floor(wh - topPos - 1)+'px') $content.children(':visible').addClass('active-element'); + $content.find('.active-element').first().focus(); } else { $content.children().removeClass('active-element'); } From 664faacde8acadde17fb955177684001e45941b2 Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 8 Jan 2024 15:22:27 +0200 Subject: [PATCH 07/40] focus is set on the buttons #1284 --- www/common/common-interface.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 1a662c4a5..597574841 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1480,14 +1480,23 @@ define([ $button.addClass('cp-toolbar-button-active'); var wh = $(window).height(); $content.css('max-height', Math.floor(wh - topPos - 1)+'px') - $content.children(':visible').addClass('active-element'); + $content.children(':visible').each(function() { + if ($(this).is('button')) { + $(this).addClass('active-element'); + } else if ($(this).find('button').length > 0) { + $(this).find('button').addClass('active-element'); + } + }); $content.find('.active-element').first().focus(); } else { - $content.children().removeClass('active-element'); + if ($(this).is('button')) { + $(this).removeClass('active-element'); + } else if ($(this).find('button').length > 0) { + $(this).find('button').removeClass('active-element'); + } } }); - var currentIndex = 0; $(document).keydown(function(e) { From 2231d20b480018484817af5c700106eb9467b5e6 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 10 Jan 2024 10:44:52 +0200 Subject: [PATCH 08/40] change span container to a li container --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 65f28c0c6..bc397414a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1488,7 +1488,7 @@ define([ } if (!config.container) { - $container = $('', containerConfig); + $container = $('
  • ', containerConfig); } // Button From f28255de7cf8a31ee71d82856723ddc38572cec5 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 8 Feb 2024 15:41:31 +0200 Subject: [PATCH 09/40] Revert "change span container to a li container" This reverts commit 2231d20b480018484817af5c700106eb9467b5e6. --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index bc397414a..65f28c0c6 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1488,7 +1488,7 @@ define([ } if (!config.container) { - $container = $('
  • ', containerConfig); + $container = $('', containerConfig); } // Button From de04ce68385ebf6dc922132ea0b90faa013c4318 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 8 Feb 2024 15:41:32 +0200 Subject: [PATCH 10/40] Revert "focus is set on the buttons #1284" This reverts commit 664faacde8acadde17fb955177684001e45941b2. --- www/common/common-interface.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 597574841..1a662c4a5 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1480,23 +1480,14 @@ define([ $button.addClass('cp-toolbar-button-active'); var wh = $(window).height(); $content.css('max-height', Math.floor(wh - topPos - 1)+'px') - $content.children(':visible').each(function() { - if ($(this).is('button')) { - $(this).addClass('active-element'); - } else if ($(this).find('button').length > 0) { - $(this).find('button').addClass('active-element'); - } - }); + $content.children(':visible').addClass('active-element'); $content.find('.active-element').first().focus(); } else { - if ($(this).is('button')) { - $(this).removeClass('active-element'); - } else if ($(this).find('button').length > 0) { - $(this).find('button').removeClass('active-element'); - } + $content.children().removeClass('active-element'); } }); + var currentIndex = 0; $(document).keydown(function(e) { From 803e4fc069ed4576d507330c39ea659d488d8aae Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 8 Feb 2024 15:41:33 +0200 Subject: [PATCH 11/40] Revert "focus is set on the first available element #1284" This reverts commit 34b13d13f22ed77814bac7bd5b49013c7abbc4a6. --- www/common/common-interface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 1a662c4a5..fa190b943 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1478,10 +1478,10 @@ define([ $button.removeClass('cp-toolbar-button-active'); if ($content.is(':visible')) { $button.addClass('cp-toolbar-button-active'); + $content.find(':first').focus(); var wh = $(window).height(); $content.css('max-height', Math.floor(wh - topPos - 1)+'px') $content.children(':visible').addClass('active-element'); - $content.find('.active-element').first().focus(); } else { $content.children().removeClass('active-element'); } From 7e85838777e044f59b0ae2522424b5db73c89dd5 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 8 Feb 2024 15:41:34 +0200 Subject: [PATCH 12/40] Revert "dropdowns close when they are not in focus #1284" This reverts commit dacb0578246f9663ab0760b776099ea808402916. --- www/common/common-interface.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index fa190b943..a65e6192d 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1471,7 +1471,6 @@ define([ */ UI.createDrawer = function ($button, $content) { $button.attr('aria-expanded', false); - $button.click(function () { var topPos = $button[0].getBoundingClientRect().bottom; $content.toggle(); @@ -1481,7 +1480,7 @@ define([ $content.find(':first').focus(); var wh = $(window).height(); $content.css('max-height', Math.floor(wh - topPos - 1)+'px') - $content.children(':visible').addClass('active-element'); + $content.children().addClass('active-element'); } else { $content.children().removeClass('active-element'); } @@ -1558,14 +1557,6 @@ define([ $content.blur(); } }); - - $(document).click(function (e) { - var $target = $(e.target); - if (!$target.closest($content).length && !$target.closest($button).length) { - $content.hide(); - $button.removeClass('cp-toolbar-button-active'); - } - }); }; /* QR code generation is synchronous once the library is loaded From fca3ac58abe18cdd81bdcff20987e35d036ca381 Mon Sep 17 00:00:00 2001 From: daria Date: Thu, 8 Feb 2024 15:41:35 +0200 Subject: [PATCH 13/40] Revert "`File` dropdown is accessible through up/down keys #1284" This reverts commit 03527bb78144b246154467b840684d51b6e35e2c. --- www/common/common-interface.js | 49 ++-------------------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index a65e6192d..20c745777 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1477,57 +1477,12 @@ define([ $button.removeClass('cp-toolbar-button-active'); if ($content.is(':visible')) { $button.addClass('cp-toolbar-button-active'); - $content.find(':first').focus(); + $content.focus(); var wh = $(window).height(); - $content.css('max-height', Math.floor(wh - topPos - 1)+'px') - $content.children().addClass('active-element'); - } else { - $content.children().removeClass('active-element'); + $content.css('max-height', Math.floor(wh - topPos - 1)+'px'); } }); - - var currentIndex = 0; - - $(document).keydown(function(e) { - if ($content.is(':visible')) { - var activeElements = $content.find('.active-element'); - switch (e.which) { - case 38: // Up - if (currentIndex > 0) { - currentIndex--; - } else { - currentIndex = activeElements.length - 1; - } - activeElements.eq(currentIndex).focus(); - break; - - case 40: // Down - if (currentIndex < activeElements.length - 1) { - currentIndex++; - } else { - currentIndex = 0; - } - activeElements.eq(currentIndex).focus(); - break; - - case 27: // Escape - $content.hide(); - $content.blur(); - break; - - case 9: // Tab - e.preventDefault(); - break; - - default: - return; - } - e.preventDefault(); - } - }); - - var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.attributeName === "style") { From 3b4dd5741a19b0fe654434a0e6e823f381728b8c Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 19 Feb 2024 18:07:23 +0200 Subject: [PATCH 14/40] `File` menu items are accessible using the keyboard WIP #1284 --- www/code/inner.js | 27 +++++-- www/common/common-ui-elements.js | 86 ++++++++++++++++++++++ www/common/sframe-app-framework.js | 113 +++++++++++++++++++++++++---- www/common/toolbar.js | 25 +++++-- 4 files changed, 224 insertions(+), 27 deletions(-) diff --git a/www/code/inner.js b/www/code/inner.js index 1e6e601cb..dc7f718a1 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -18,6 +18,7 @@ define([ '/common/TypingTests.js', '/customize/messages.js', 'cm/lib/codemirror', + '/common/common-ui-elements.js', 'css!cm/lib/codemirror.css', @@ -64,7 +65,8 @@ define([ Visible, TypingTest, Messages, - CMeditor) + CMeditor, + UIElements) { window.CodeMirror = CMeditor; @@ -106,7 +108,7 @@ define([ framework._.toolbar.$theme.append($showAuthorColorsButton); markers.setButton($showAuthorColorsButton); }; - var mkPrintButton = function (framework, $content, $print) { + var mkPrintButton = function (framework, $content) { var $printButton = framework._.sfCommon.createButton('print', true); $printButton.click(function () { $print.html($content.html()); @@ -114,7 +116,15 @@ define([ window.print(); framework.feedback('PRINT_CODE'); }); - framework._.toolbar.$drawer.append($printButton); + var $print = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $printButton.attr('class') }, + content: h('span', $printButton.text()), + action: function () { + $printButton.click(); + } + }); + framework._.toolbar.$drawer.append($print); }; var mkMarkdownTb = function (editor, framework) { var $codeMirrorContainer = $('#cp-app-code-container'); @@ -136,8 +146,15 @@ define([ var $codeMirrorContainer = $('#cp-app-code-container'); var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'code']); $codeMirrorContainer.prepend(helpMenu.menu); - - framework._.toolbar.$drawer.append(helpMenu.button); + var $helpMenuButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': helpMenu.button.attr('class') }, + content: h('span', helpMenu.button.text()), + action: function () { + helpMenu.button.click(); + } + }); + framework._.toolbar.$drawer.append($helpMenuButton); }; var previews = {}; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 65f28c0c6..4b4b3a48a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1455,6 +1455,92 @@ define([ }; }; + UIElements.createDropdownEntry = function (config) { + var allowedTags = ['a', 'li', 'p', 'hr', 'div']; + var isValidOption = function (o) { + if (typeof o !== "object") { return false; } + if (isElement(o)) { return true; } + if (!o.tag || allowedTags.indexOf(o.tag) === -1) { return false; } + return true; + }; + + var isElement = function (o) { + return /HTML/.test(Object.prototype.toString.call(o)) && + typeof(o.tagName) === 'string'; + }; + var entry; + + if (!isValidOption(config)) { + return null; + } + + if (isElement(config)) { + entry = $(config); + } else { + var $el = $(h(config.tag, (config.attributes || {}))); + + if (typeof(config.content) === 'string' || (config.content instanceof Element)) { + config.content = [config.content]; + } + + if (Array.isArray(config.content)) { + config.content.forEach(function (item) { + if (item instanceof Element) { + return void $el.append(item); + } + if (typeof(item) === 'string') { + $el[0].appendChild(document.createTextNode(item)); + } + }); + } + + // Everything is added as an "li" tag + // Links and items with action are focusable + // Add correct "role" attribute + entry = $(h('li')); + if (config.tag === 'a') { + $el.attr('tabindex', '-1'); + entry.attr('role', 'menuitem'); + entry.attr('tabindex', '0'); + } else if (config.tag === 'li') { + entry = $el; + entry.attr('role', 'menuitem'); + entry.attr('tabindex', '0'); + } else if (config.tag === 'hr') { + entry.attr('role', 'separator'); + } else { + entry.attr('role', 'none'); + } + entry.append($el); + + // Action can be triggered with a click or keyboard event + if (config.tag !== 'a' && config.tag !== 'li') { + return null; + } + + entry.on('mouseenter', (e) => { + e.stopPropagation(); + entry.focus(); + }); + + entry.on('click keydown', function(e) { + if (config.isSelect) { return; } + if (e.type === 'click' || (e.type === 'keydown' && e.keyCode === 13) || (e.type === 'keydown' && e.keyCode === 32)) { + e.stopPropagation(); + if (typeof(config.action) === "function") { + var close = config.action(e); + if (close) { hide(); } + } else { + // Click on with an href + if (e.type === 'keydown'){ $el.get(0).click(); } + } + } + }); + } + + return entry; + }; + // Create a button with a dropdown menu // input is a config object with parameters: // - container (optional): the dropdown container (span) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 05590ce47..19965de20 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -709,7 +709,7 @@ define([ var setFileExporter = function (extension, fe, async) { fileExporter = fe; - var $export = common.createButton('export', true, {}, function () { + var $exportButton = common.createButton('export', true, {}, function () { var ext = (typeof(extension) === 'function') ? extension() : extension; var suggestion = title.suggestTitle('cryptpad-document'); ext = ext || '.txt'; @@ -773,6 +773,14 @@ define([ }); $select.find('button').addClass('btn'); }); + var $export = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $exportButton.attr('class') }, + content: h('span', $exportButton.text()), + action: function () { + $exportButton.click(); + } + }); toolbar.$drawer.append($export); }; @@ -802,9 +810,16 @@ define([ onLocal(); }); }; - toolbar.$drawer.append( - common.createButton('import', true, options, fileImporter) - ); + var $importButton = common.createButton('import', true, options, fileImporter); + var $import = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $importButton.attr('class') }, + content: h('span', $importButton.text()), + action: function () { + $importButton.click(); + }, + }); + toolbar.$drawer.append($import); }; var feedback = function (action, force) { @@ -997,17 +1012,42 @@ define([ }, $toolbar: $(toolbarContainer) }; - var $hist = common.createButton('history', true, {histConfig: histConfig}); + var $histButton = common.createButton('history', true, {histConfig: histConfig}); + var $hist = UIElements.createDropdownEntry({ + tag: 'a', + attributes: {'class': $histButton.attr('class')}, + content: h('span', $histButton.text()), + action: function () { + $histButton.click(); + }, + }); toolbar.$drawer.append($hist); - var $snapshot = common.createButton('snapshots', true, { + var $snapshotButton = common.createButton('snapshots', true, { remove: deleteSnapshot, make: makeSnapshot, load: loadSnapshot }); + var $snapshot = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $snapshotButton.attr('class') }, + content: h('span', $snapshotButton.text()), + action: function () { + $snapshotButton.click(); + } + }); toolbar.$drawer.append($snapshot); - var $copy = common.createButton('copy', true); + + var $copyButton = common.createButton('copy', true); + var $copy = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $copyButton.attr('class') }, + content: h('span', $copyButton.text()), + action: function () { + $copyButton.click(); + } + }); toolbar.$drawer.append($copy); var $store = common.createButton('storeindrive', true); @@ -1019,26 +1059,71 @@ define([ getTitle: function () { return cpNfInner.metadataMgr.getMetadata().title; } }; var $templateButton = common.createButton('template', true, templateObj); - toolbar.$drawer.append($templateButton); + var $template = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $templateButton.attr('class') }, + content: h('span', $templateButton.text()), + action: function () { + $templateButton.click(); + } + }); + toolbar.$drawer.append($template); + } var $importTemplateButton = common.createButton('importtemplate', true); if (!readOnly) { - toolbar.$drawer.append($importTemplateButton); + var $importTemplate = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $importTemplateButton.attr('class') }, + content: h('span', $importTemplateButton.text()), + action: function () { + $importTemplateButton.click(); + } + }); + toolbar.$drawer.append($importTemplate); } - /* add a forget button */ - toolbar.$drawer.append(common.createButton('forget', true, {}, function (err) { + /* add a forget button = trash button */ + var $forgetButton = common.createButton('forget', true, {}, function (err) { if (err) { return; } stateChange(STATE.FORGOTTEN); - })); + }); + + var $forget = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $forgetButton.attr('class') }, + content: h('span', $forgetButton.text()), + action: function () { + $forgetButton.click(); + } + }); + + toolbar.$drawer.append($forget); + if (common.isLoggedIn()) { - var $tags = common.createButton('hashtag', true); + var $tagsButton = common.createButton('hashtag', true); + var $tags = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $tagsButton.attr('class') }, + content: h('span', $tagsButton.text()), + action: function () { + $tagsButton.click(); + } + }); toolbar.$drawer.append($tags); } - var $properties = common.createButton('properties', true); + var $propertiesButton = common.createButton('properties', true); + var $properties = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $propertiesButton.attr('class') }, + content: h('span', $propertiesButton.text()), + action: function () { + $propertiesButton.click(); + } + }); toolbar.$drawer.append($properties); createFilePicker(); diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 15110daa9..c5d078ca6 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -148,12 +148,13 @@ MessengerUI, Messages, Pages) { var $file = $toolbar.find('.'+BOTTOM_LEFT_CLS); if (!config.hideDrawer) { - var $drawer = $(h('button.' + FILE_CLS, [ - h('i.fa.fa-file-o'), - h('span.cp-button-name', Messages.toolbar_file) - ])).appendTo($file).hide(); - var $drawerContent = $(h('ul.'+ DRAWER_CLS, {tabindex: 1})).hide(); - UI.createDrawer($drawer, $drawerContent); + var $drawer = UIElements.createDropdown({ + text: Messages.toolbar_file, + options: [], + common: Common + }); + $drawer.addClass(FILE_CLS).appendTo($file); + $drawer.find('.cp-dropdown-content').addClass(DRAWER_CLS); } // The 'notitle' class removes the line added for the title with a small screen @@ -1000,8 +1001,16 @@ MessengerUI, Messages, Pages) { var createNewPad = function (toolbar, config) { var $button = Common.createButton('newpad', true); - toolbar.$drawer.append($button); - return $button; + var $newPad = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $button.attr('class') }, + content: h('span', $button.text()), + action: function () { + $button.click(); + } + }); + toolbar.$drawer.append($newPad); + return $newPad; }; var createUserAdmin = function (toolbar, config) { From 49b42e15f944374b124aa61f34b313a17f422e48 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 20 Feb 2024 15:19:22 +0200 Subject: [PATCH 15/40] `File` menu items (OnlyOffice) are accessible using the keyboard WIP #1284 --- www/common/onlyoffice/inner.js | 132 +++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 16 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index c45bb2b87..8f20f69ab 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -2733,12 +2733,14 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null }); }; - common.createButton('', true, { + var $historyButton = common.createButton('', true, { name: 'history', icon: 'fa-history', text: Messages.historyText, tippy: Messages.historyButton - }).click(function () { + }); + + $historyButton.click(function () { ooChannel.historyLastHash = ooChannel.lastHash; ooChannel.currentIndex = ooChannel.cpIndex; Feedback.send('OO_HISTORY'); @@ -2756,19 +2758,50 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null $toolbar: $('.cp-toolbar-container') }; History.create(common, histConfig); - }).appendTo(toolbar.$drawer); + }); + + var $historyDropdown = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $historyButton.attr('class') }, + content: [ + h('i', { 'class': $historyButton.children('i').attr('class') }), + h('span', $historyButton.text()) + ], + + action: function () { + $historyButton.click(); + } + }); + + $historyDropdown.appendTo(toolbar.$drawer); // Snapshots - var $snapshot = common.createButton('snapshots', true, { + var $snapshotButton = common.createButton('snapshots', true, { remove: deleteSnapshot, make: makeSnapshot, load: loadSnapshot }); + var $snapshot = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $snapshotButton.attr('class') }, + content: h('span', $snapshotButton.text()), + action: function () { + $snapshotButton.click(); + } + }); toolbar.$drawer.append($snapshot); // Import template - var $template = common.createButton('importtemplate', true, {}, openTemplatePicker); - if ($template && typeof($template.appendTo) === 'function') { + var $templateButton = common.createButton('importtemplate', true, {}, openTemplatePicker); + if ($templateButton && typeof($templateButton.appendTo) === 'function') { + var $template = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $templateButton.attr('class') }, + content: h('span', $templateButton.text()), + action: function () { + openTemplatePicker(); + } + }); $template.appendTo(toolbar.$drawer); } @@ -2792,7 +2825,15 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null } }; var $templateButton = common.createButton('template', true, templateObj); - toolbar.$drawer.append($templateButton); + var $template = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $templateButton.attr('class') }, + content: h('span', $templateButton.text()), + action: function () { + $templateButton.click(); + } + }); + toolbar.$drawer.append($template); } })(); } @@ -2816,7 +2857,15 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null }).attr('title', 'Restore last checkpoint').appendTo(toolbar.$bottomM); } - var $exportXLSX = common.createButton('export', true, {}, exportXLSXFile); + var $exportXLSXButton = common.createButton('export', true, {}, exportXLSXFile); + var $exportXLSX = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $exportXLSXButton.attr('class') }, + content: h('span', $exportXLSXButton.text()), + action: function () { + exportXLSXFile(); + } + }); $exportXLSX.appendTo(toolbar.$drawer); var type = privateData.ooType; @@ -2844,35 +2893,86 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null if (common.isLoggedIn()) { window.CryptPad_deleteLastCp = deleteLastCp; - var $importXLSX = common.createButton('import', true, { + var $importXLSXButton = common.createButton('import', true, { accept: accept, - binary : ["ods", "xlsx", "odt", "docx", "odp", "pptx"], + binary: ["ods", "xlsx", "odt", "docx", "odp", "pptx"], first: first, }, importXLSXFile); + var $importXLSX = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $importXLSXButton.attr('class') }, + content: h('span', $importXLSXButton.text()), + action: function () { + importXLSXFile(); + } + }); + // tag button + var $hashtagButton = common.createButton('hashtag', true); + var $hashtag = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $hashtagButton.attr('class') }, + content: h('span', $hashtagButton.text()), + action: function () { + $hashtagButton.click(); + } + }); $importXLSX.appendTo(toolbar.$drawer); - common.createButton('hashtag', true).appendTo(toolbar.$drawer); + $hashtag.appendTo(toolbar.$drawer); } var $store = common.createButton('storeindrive', true); toolbar.$drawer.append($store); - var $forget = common.createButton('forget', true, {}, function (err) { + // Move to trash button + var $forgetButton = common.createButton('forget', true, {}, function (err) { if (err) { return; } setEditable(false); }); + var $forget = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $forgetButton.attr('class') }, + content: h('span', $forgetButton.text()), + action: function () { + $forgetButton.click(); + } + }); toolbar.$drawer.append($forget); if (!privateData.isEmbed) { var helpMenu = APP.helpMenu = common.createHelpMenu(['beta', 'oo']); $('#cp-app-oo-editor').prepend(common.getBurnAfterReadingWarning()); $('#cp-app-oo-editor').prepend(helpMenu.menu); - toolbar.$drawer.append(helpMenu.button); + var $helpMenuButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': helpMenu.button.attr('class') }, + content: h('span', helpMenu.button.text()), + action: function () { + helpMenu.button.click(); + } + }); + toolbar.$drawer.append($helpMenuButton); } - var $properties = common.createButton('properties', true); + var $propertiesButton = common.createButton('properties', true); + var $properties = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $propertiesButton.attr('class') }, + content: h('span', $propertiesButton.text()), + action: function () { + $propertiesButton.click(); + } + }); toolbar.$drawer.append($properties); - - var $copy = common.createButton('copy', true); + + var $copyButton = common.createButton('copy', true); + var $copy = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $copyButton.attr('class') }, + content: h('span', $copyButton.text()), + action: function () { + $copyButton.click(); + } + }); toolbar.$drawer.append($copy); }; From 869e64a37c660240a16c1e4c813048747059fbac Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 20 Feb 2024 15:42:41 +0200 Subject: [PATCH 16/40] `File` menu items (Pad) are accessible using the keyboard WIP #1284 --- www/pad/inner.js | 53 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/www/pad/inner.js b/www/pad/inner.js index 4e3cdd97b..3dc955396 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -345,15 +345,36 @@ define([ }, function () { UI.alert(getSettings()); }); - framework._.toolbar.$drawer.append($settingsButton); + var $settings = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $settingsButton.attr('class') }, + content: [ + h('i', { 'class': $settingsButton.children('i').attr('class') }), + h('span', $settingsButton.text()) + ], + action: function () { + UI.alert(getSettings()); + } + }); + + framework._.toolbar.$drawer.append($settings); + }; var mkHelpMenu = function(framework) { var $toolbarContainer = $('.cke_toolbox_main'); var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'pad']); - $toolbarContainer.before(helpMenu.menu); + var $helpMenuButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': helpMenu.button.attr('class') }, + content: h('span', helpMenu.button.text()), + action: function () { + helpMenu.button.click(); + } + }); - framework._.toolbar.$drawer.append(helpMenu.button); + $toolbarContainer.before(helpMenu.menu); + framework._.toolbar.$drawer.append($helpMenuButton); }; var mkDiffOptions = function(cursor, readOnly) { @@ -618,17 +639,23 @@ define([ var mkPrintButton = function (framework, editor) { var $printButton = framework._.sfCommon.createButton('print', true); - $printButton.click(function () { - /* - // NOTE: alternative print system in case we keep having more issues on Firefox - var $iframe = $('html').find('iframe'); - var iframe = $iframe[0].contentWindow; - iframe.print(); - */ - editor.execCommand('print'); - framework.feedback('PRINT_PAD'); + var $print = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $printButton.attr('class') }, + content: h('span', $printButton.text()), + action: function () { + /* + // NOTE: alternative print system in case we keep having more issues on Firefox + var $iframe = $('html').find('iframe'); + var iframe = $iframe[0].contentWindow; + iframe.print(); + */ + editor.execCommand('print'); + framework.feedback('PRINT_PAD'); + } }); - framework._.toolbar.$drawer.append($printButton); + framework._.toolbar.$drawer.append($print); + }; var andThen2 = function(editor, Ckeditor, framework) { From 6f58e5a65fd159cb1ecf8d27f4d4a547ed929ed1 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 20 Feb 2024 15:51:32 +0200 Subject: [PATCH 17/40] `File` menu items (Kanban) are accessible using the keyboard WIP #1284 --- www/kanban/inner.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 22db72ca4..69fdb9207 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -1078,11 +1078,21 @@ define([ var $toolbarContainer = $('#cp-app-kanban-container'); var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']); - $toolbarContainer.prepend(helpMenu.menu); - framework._.toolbar.$drawer.append(helpMenu.button); + var $helpMenuButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': helpMenu.button.attr('class') }, + content: h('span', helpMenu.button.text()), + action: function () { + helpMenu.button.click(); + } + }); + + $toolbarContainer.prepend(helpMenu.menu); + framework._.toolbar.$drawer.append($helpMenuButton); }; + // Start of the main loop var andThen2 = function (framework) { From 2c4bedb056d957f0a6dc36769bc39da42d37bf1c Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 20 Feb 2024 15:57:15 +0200 Subject: [PATCH 18/40] `File` menu items (Form) are accessible using the keyboard WIP #1284 --- www/form/inner.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/www/form/inner.js b/www/form/inner.js index a95f256ae..0427f2454 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4494,8 +4494,16 @@ define([ var $toolbarContainer = $('#cp-toolbar'); var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'pad']); + var $helpMenuButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': helpMenu.button.attr('class') }, + content: h('span', helpMenu.button.text()), + action: function () { + helpMenu.button.click(); + } + }); $toolbarContainer.after(helpMenu.menu); - framework._.toolbar.$drawer.append(helpMenu.button); + framework._.toolbar.$drawer.append($helpMenuButton); if (!APP.isEditor && !priv.form_auditorKey) { $(helpMenu.menu).hide(); } From 05e1874f9bc306759fd67efa7467ae6cf50f8722 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 20 Feb 2024 16:11:25 +0200 Subject: [PATCH 19/40] `File` menu items (Whiteboard) are accessible using the keyboard WIP #1284 --- www/whiteboard/inner.js | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index f8b19efa2..414e5cf61 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -17,6 +17,7 @@ define([ '/whiteboard/colors.js', '/customize/application_config.js', '/components/chainpad/chainpad.dist.js', + '/common/common-ui-elements.js', '/lib/fabric.min.js', 'less!/whiteboard/app-whiteboard.less' @@ -34,7 +35,8 @@ define([ Messages, Colors, AppConfig, - ChainPad) + ChainPad, + UIElements) { var APP = window.APP = { @@ -298,8 +300,16 @@ define([ var mkHelpMenu = function (framework) { var $appContainer = $('#cp-app-whiteboard-container'); var helpMenu = framework._.sfCommon.createHelpMenu(['whiteboard']); + var $helpMenuButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': helpMenu.button.attr('class') }, + content: h('span', helpMenu.button.text()), + action: function () { + helpMenu.button.click(); + } + }); $appContainer.prepend(helpMenu.menu); - framework._.toolbar.$drawer.append(helpMenu.button); + framework._.toolbar.$drawer.append($helpMenuButton); }; // Start of the main loop @@ -434,13 +444,24 @@ define([ }); // Export to drive as PNG - framework._.sfCommon.createButton('savetodrive', true, {}).click(function () { - var defaultName = framework._.title.getTitle(); - UI.prompt(Messages.exportPrompt, defaultName + '.png', function (name) { - if (name === null || !name.trim()) { return; } - APP.upload(name); - }); - }).appendTo($drawer); + var $saveToDriveButton = framework._.sfCommon.createButton('savetodrive', true, {}); + var $saveToDrive = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $saveToDriveButton.attr('class') }, + content: [ + h('i', { 'class': $saveToDriveButton.children('i').attr('class') }), + h('span', $saveToDriveButton.text()) + ], + action: function () { + var defaultName = framework._.title.getTitle(); + UI.prompt(Messages.exportPrompt, defaultName + '.png', function (name) { + if (name === null || !name.trim()) { return; } + APP.upload(name); + }); + } + }); + $saveToDrive.appendTo($drawer); + } else { framework._.sfCommon.createButton('', true, { title: Messages.canvas_imageEmbed, From 517615463d45af44b6ce78398762438a5dba3fc7 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 20 Feb 2024 16:21:49 +0200 Subject: [PATCH 20/40] `File` menu items (Slides) are accessible using the keyboard WIP #1284 --- www/slide/inner.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/www/slide/inner.js b/www/slide/inner.js index fb1b51866..5eabc845d 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -16,6 +16,7 @@ define([ '/common/hyperscript.js', '/customize/messages.js', 'cm/lib/codemirror', + '/common/common-ui-elements.js', 'css!/components/bootstrap/dist/css/bootstrap.min.css', 'css!/components/components-font-awesome/css/font-awesome.min.css', @@ -61,7 +62,8 @@ define([ UI, h, Messages, - CMeditor) + CMeditor, + UIElements) { window.CodeMirror = CMeditor; @@ -123,14 +125,19 @@ define([ var mkPrintButton = function (framework, editor, $content, $print) { var $printButton = framework._.sfCommon.createButton('print', true); - $printButton.click(function () { - Slide.update(editor.getValue(), true); - $print.html($content.html()); - window.focus(); - window.print(); - framework.feedback('PRINT_SLIDES'); + var $print = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $printButton.attr('class') }, + content: h('span', $printButton.text()), + action: function () { + Slide.update(editor.getValue(), true); + $print.html($content.html()); + window.focus(); + window.print(); + framework.feedback('PRINT_SLIDES'); + } }); - framework._.toolbar.$drawer.append($printButton); + framework._.toolbar.$drawer.append($print); }; // Flag to check if a file from the filepicker is a mediatag for the slides or a background image @@ -436,9 +443,16 @@ define([ var mkHelpMenu = function (framework) { var $codeMirrorContainer = $('#cp-app-slide-editor-container'); var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'slide']); + var $helpMenuButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': helpMenu.button.attr('class') }, + content: h('span', helpMenu.button.text()), + action: function () { + helpMenu.button.click(); + } + }); $codeMirrorContainer.prepend(helpMenu.menu); - - framework._.toolbar.$drawer.append(helpMenu.button); + framework._.toolbar.$drawer.append($helpMenuButton); }; var activateLinks = function ($content, framework) { From f5c6fb9c678ea89763455bfbe1f934d26be959a7 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 20 Feb 2024 16:46:18 +0200 Subject: [PATCH 21/40] Help icon is visible bug fixed from File dropdown #1284 --- www/code/inner.js | 5 ++++- www/common/onlyoffice/inner.js | 5 ++++- www/form/inner.js | 5 ++++- www/kanban/inner.js | 5 ++++- www/pad/inner.js | 5 ++++- www/slide/inner.js | 5 ++++- www/whiteboard/inner.js | 5 ++++- 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/www/code/inner.js b/www/code/inner.js index dc7f718a1..f17d66b21 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -149,7 +149,10 @@ define([ var $helpMenuButton = UIElements.createDropdownEntry({ tag: 'a', attributes: { 'class': helpMenu.button.attr('class') }, - content: h('span', helpMenu.button.text()), + content: [ + h('i', { 'class': helpMenu.button.children('i').attr('class') }), + h('span', helpMenu.button.text()) + ], action: function () { helpMenu.button.click(); } diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 8f20f69ab..f47c2835f 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -2945,7 +2945,10 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null var $helpMenuButton = UIElements.createDropdownEntry({ tag: 'a', attributes: { 'class': helpMenu.button.attr('class') }, - content: h('span', helpMenu.button.text()), + content: [ + h('i', { 'class': helpMenu.button.children('i').attr('class') }), + h('span', helpMenu.button.text()) + ], action: function () { helpMenu.button.click(); } diff --git a/www/form/inner.js b/www/form/inner.js index 0427f2454..20cf23545 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4497,7 +4497,10 @@ define([ var $helpMenuButton = UIElements.createDropdownEntry({ tag: 'a', attributes: { 'class': helpMenu.button.attr('class') }, - content: h('span', helpMenu.button.text()), + content: [ + h('i', { 'class': helpMenu.button.children('i').attr('class') }), + h('span', helpMenu.button.text()) + ], action: function () { helpMenu.button.click(); } diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 69fdb9207..52d8320f2 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -1082,7 +1082,10 @@ define([ var $helpMenuButton = UIElements.createDropdownEntry({ tag: 'a', attributes: { 'class': helpMenu.button.attr('class') }, - content: h('span', helpMenu.button.text()), + content: [ + h('i', { 'class': helpMenu.button.children('i').attr('class') }), + h('span', helpMenu.button.text()) + ], action: function () { helpMenu.button.click(); } diff --git a/www/pad/inner.js b/www/pad/inner.js index 3dc955396..5d3ecafa2 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -367,7 +367,10 @@ define([ var $helpMenuButton = UIElements.createDropdownEntry({ tag: 'a', attributes: { 'class': helpMenu.button.attr('class') }, - content: h('span', helpMenu.button.text()), + content: [ + h('i', { 'class': helpMenu.button.children('i').attr('class') }), + h('span', helpMenu.button.text()) + ], action: function () { helpMenu.button.click(); } diff --git a/www/slide/inner.js b/www/slide/inner.js index 5eabc845d..bed3fb61f 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -446,7 +446,10 @@ define([ var $helpMenuButton = UIElements.createDropdownEntry({ tag: 'a', attributes: { 'class': helpMenu.button.attr('class') }, - content: h('span', helpMenu.button.text()), + content: [ + h('i', { 'class': helpMenu.button.children('i').attr('class') }), + h('span', helpMenu.button.text()) + ], action: function () { helpMenu.button.click(); } diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index 414e5cf61..58f63496d 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -303,7 +303,10 @@ define([ var $helpMenuButton = UIElements.createDropdownEntry({ tag: 'a', attributes: { 'class': helpMenu.button.attr('class') }, - content: h('span', helpMenu.button.text()), + content: [ + h('i', { 'class': helpMenu.button.children('i').attr('class') }), + h('span', helpMenu.button.text()) + ], action: function () { helpMenu.button.click(); } From 8175c001b255a3690cd96f521bbc6745456d8a7a Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 23 Feb 2024 15:14:31 +0200 Subject: [PATCH 22/40] Theme dropdown has accessible items WIP #1284 --- www/code/inner.js | 35 ++++++++++++++++++++------ www/common/common-ui-elements.js | 1 - www/common/sframe-common-codemirror.js | 28 ++++++++++++++++++--- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/www/code/inner.js b/www/code/inner.js index f17d66b21..ed779ac96 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -68,6 +68,7 @@ define([ CMeditor, UIElements) { + var Common; window.CodeMirror = CMeditor; var MEDIA_TAG_MODES = Object.freeze([ @@ -93,10 +94,13 @@ define([ })).hide(); // set up all the necessary events - UI.createDrawer($theme, $content); - - framework._.toolbar.$theme = $content; - framework._.toolbar.$bottomL.append($theme); + const $drawer = UIElements.createDropdown({ + text: Messages.toolbar_theme, + options: [], + common: Common + }); + framework._.toolbar.$theme = $drawer.find('ul.cp-dropdown-content'); + framework._.toolbar.$bottomL.append($drawer); }; var mkCbaButton = function (framework, markers) { @@ -105,8 +109,17 @@ define([ name: 'authormarks', icon: 'fa-paint-brush', }).hide(); - framework._.toolbar.$theme.append($showAuthorColorsButton); - markers.setButton($showAuthorColorsButton); + + var $showAuthorColors = UIElements.createDropdownEntry({ + tag: 'a', + attributes: {'class': $showAuthorColorsButton.attr('class')}, + content: h('span', $showAuthorColorsButton.text()), + action: function () { + $showAuthorColorsButton.click(); + }, + }); + framework._.toolbar.$theme.append($showAuthorColors); + markers.setButton($showAuthorColors); }; var mkPrintButton = function (framework, $content) { var $printButton = framework._.sfCommon.createButton('print', true); @@ -413,7 +426,15 @@ define([ setButton(!markers.getState()); UI.alert(content); }); - framework._.toolbar.$theme.append($cbaButton); + var $cba = UIElements.createDropdownEntry({ + tag: 'a', + attributes: {'class': $cbaButton.attr('class')}, + content: h('span', $cbaButton.text()), + action: function () { + $cbaButton.click(); + }, + }); + framework._.toolbar.$theme.append($cba); }; var mkFilePicker = function (framework, editor, evModeChange) { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 4b4b3a48a..580e9a23f 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1600,7 +1600,6 @@ define([ }).append($('', {'class': 'cp-dropdown-button-title'}).text(config.text || "")); } - if (config.caretDown) { $('', { 'class': 'fa fa-caret-down', diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 06fef19b0..169c23c8b 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -13,7 +13,8 @@ define([ '/common/common-util.js', '/common/text-cursor.js', '/components/chainpad/chainpad.dist.js', -], function ($, Modes, Themes, Messages, UIElements, MT, Hash, Util, TextCursor, ChainPad) { + '/common/hyperscript.js', +], function ($, Modes, Themes, Messages, UIElements, MT, Hash, Util, TextCursor, ChainPad, h) { var module = {}; var cursorToPos = module.cursorToPos = function(cursor, oldText) { @@ -390,7 +391,17 @@ define([ onLocal(); }); - if ($drawer) { $drawer.append($block); } + if ($drawer) { + var $blockButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: {'class': $block.find('button').attr('class')}, + content: h('span', $block.find('button').text()), + action: function () { + $block.click(); + }, + }); + $drawer.append($blockButton); + } if (exp.highlightMode) { exp.setMode(exp.highlightMode); } if (cb) { cb(); } }; @@ -454,7 +465,18 @@ define([ Common.setAttribute(themeKey, theme); }); - if ($drawer) { $drawer.append($block); } + if ($drawer) { + const $blockButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: {'class': $block.find('button').attr('class')}, + content: h('span', $block.find('button').text()), + action: function () { + $block.click(); + }, + }); + // $blockButton.append($block.find('ul')); + $drawer.append($blockButton); + } if (cb) { cb(); } }; Common.getAttribute(themeKey, todo); From 019fb2ff4a3f45bed2ea43e338822cfdb787cc4d Mon Sep 17 00:00:00 2001 From: daria Date: Mon, 26 Feb 2024 12:49:50 +0200 Subject: [PATCH 23/40] fix `File` dropdown style WIP #1284 --- customize.dist/src/less2/include/dropdown.less | 1 + 1 file changed, 1 insertion(+) diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index d5ed0955a..b6b83ad60 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -139,6 +139,7 @@ } li[role="menuitem"] { border-radius: @variables_radius; + white-space: nowrap; &:hover, &:focus { background-color: @cp_dropdown-bg-hover !important; color: @cp_dropdown-fg; From 53a7608b8e7424d61ca209529ea61b2a3f02b631 Mon Sep 17 00:00:00 2001 From: daria Date: Tue, 27 Feb 2024 17:43:06 +0200 Subject: [PATCH 24/40] dropdown buttons have icons WIP #1284 --- www/code/inner.js | 13 +++---------- www/common/toolbar.js | 4 +++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/www/code/inner.js b/www/code/inner.js index ed779ac96..fe8ca65c0 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -85,22 +85,15 @@ define([ ]); var mkThemeButton = function (framework) { - var $theme = $(h('button.cp-toolbar-appmenu', [ - h('i.cptools.cptools-palette'), - h('span.cp-button-name', Messages.toolbar_theme) - ])); - var $content = $(h('ul.cp-toolbar-drawer-content', { - tabindex: 1 - })).hide(); - - // set up all the necessary events const $drawer = UIElements.createDropdown({ text: Messages.toolbar_theme, options: [], - common: Common + common: Common, + buttonCls: 'cptools cptools-palette' }); framework._.toolbar.$theme = $drawer.find('ul.cp-dropdown-content'); framework._.toolbar.$bottomL.append($drawer); + $drawer.find('span').addClass('cp-button-name'); }; var mkCbaButton = function (framework, markers) { diff --git a/www/common/toolbar.js b/www/common/toolbar.js index c5d078ca6..803201371 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -151,10 +151,12 @@ MessengerUI, Messages, Pages) { var $drawer = UIElements.createDropdown({ text: Messages.toolbar_file, options: [], - common: Common + common: Common, + buttonCls: 'fa fa-file-o' }); $drawer.addClass(FILE_CLS).appendTo($file); $drawer.find('.cp-dropdown-content').addClass(DRAWER_CLS); + $drawer.find('span').addClass('cp-button-name'); } // The 'notitle' class removes the line added for the title with a small screen From 4c713ee80c5cf5d30f573b39397bf385027ac24d Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 28 Feb 2024 14:33:02 +0200 Subject: [PATCH 25/40] `Theme` items have icons WIP #1284 --- www/code/inner.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/www/code/inner.js b/www/code/inner.js index fe8ca65c0..492fc82e0 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -106,11 +106,15 @@ define([ var $showAuthorColors = UIElements.createDropdownEntry({ tag: 'a', attributes: {'class': $showAuthorColorsButton.attr('class')}, - content: h('span', $showAuthorColorsButton.text()), + content: [ + h('i', { 'class': $showAuthorColorsButton.children('i').attr('class') }), + h('span', $showAuthorColorsButton.text()) + ], action: function () { $showAuthorColorsButton.click(); }, }); + $showAuthorColors.find('span').addClass('cp-toolbar-name cp-toolbar-drawer-element'); framework._.toolbar.$theme.append($showAuthorColors); markers.setButton($showAuthorColors); }; @@ -422,7 +426,10 @@ define([ var $cba = UIElements.createDropdownEntry({ tag: 'a', attributes: {'class': $cbaButton.attr('class')}, - content: h('span', $cbaButton.text()), + content: [ + h('i', { 'class': $cbaButton.children('i').attr('class') }), + h('span', $cbaButton.text()) + ], action: function () { $cbaButton.click(); }, From c35771e59dd160e0752f06836393ebe698474a45 Mon Sep 17 00:00:00 2001 From: daria Date: Fri, 1 Mar 2024 15:54:08 +0200 Subject: [PATCH 26/40] `Theme` items(Slide) are accessible using the keyboard WIP #1284 --- www/slide/inner.js | 64 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/www/slide/inner.js b/www/slide/inner.js index bed3fb61f..ebd188af2 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -65,6 +65,7 @@ define([ CMeditor, UIElements) { + var Common; window.CodeMirror = CMeditor; var SLIDE_BACKCOLOR_ID = "cp-app-slide-toolbar-backcolor"; @@ -108,19 +109,15 @@ define([ }; var mkThemeButton = function (framework) { - var $theme = $(h('button.cp-toolbar-appmenu', [ - h('i.cptools.cptools-palette'), - h('span.cp-button-name', Messages.toolbar_theme) - ])); - var $content = $(h('div.cp-toolbar-drawer-content', { - tabindex: 1 - })).hide(); - - // set up all the necessary events - UI.createDrawer($theme, $content); - - framework._.toolbar.$theme = $content; - framework._.toolbar.$bottomL.append($theme); + const $drawer = UIElements.createDropdown({ + text: Messages.toolbar_theme, + options: [], + common: Common, + buttonCls: 'cptools cptools-palette' + }); + framework._.toolbar.$theme = $drawer.find('ul.cp-dropdown-content'); + framework._.toolbar.$bottomL.append($drawer); + $drawer.find('span').addClass('cp-button-name'); }; var mkPrintButton = function (framework, editor, $content, $print) { @@ -334,10 +331,20 @@ define([ text: Messages.slideOptionsText, name: 'options' }); - $optionsButton.click(function () { - $('body').append(createPrintDialog()); + var $options = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $optionsButton.attr('class') }, + content: [ + h('i', { 'class': $optionsButton.children('i').attr('class') }), + h('span', $optionsButton.text()) + ], + action: function () { + $options.click(function () { + $('body').append(createPrintDialog()); + }); + } }); - framework._.toolbar.$theme.append($optionsButton); + framework._.toolbar.$theme.append($options); metadataMgr.onChange(function () { var md = metadataMgr.getMetadata(); @@ -392,6 +399,17 @@ define([ $backgroundPicker.val(backColor); $backgroundPicker.click(); }); + var $backButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $back.attr('class'), 'id': SLIDE_BACKCOLOR_ID }, + content: [ + h('i', { 'class': $back.children('i').attr('class')}), + h('span', $back.text()) + ], + action: function () { + $back.click(); + } + }); var $foregroundPicker = $('', { type: 'color', value: textColor }) .css({ display: 'none', }) @@ -407,13 +425,25 @@ define([ $foregroundPicker.val(textColor); $foregroundPicker.click(); }); + + var $textButton = UIElements.createDropdownEntry({ + tag: 'a', + attributes: { 'class': $text.attr('class'),'id': SLIDE_COLOR_ID }, + content: [ + h('i', { 'class': $text.children('i').attr('class') }), + h('span', $text.text()) + ], + action: function () { + $text.click(); + } + }); var $testColor = $('', { type: 'color', value: '!' }); if ($testColor.attr('type') !== "color" || $testColor.val() === '!') { return; } $check.append($backgroundPicker); $check.append($foregroundPicker); - framework._.toolbar.$theme.append($text).append($back); + framework._.toolbar.$theme.append($textButton).append($backButton); metadataMgr.onChange(function () { var md = metadataMgr.getMetadata(); From 34c8266e438c1903bcae6552ade9495fc202f666 Mon Sep 17 00:00:00 2001 From: daria Date: Wed, 6 Mar 2024 14:43:50 +0200 Subject: [PATCH 27/40] delete unused code --- www/common/common-interface.js | 50 ---------------------------------- 1 file changed, 50 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 20c745777..6d6521e98 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1464,56 +1464,6 @@ define([ }; }; - /* Given two jquery objects (a 'button' and a 'drawer') - add handlers to make it such that clicking the button - displays the drawer contents, and blurring the button - hides the drawer content. Used for toolbar buttons at the moment. - */ - UI.createDrawer = function ($button, $content) { - $button.attr('aria-expanded', false); - $button.click(function () { - var topPos = $button[0].getBoundingClientRect().bottom; - $content.toggle(); - $button.removeClass('cp-toolbar-button-active'); - if ($content.is(':visible')) { - $button.addClass('cp-toolbar-button-active'); - $content.focus(); - var wh = $(window).height(); - $content.css('max-height', Math.floor(wh - topPos - 1)+'px'); - } - }); - - var observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.attributeName === "style") { - var isVisible = $content.is(':visible'); - $button.attr('aria-expanded', isVisible); - } - }); - }); - observer.observe($content[0], { attributes: true }); - - var onBlur = function (e) { - if (e.relatedTarget) { - var $relatedTarget = $(e.relatedTarget); - - if ($relatedTarget.is('.cp-toolbar-drawer-button')) { return; } - if ($relatedTarget.parents('.cp-toolbar-drawer-content').length) { - $relatedTarget.blur(onBlur); - return; - } - } - $button.removeClass('cp-toolbar-button-active'); - $content.hide(); - }; - $content.blur(onBlur).appendTo($button); - $('body').keydown(function (e) { - if (e.which === 27) { - $content.blur(); - } - }); - }; - /* QR code generation is synchronous once the library is loaded so this could be syncronous if we load the library separately. */ UI.createQRCode = function (data, _cb) { From 67cdadca1fb3138546cf4a725cfa91e51b8d1974 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 7 Mar 2024 18:39:10 +0100 Subject: [PATCH 28/40] Dropdown accessibility: handle submenus and other fixes --- customize.dist/src/less2/include/drive.less | 7 + .../src/less2/include/dropdown.less | 12 ++ customize.dist/src/less2/include/toolbar.less | 39 ++-- www/code/inner.js | 22 +-- www/code/markers.js | 4 +- www/common/common-ui-elements.js | 178 ++++++------------ www/common/common-util.js | 2 +- www/common/sframe-app-framework.js | 10 +- www/common/sframe-common-codemirror.js | 33 ++-- www/common/toolbar.js | 20 +- www/slide/inner.js | 4 +- 11 files changed, 155 insertions(+), 176 deletions(-) diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index 5137f07e0..239f73287 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -1038,6 +1038,13 @@ align-items: center; } } + .cp-dropdown-container { + button { + .cp-icon { + margin-left: 5px; + } + } + } } .cp-app-drive-button { diff --git a/customize.dist/src/less2/include/dropdown.less b/customize.dist/src/less2/include/dropdown.less index 46a4e9bd6..18aa262e2 100644 --- a/customize.dist/src/less2/include/dropdown.less +++ b/customize.dist/src/less2/include/dropdown.less @@ -48,6 +48,14 @@ position: relative; display: inline-block; + &.cp-dropdown-recursive { + position: unset; + .cp-dropdown-content { + top: 0; + left: 100%; + } + } + .fa { font-family: FontAwesome; } @@ -76,6 +84,10 @@ font: @dropdown_font; line-height: 1em; + &.cp-dropdown-has-submenu { + overflow: visible; + } + &.cp-dropdown-left { right: 0; margin-right: 5px; diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 12c3736fc..9f7c369f8 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -523,11 +523,6 @@ &::before { padding-top: 1px; } - // .cp-dropdown-button-title { - // transform: scale(0.5); - // bottom: -5px; - // right: -5px; - // } } } .cp-toolbar-user-dropdown { @@ -1053,7 +1048,7 @@ &.cp-toolbar-small { button { - .cp-toolbar-name, .cp-button-name { + .cp-toolbar-name, .cp-button-name, .cp-dropdown-button-title { display: none; } i, span { @@ -1127,27 +1122,29 @@ order: 8; } .cp-toolbar-file { - button { - &.fa-plus { order: 0; } - &.fa-history { order: 5; } - &.fa-hashtag { order: 10; } - &.fa-bookmark { order: 15; } - &.fa-upload { order: 20; } - &.fa-files-o { order: 25; } - &.fa-download { order: 30; } - &.fa-print { order: 35; } - &.fa-trash { order: 40; } - &.cp-toolbar-icon-pad-settings { order: 75; } - &.fa-info-circle { order: 100; } + li, button { + .fa-plus { order: 0; } + .fa-history { order: 5; } + .fa-hashtag { order: 10; } + .fa-bookmark { order: 15; } + .fa-upload { order: 20; } + .fa-files-o { order: 25; } + .fa-download { order: 30; } + .fa-print { order: 35; } + .fa-trash { order: 40; } + .cp-toolbar-icon-pad-settings { order: 75; } + .fa-info-circle { order: 100; } - &.cp-toolbar-icon-help { order: 150; } + .fa-question { order: 150; } + + span { order: 1000; } // text always after icon } } - .cp-toolbar-drawer-content:empty ~ .cp-toolbar-drawer-button { + .cp-toolbar-drawer-contentsss:empty ~ .cp-toolbar-drawer-button { display: none; } - .cp-toolbar-drawer-content { + .cp-toolbar-drawer-contentsss { box-shadow: 0px 1px 5px 0px @cp_shadow-color; border-radius: @variables_radius; overflow-y: auto; diff --git a/www/code/inner.js b/www/code/inner.js index 492fc82e0..a7d925932 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -89,11 +89,11 @@ define([ text: Messages.toolbar_theme, options: [], common: Common, - buttonCls: 'cptools cptools-palette' + iconCls: 'cptools cptools-palette' }); framework._.toolbar.$theme = $drawer.find('ul.cp-dropdown-content'); framework._.toolbar.$bottomL.append($drawer); - $drawer.find('span').addClass('cp-button-name'); + $drawer.addClass('cp-toolbar-appmenu'); }; var mkCbaButton = function (framework, markers) { @@ -105,15 +105,12 @@ define([ var $showAuthorColors = UIElements.createDropdownEntry({ tag: 'a', - attributes: {'class': $showAuthorColorsButton.attr('class')}, - content: [ - h('i', { 'class': $showAuthorColorsButton.children('i').attr('class') }), - h('span', $showAuthorColorsButton.text()) - ], + attributes: {'class': 'fa fa-paint-brush ' + $showAuthorColorsButton.attr('class')}, + content: h('span', $showAuthorColorsButton.text()), action: function () { $showAuthorColorsButton.click(); }, - }); + }).hide(); $showAuthorColors.find('span').addClass('cp-toolbar-name cp-toolbar-drawer-element'); framework._.toolbar.$theme.append($showAuthorColors); markers.setButton($showAuthorColors); @@ -425,16 +422,13 @@ define([ }); var $cba = UIElements.createDropdownEntry({ tag: 'a', - attributes: {'class': $cbaButton.attr('class')}, - content: [ - h('i', { 'class': $cbaButton.children('i').attr('class') }), - h('span', $cbaButton.text()) - ], + attributes: {'class': 'fa fa-paint-brush ' + $cbaButton.attr('class')}, + content: h('span', $cbaButton.text()), action: function () { $cbaButton.click(); }, }); - framework._.toolbar.$theme.append($cba); + framework._.toolbar.$theme.prepend($cba); // Put at the top }; var mkFilePicker = function (framework, editor, evModeChange) { diff --git a/www/code/markers.js b/www/code/markers.js index 0d53b5dd8..c96d22168 100644 --- a/www/code/markers.js +++ b/www/code/markers.js @@ -624,11 +624,11 @@ define([ if (Env.opacity) { Env.opacity = 0; $button.find('.cp-toolbar-drawer-element').text(Messages.cba_show); - $button.removeClass("cp-toolbar-button-active"); + //$button.removeClass("cp-toolbar-button-active"); } else { Env.opacity = MARK_OPACITY; $button.find('.cp-toolbar-drawer-element').text(Messages.cba_hide); - $button.addClass("cp-toolbar-button-active"); + //$button.addClass("cp-toolbar-button-active"); } }; toggle(); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index b36a2f834..ab619490a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -859,10 +859,9 @@ define([ ])).click(common.prepareFeedback(type)); break; case 'storeindrive': - button = $(h('button.cp-toolbar-storeindrive', { + button = $(h('button.cp-toolbar-storeindrive.fa.fa-hdd-o', { style: 'display:none;' }, [ - h('i.fa.fa-hdd-o'), h('span.cp-toolbar-name.cp-toolbar-drawer-element', Messages.toolbar_storeInDrive) ])).click(common.prepareFeedback(type)).click(function () { $(button).hide(); @@ -1454,7 +1453,12 @@ define([ }; UIElements.createDropdownEntry = function (config) { + var hide = function () {}; var allowedTags = ['a', 'li', 'p', 'hr', 'div']; + var isElement = function (o) { + return /HTML/.test(Object.prototype.toString.call(o)) && + typeof(o.tagName) === 'string'; + }; var isValidOption = function (o) { if (typeof o !== "object") { return false; } if (isElement(o)) { return true; } @@ -1462,15 +1466,8 @@ define([ return true; }; - var isElement = function (o) { - return /HTML/.test(Object.prototype.toString.call(o)) && - typeof(o.tagName) === 'string'; - }; var entry; - - if (!isValidOption(config)) { - return null; - } + if (!isValidOption(config)) { return; } if (isElement(config)) { entry = $(config); @@ -1512,18 +1509,14 @@ define([ entry.append($el); // Action can be triggered with a click or keyboard event - if (config.tag !== 'a' && config.tag !== 'li') { - return null; - } + if (config.tag === 'a' || config.tag === 'li') { + entry.on('mouseenter', (e) => { + e.stopPropagation(); + entry.focus(); + }); - entry.on('mouseenter', (e) => { - e.stopPropagation(); - entry.focus(); - }); - - entry.on('click keydown', function(e) { - if (config.isSelect) { return; } - if (e.type === 'click' || (e.type === 'keydown' && e.keyCode === 13) || (e.type === 'keydown' && e.keyCode === 32)) { + Util.onClickEnter(entry, function(e) { + if (config.isSelect) { return; } e.stopPropagation(); if (typeof(config.action) === "function") { var close = config.action(e); @@ -1532,10 +1525,17 @@ define([ // Click on with an href if (e.type === 'keydown'){ $el.get(0).click(); } } - } - }); + }, {space: true}); + } } + hide = function () { + window.setTimeout(function () { + entry.closest('ul.cp-dropdown-content').hide(); + entry.parents('.cp-dropdown-content').removeClass('cp-dropdown-has-submenu').hide(); + }, 0); + }; + return entry; }; @@ -1576,37 +1576,23 @@ define([ } // Button - var $button; - - if (config.buttonContent) { - $button = $(h('button', { - class: config.buttonCls || '', - 'aria-haspopup': 'menu', - 'aria-expanded': 'false', - 'title': config.buttonTitle || '', - 'aria-label': config.buttonTitle || '', - }, [ - h('span.cp-dropdown-button-title', config.buttonContent), - ])); - } else { - $button = $('