Don't accept keyboard input before new-collection prompt appears

Fixes #1613
This commit is contained in:
Dan Stillman 2018-12-27 07:11:15 -05:00
parent 4bc8fab4f5
commit 7575cd8b29
5 changed files with 92 additions and 44 deletions

View file

@ -107,14 +107,16 @@ var ZoteroAdvancedSearch = new function() {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService); .getService(Components.interfaces.nsIPromptService);
var untitled = yield Zotero.DB.getNextName( var libraryID = _searchBox.search.libraryID;
_searchBox.search.libraryID,
'savedSearches', var searches = yield Zotero.Searches.getAll(libraryID)
'savedSearchName', var prefix = Zotero.getString('pane.collections.untitled');
Zotero.getString('pane.collections.untitled') var name = Zotero.Utilities.Internal.getNextName(
prefix,
searches.map(s => s.name).filter(n => n.startsWith(prefix))
); );
var name = { value: untitled }; var name = { value: name };
var result = promptService.prompt(window, var result = promptService.prompt(window,
Zotero.getString('pane.collections.newSavedSeach'), Zotero.getString('pane.collections.newSavedSeach'),
Zotero.getString('pane.collections.savedSearchName'), name, "", {}); Zotero.getString('pane.collections.savedSearchName'), name, "", {});

View file

@ -104,8 +104,8 @@ Zotero.Searches = function() {
this.getNextName = async function (libraryID, name) { this.getNextName = async function (libraryID, name) {
// Trim '(1)', etc. // Trim '1', etc.
var matches = name.match(/^(.+) \(\d+\)$/); var matches = name.match(/^(.+) \d+$/);
if (matches) { if (matches) {
name = matches[1].trim(); name = matches[1].trim();
} }
@ -115,7 +115,7 @@ Zotero.Searches = function() {
sql, sql,
[libraryID, Zotero.DB.escapeSQLExpression(name) + '%'] [libraryID, Zotero.DB.escapeSQLExpression(name) + '%']
); );
return Zotero.Utilities.Internal.getNextName(name, names); return Zotero.Utilities.Internal.getNextName(name, names, true);
}; };

View file

@ -1194,23 +1194,51 @@ Zotero.Utilities.Internal = {
/** /**
* Get the next available numbered name that matches a base name, for use when duplicating * Get the next available numbered name that matches a base name, for use when duplicating
* *
* - Given 'Foo' and ['Foo'], returns 'Foo (1)'. * - Given 'Foo' and ['Foo'], returns 'Foo 1'.
* - Given 'Foo (1)' and ['Foo', 'Foo (1)'], returns 'Foo (2)' * - Given 'Foo' and ['Foo', 'Foo 1'], returns 'Foo 2'.
* - Given 'Foo' and ['Foo', 'Foo 1'], returns 'Foo 2'.
* - Given 'Foo 1', ['Foo', 'Foo 1'], and trim=true, returns 'Foo 2'
* - Given 'Foo' and ['Foo', 'Foo 2'], returns 'Foo 1'
*/ */
getNextName: function (name, existingNames) { getNextName: function (name, existingNames, trim = false) {
// Trim '(1)', etc. // Trim numbers at end of given name
var matches = name.match(/^(.+) \(\d+\)$/); if (trim) {
if (matches) { let matches = name.match(/^(.+) \d+$/);
name = matches[1].trim(); if (matches) {
} name = matches[1].trim();
var highest = 0;
for (let existingName of existingNames) {
let matches = existingName.match(/ \((\d+)\)$/);
if (matches && matches[1] > highest) {
highest = matches[1];
} }
} }
return name + ' (' + ++highest + ')';
if (!existingNames.includes(name)) {
return name;
}
var suffixes = existingNames
// Get suffix
.map(x => x.substr(name.length))
// Get "2", "5", etc.
.filter(x => x.match(/^ (\d+)$/));
suffixes.sort(function (a, b) {
return parseInt(a) - parseInt(b);
});
// If no existing numbered names found, use 1
if (!suffixes.length) {
return name + ' ' + 1;
}
// Find first available number
var i = 0;
var num = 1;
while (suffixes[i] == num) {
while (suffixes[i + 1] && suffixes[i] == suffixes[i + 1]) {
i++;
}
i++;
num++;
}
return name + ' ' + num;
}, },

View file

@ -912,17 +912,23 @@ var ZoteroPane = new function()
var libraryID = this.getSelectedLibraryID(); var libraryID = this.getSelectedLibraryID();
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] // Get a unique "Untitled" name for this level in the collection hierarchy
.getService(Components.interfaces.nsIPromptService); var collections;
var untitled = yield Zotero.DB.getNextName( if (parentKey) {
libraryID, let parent = Zotero.Collections.getIDFromLibraryAndKey(libraryID, parentKey);
'collections', collections = Zotero.Collections.getByParent(parent);
'collectionName', }
Zotero.getString('pane.collections.untitled') else {
collections = Zotero.Collections.getByLibrary(libraryID);
}
var prefix = Zotero.getString('pane.collections.untitled');
var name = Zotero.Utilities.Internal.getNextName(
prefix,
collections.map(c => c.name).filter(n => n.startsWith(prefix))
); );
var newName = { value: untitled }; var newName = { value: name };
var result = promptService.prompt(window, var result = Services.prompt.prompt(window,
Zotero.getString('pane.collections.newCollection'), Zotero.getString('pane.collections.newCollection'),
Zotero.getString('pane.collections.name'), newName, "", {}); Zotero.getString('pane.collections.name'), newName, "", {});
@ -991,18 +997,20 @@ var ZoteroPane = new function()
yield Zotero.DB.waitForTransaction(); yield Zotero.DB.waitForTransaction();
} }
var libraryID = this.getSelectedLibraryID();
var s = new Zotero.Search(); var s = new Zotero.Search();
s.libraryID = this.getSelectedLibraryID(); s.libraryID = libraryID;
s.addCondition('title', 'contains', ''); s.addCondition('title', 'contains', '');
var untitled = Zotero.getString('pane.collections.untitled'); var searches = yield Zotero.Searches.getAll(libraryID)
untitled = yield Zotero.DB.getNextName( var prefix = Zotero.getString('pane.collections.untitled');
s.libraryID, var name = Zotero.Utilities.Internal.getNextName(
'savedSearches', prefix,
'savedSearchName', searches.map(s => s.name).filter(n => n.startsWith(prefix))
Zotero.getString('pane.collections.untitled')
); );
var io = {dataIn: {search: s, name: untitled}, dataOut: null};
var io = { dataIn: { search: s, name }, dataOut: null };
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io); window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
if (!io.dataOut) { if (!io.dataOut) {
return false; return false;

View file

@ -203,13 +203,23 @@ describe("Zotero.Utilities.Internal", function () {
describe("#getNextName()", function () { describe("#getNextName()", function () {
it("should get the next available numbered name", function () { it("should get the next available numbered name", function () {
var existing = ['Name (1)', 'Name (3)']; var existing = ['Name', 'Name 1', 'Name 3'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name (4)'); assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name 2');
}); });
it("should return 'Name (1)' if no numbered names", function () { it("should return 'Name 1' if no numbered names", function () {
var existing = ['Name']; var existing = ['Name'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name (1)'); assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name 1');
});
it("should return 'Name' if only numbered names", function () {
var existing = ['Name 1', 'Name 3'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name');
});
it("should trim given name if trim=true", function () {
var existing = ['Name', 'Name 1', 'Name 2', 'Name 3'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name 2', existing, true), 'Name 4');
}); });
}); });
}) })