diff --git a/src/assets/new-project/code-editor.html b/src/assets/new-project/code-editor.html
index 88a4fa7830..594ff0c001 100644
--- a/src/assets/new-project/code-editor.html
+++ b/src/assets/new-project/code-editor.html
@@ -70,7 +70,7 @@
-
{{CMD_TOGGLE_RECENT_PROJECTS}}
+ {{CMD_RECENT_PROJECTS}}
diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json
index cd3b643e96..8674ac0619 100644
--- a/src/base-config/keyboard.json
+++ b/src/base-config/keyboard.json
@@ -338,10 +338,5 @@
],
"help.docs": [
"Shift-F1"
- ],
- "recentProjects.toggle": [
- {
- "key": "Ctrl-Alt-R"
- }
]
}
diff --git a/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html b/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html
index cf84bd6fa6..8055627445 100644
--- a/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html
+++ b/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html
@@ -13,6 +13,7 @@
{{folder}}
{{rest}}
+ ×
{{/projectList}}
diff --git a/src/extensionsIntegrated/RecentProjects/main.js b/src/extensionsIntegrated/RecentProjects/main.js
index ee5da356ee..34b1ea25f1 100644
--- a/src/extensionsIntegrated/RecentProjects/main.js
+++ b/src/extensionsIntegrated/RecentProjects/main.js
@@ -31,7 +31,6 @@ define(function (require, exports, module) {
Commands = require("command/Commands"),
CommandManager = require("command/CommandManager"),
Menus = require("command/Menus"),
- MainViewManager = require("view/MainViewManager"),
FileSystem = require("filesystem/FileSystem"),
AppInit = require("utils/AppInit"),
KeyEvent = require("utils/KeyEvent"),
@@ -47,16 +46,11 @@ define(function (require, exports, module) {
ExtensionInterface.registerExtensionInterface(RECENT_PROJECTS_INTERFACE, exports);
const RECENT_PROJECT_STATE = "recentProjects";
- /** @const {string} Recent Projects commands ID */
- let TOGGLE_DROPDOWN = "recentProjects.toggle";
-
/** @const {number} Maximum number of displayed recent projects */
var MAX_PROJECTS = 20;
/** @type {$.Element} jQuery elements used for the dropdown menu */
- var $dropdownItem,
- $dropdown,
- $links;
+ let $dropdown;
/**
* Get the stored list of recent projects, fixing up paths as appropriate.
@@ -95,24 +89,6 @@ define(function (require, exports, module) {
PreferencesManager.setViewState(RECENT_PROJECT_STATE, recentProjects);
}
- /**
- * Check the list of items to see if any of them are hovered, and if so trigger a mouseenter.
- * Normally the mouseenter event handles this, but when a previous item is deleted and the next
- * item moves up to be underneath the mouse, we don't get a mouseenter event for that item.
- */
- function checkHovers(pageX, pageY) {
- $dropdown.children().each(function () {
- var offset = $(this).offset(),
- width = $(this).outerWidth(),
- height = $(this).outerHeight();
-
- if (pageX >= offset.left && pageX <= offset.left + width &&
- pageY >= offset.top && pageY <= offset.top + height) {
- $(".recent-folder-link", this).triggerHandler("mouseenter");
- }
- });
- }
-
function removeFromRecentProject(fullPath) {
fullPath = FileUtils.stripTrailingSlash(fullPath);
let recentProjects = getRecentProjects(),
@@ -128,195 +104,27 @@ define(function (require, exports, module) {
}
/**
- * Create the "delete" button that shows up when you hover over a project.
+ * Handles the Key Down events
+ * @param {KeyboardEvent} event
+ * @param $popUp
+ * @return {boolean} True if the key was handled
*/
- function renderDelete() {
- return $("×
")
- .mouseup(function (e) {
- // Don't let the click bubble upward.
- e.stopPropagation();
-
+ function _handlePopupKeyEvents(event, $popUp) {
+ if(event.keyCode === KeyEvent.DOM_VK_DELETE){
+ event.stopPropagation();
+ const $selectedItem = $popUp.find(".selected");
+ if ($selectedItem.length && $selectedItem.data("path")) {
// Remove the project from the preferences.
- removeFromRecentProject($(this).parent().data("path"));
- $(this).closest("li").remove();
- checkHovers(e.pageX, e.pageY);
+ removeFromRecentProject($selectedItem.data("path"));
+ PopUpManager.selectNextItem(+1, $popUp);
+ $selectedItem.closest("li").remove();
if (getRecentProjects().length === 1) {
$dropdown.find(".divider").remove();
}
- });
- }
-
- /**
- * Hide the delete button.
- */
- function removeDeleteButton() {
- $("#recent-folder-delete").remove();
- }
-
- /**
- * Show the delete button over a given target.
- */
- function addDeleteButton($target) {
- removeDeleteButton();
- renderDelete()
- .css("top", $target.position().top + 6)
- .appendTo($target);
- }
-
-
- /**
- * Selects the next or previous item in the list
- * @param {number} direction +1 for next, -1 for prev
- */
- function selectNextItem(direction) {
- let $links = $dropdown.find("a:visible"),
- index = $dropdownItem ? $links.index($dropdownItem) : (direction > 0 ? -1 : 0),
- $newItem = $links.eq((index + direction) % $links.length);
-
- if(searchStr && $links.length === 1){
- // no search result, only the top search field visible
- return;
- }
- if($newItem.parent().hasClass("sticky-li-top")) {
- if(index === -1){
- index = 0;
}
- $newItem = $links.eq((index + direction) % $links.length);
- }
- if ($dropdownItem) {
- $dropdownItem.removeClass("selected");
+ return true;
}
- $newItem.addClass("selected");
-
- $dropdownItem = $newItem;
- removeDeleteButton();
- }
-
- let searchStr ="";
- /**
- * hides all elements in popup that doesn't match the given search string, also shows the search bar in popup
- * @param searchString
- */
- function filterDropdown(searchString) {
- searchStr = searchString;
- const $stickyLi = $dropdown.find('li.sticky-li-top');
- if(searchString){
- $stickyLi.removeClass("forced-hidden");
- } else {
- $stickyLi.addClass("forced-hidden");
- }
-
- $dropdown.find('li').each(function(index, li) {
- if(index === 0){
- // this is the top search box itself
- return;
- }
- const $li = $(li);
- if(!$li.text().toLowerCase().includes(searchString.toLowerCase())){
- $li.addClass("forced-hidden");
- } else {
- $li.removeClass("forced-hidden");
- }
- });
-
- if(searchString) {
- $stickyLi.removeClass('forced-hidden');
- $stickyLi.find('.searchTextSpan').text(searchString);
- } else {
- $stickyLi.addClass('forced-hidden');
- }
- }
-
- /**
- * Deletes the selected item and
- * move the focus to next item in list.
- *
- * @return {boolean} TRUE if project is removed
- */
- function removeSelectedItem(e) {
- var recentProjects = getRecentProjects(),
- $cacheItem = $dropdownItem,
- index = recentProjects.indexOf($cacheItem.data("path"));
-
- // When focus is not on project item
- if (index === -1) {
- return false;
- }
-
- // remove project
- recentProjects.splice(index, 1);
- PreferencesManager.setViewState(RECENT_PROJECT_STATE, recentProjects);
- checkHovers(e.pageX, e.pageY);
-
- if (recentProjects.length === 1) {
- $dropdown.find(".divider").remove();
- }
- selectNextItem(+1);
- $cacheItem.closest("li").remove();
- return true;
- }
-
- /**
- * Handles the Key Down events
- * @param {KeyboardEvent} event
- * @return {boolean} True if the key was handled
- */
- function keydownHook(event) {
- var keyHandled = false;
-
- switch (event.keyCode) {
- case KeyEvent.DOM_VK_UP:
- selectNextItem(-1);
- keyHandled = true;
- break;
- case KeyEvent.DOM_VK_DOWN:
- selectNextItem(+1);
- keyHandled = true;
- break;
- case KeyEvent.DOM_VK_ENTER:
- case KeyEvent.DOM_VK_RETURN:
- if ($dropdownItem) {
- $dropdownItem.trigger("click");
- }
- keyHandled = true;
- break;
- case KeyEvent.DOM_VK_DELETE:
- if ($dropdownItem) {
- removeSelectedItem(event);
- }
- keyHandled = true;
- break;
- }
-
- if(keyHandled){
- event.stopImmediatePropagation();
- event.preventDefault();
- return keyHandled;
- } else if((event.ctrlKey || event.metaKey) && event.key === 'v') {
- Phoenix.app.clipboardReadText().then(text=>{
- searchStr += text;
- filterDropdown(searchStr);
- });
- keyHandled = true;
- } else if (event.key.length === 1) {
- searchStr += event.key;
- keyHandled = true;
- } else if (event.key === 'Backspace') {
- // Remove the last character when Backspace is pressed
- searchStr = searchStr.slice(0, -1);
- keyHandled = true;
- } else {
- // bubble up, not for us to handle
- return false;
- }
- filterDropdown(searchStr);
-
- if (keyHandled) {
- event.stopImmediatePropagation();
- event.preventDefault();
- }
- return keyHandled;
}
@@ -330,7 +138,6 @@ define(function (require, exports, module) {
if ($dropdown) {
PopUpManager.removePopUp($dropdown);
}
- searchStr = "";
}
/**
@@ -342,9 +149,6 @@ define(function (require, exports, module) {
$("#project-files-container").off("scroll", closeDropdown);
$("#titlebar .nav").off("click", closeDropdown);
$dropdown = null;
-
- $(window).off("keydown", keydownHook);
- searchStr = "";
}
function openProjectWithPath(fullPath) {
@@ -374,6 +178,18 @@ define(function (require, exports, module) {
*/
function _handleListEvents() {
$dropdown
+ .on("click", ".recent-project-delete", function (e) {
+ // Don't let the click bubble upward.
+ e.stopPropagation();
+
+ // Remove the project from the preferences.
+ removeFromRecentProject($(this).parent().data("path"));
+ $(this).closest("li").remove();
+
+ if (getRecentProjects().length === 1) {
+ $dropdown.find(".divider").remove();
+ }
+ })
.on("click", "a", function () {
var $link = $(this),
id = $link.attr("id"),
@@ -391,28 +207,6 @@ define(function (require, exports, module) {
CommandManager.execute(Commands.FILE_DOWNLOAD_PROJECT);
}
- })
- .on("mouseenter", "a", function () {
- if ($dropdownItem) {
- $dropdownItem.removeClass("selected");
- }
- $dropdownItem = $(this).addClass("selected");
-
- if ($dropdownItem.hasClass("recent-folder-link")) {
- // Note: we can't depend on the event here because this can be triggered
- // manually from checkHovers().
- addDeleteButton($(this));
- }
- })
- .on("mouseleave", "a", function () {
- var $link = $(this).removeClass("selected");
-
- if ($link.get(0) === $dropdownItem.get(0)) {
- $dropdownItem = null;
- }
- if ($link.hasClass("recent-folder-link")) {
- removeDeleteButton();
- }
});
}
@@ -483,6 +277,10 @@ define(function (require, exports, module) {
.appendTo($("body"));
PopUpManager.addPopUp($dropdown, cleanupDropdown, true, {closeCurrentPopups: true});
+ PopUpManager.handleSelectionEvents($dropdown, {
+ enableSearchFilter: true,
+ keyboardEventHandler: _handlePopupKeyEvents
+ });
// TODO: should use capture, otherwise clicking on the menus doesn't close it. More fallout
// from the fact that we can't use the Boostrap (1.4) dropdowns.
@@ -503,38 +301,8 @@ define(function (require, exports, module) {
$("#titlebar .nav").on("click", closeDropdown);
_handleListEvents();
- $(window).on("keydown", keydownHook);
}
-
- /**
- * Show or hide the recent projects dropdown from the toogle command.
- */
- function handleKeyEvent() {
- if (!$dropdown) {
- if (!SidebarView.isVisible()) {
- SidebarView.show();
- }
-
- $("#project-dropdown-toggle").trigger("click");
-
- $dropdown.focus();
- $links = $dropdown.find("a");
- // By default, select the most recent project (which is at the top of the list underneath Open Folder),
- // but if there are none, select Open Folder instead.
- $dropdownItem = $links.eq($links.length > 1 ? 1 : 0);
- $dropdownItem.addClass("selected");
-
- // If focusing the dropdown caused a modal bar to close, we need to refocus the dropdown
- window.setTimeout(function () {
- $dropdown.focus();
- }, 0);
- }
- }
-
- // Register command handlers
- CommandManager.register(Strings.CMD_TOGGLE_RECENT_PROJECTS, TOGGLE_DROPDOWN, handleKeyEvent);
-
// Initialize extension
AppInit.appReady(function () {
PreferencesManager.stateManager.definePreference(RECENT_PROJECT_STATE, 'array', [])
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 6289789240..803168b3da 100644
--- a/src/nls/root/strings.js
+++ b/src/nls/root/strings.js
@@ -954,7 +954,7 @@ define({
"TOOLTIP_CLICK_TO_EDIT_COLOR": "Click here to edit color",
// extensions/default/RecentProjects
- "CMD_TOGGLE_RECENT_PROJECTS": "Recent Projects",
+ "CMD_RECENT_PROJECTS": "Recent Projects",
"REMOVE_FROM_RECENT_PROJECTS": "Remove from Recent Projects",
// extensions/default/MDNDocs
diff --git a/src/styles/Extn-RecentProjects.less b/src/styles/Extn-RecentProjects.less
index 2465b24770..c77967714a 100644
--- a/src/styles/Extn-RecentProjects.less
+++ b/src/styles/Extn-RecentProjects.less
@@ -99,6 +99,16 @@
#project-dropdown.dropdown-menu li a {
padding: 5px 15px;
+ position: relative;
+}
+
+.recent-project-delete {
+ visibility: hidden;
+}
+
+.recent-folder-link:hover > .recent-project-delete {
+ top: 26%;
+ visibility: visible;
}
#project-dropdown.dropdown-menu .recent-folder-link, #project-dropdown.dropdown-menu #open-folder-link #new-project-link #download-project-link{
diff --git a/src/widgets/PopUpManager.js b/src/widgets/PopUpManager.js
index 094f09be57..99481f03b6 100644
--- a/src/widgets/PopUpManager.js
+++ b/src/widgets/PopUpManager.js
@@ -88,6 +88,19 @@ define(function (require, exports, module) {
$popUp.off("keydown", _processSelectionEvent);
$popUp.on("keydown", _processSelectionEvent);
$popUp.focus();
+ function _selectItem() {
+ $popUp.find(".selected").removeClass("selected");
+ $(this).addClass("selected");
+ }
+ function _unselectItem() {
+ $(this).removeClass("selected");
+ }
+ $popUp
+ .off("mouseenter", "a", _selectItem)
+ .off("mouseleave", "a", _unselectItem);
+ $popUp
+ .on("mouseenter", "a", _selectItem)
+ .on("mouseleave", "a", _unselectItem);
}
/**
@@ -207,7 +220,7 @@ define(function (require, exports, module) {
/**
- * Selects the next or previous item in the list
+ * Selects the next or previous item in the popup.
* @param {number} direction +1 for next, -1 for prev
* @param $popUp
*/
@@ -246,7 +259,7 @@ define(function (require, exports, module) {
return false;
}
if(keyboardEventHandler) {
- const processed = keyboardEventHandler(event);
+ const processed = keyboardEventHandler(event, $popUp);
if(processed){
return true;
}
@@ -367,6 +380,7 @@ define(function (require, exports, module) {
exports.addPopUp = addPopUp;
exports.handleSelectionEvents = handleSelectionEvents;
+ exports.selectNextItem = selectNextItem;
exports.removePopUp = removePopUp;
exports.closeAllPopups = closeAllPopups;
exports.listenToContextMenu = listenToContextMenu;
diff --git a/test/spec/Extn-RecentProjects-integ-test.js b/test/spec/Extn-RecentProjects-integ-test.js
index 357bfe8e9c..9ae0de9d1f 100644
--- a/test/spec/Extn-RecentProjects-integ-test.js
+++ b/test/spec/Extn-RecentProjects-integ-test.js
@@ -24,10 +24,8 @@
define(function (require, exports, module) {
- var SpecRunnerUtils = require("spec/SpecRunnerUtils"),
- FileUtils = require("file/FileUtils"),
- KeyEvent = require("utils/KeyEvent"),
- _ = require("thirdparty/lodash");
+ const SpecRunnerUtils = require("spec/SpecRunnerUtils"),
+ KeyEvent = require("utils/KeyEvent");
describe("integration:Recent Projects", function () {
const testFolder = SpecRunnerUtils.getTestPath("/spec/LiveDevelopment-MultiBrowser-test-files"),
@@ -36,17 +34,12 @@ define(function (require, exports, module) {
const testFolderProjectName = "LiveDevelopment-MultiBrowser-test-files",
prettierTestFolderProjectName = "prettier-test-files",
jsUtilsTestFolderProjectName = "JSUtils-test-files";
- let extensionPath = FileUtils.getNativeModuleDirectoryPath(module),
- testWindow,
- $,
- CommandManager,
- PreferencesManager;
+ let testWindow,
+ $;
beforeAll(async function () {
testWindow = await SpecRunnerUtils.createTestWindowAndRun();
$ = testWindow.$;
- CommandManager = testWindow.brackets.test.CommandManager;
- PreferencesManager = testWindow.brackets.test.PreferencesManager;
}, 30000);
afterAll(async function () {
@@ -55,7 +48,9 @@ define(function (require, exports, module) {
}, 30000);
async function openRecentProjectDropDown() {
- CommandManager.execute("recentProjects.toggle");
+ if(!$("#project-dropdown").is(":visible")){
+ $("#project-dropdown-toggle").click();
+ }
await awaitsFor(function () {
return $("#project-dropdown").is(":visible");
});