зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1421664 - 3 - Fix the UI and tests. r=standard8
test_419731.js can be removed since it is already covered by browser_bookmarkProperties_editTagContainer.js MozReview-Commit-ID: K0LFuTptWyW --HG-- extra : rebase_source : 10066aa0bdb6a598fc6af638fed455d58422b7fb
This commit is contained in:
Родитель
66117a6abe
Коммит
d666e0fda1
|
@ -205,10 +205,7 @@ PlacesViewBase.prototype = {
|
|||
container = selectedNode.parent;
|
||||
index = container.getChildIndex(selectedNode);
|
||||
if (PlacesUtils.nodeIsTagQuery(container)) {
|
||||
tagName = container.title;
|
||||
// TODO (Bug 1160193): properly support dropping on a tag root.
|
||||
if (!tagName)
|
||||
return null;
|
||||
tagName = PlacesUtils.asQuery(container).query.tags[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,7 +175,8 @@ PlacesController.prototype = {
|
|||
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
|
||||
case "placesCmd_show:info": {
|
||||
let selectedNode = this._view.selectedNode;
|
||||
return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1;
|
||||
return selectedNode && (PlacesUtils.nodeIsTagQuery(selectedNode) ||
|
||||
PlacesUtils.getConcreteItemId(selectedNode) != -1);
|
||||
}
|
||||
case "placesCmd_reload": {
|
||||
// Livemark containers
|
||||
|
@ -807,18 +808,27 @@ PlacesController.prototype = {
|
|||
if (PlacesUtils.nodeIsTagQuery(node.parent)) {
|
||||
// This is a uri node inside a tag container. It needs a special
|
||||
// untag transaction.
|
||||
let tag = node.parent.title;
|
||||
let tag = node.parent.title || "";
|
||||
if (!tag) {
|
||||
// TODO: Bug 1432405 Try using getConcreteItemGuid.
|
||||
let tagItemId = PlacesUtils.getConcreteItemId(node.parent);
|
||||
let tagGuid = await PlacesUtils.promiseItemGuid(tagItemId);
|
||||
tag = (await PlacesUtils.bookmarks.fetch(tagGuid)).title;
|
||||
// The parent may be the root node, that doesn't have a title.
|
||||
// Until we fix bug 1293445, we have two ways to get tags:
|
||||
if (node.parent.queryOptions.resultType ==
|
||||
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
|
||||
// Get the tag using the bookmarks API.
|
||||
let tagItemId = PlacesUtils.getConcreteItemId(node.parent);
|
||||
let tagGuid = await PlacesUtils.promiseItemGuid(tagItemId);
|
||||
tag = (await PlacesUtils.bookmarks.fetch(tagGuid)).title;
|
||||
} else {
|
||||
// Extract the tag from the query itself.
|
||||
tag = node.parent.query.tags[0];
|
||||
}
|
||||
}
|
||||
transactions.push(PlacesTransactions.Untag({ urls: [node.uri], tag }));
|
||||
} else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
|
||||
PlacesUtils.nodeIsQuery(node.parent) &&
|
||||
PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
|
||||
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
|
||||
} else if (PlacesUtils.nodeIsTagQuery(node) &&
|
||||
node.parent &&
|
||||
PlacesUtils.nodeIsQuery(node.parent) &&
|
||||
PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
|
||||
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
|
||||
// This is a tag container.
|
||||
// Untag all URIs tagged with this tag only if the tag container is
|
||||
// child of the "Tags" query in the library, in all other places we
|
||||
|
@ -827,16 +837,16 @@ PlacesController.prototype = {
|
|||
let URIs = PlacesUtils.tagging.getURIsForTag(tag);
|
||||
transactions.push(PlacesTransactions.Untag({ tag, urls: URIs }));
|
||||
} else if (PlacesUtils.nodeIsURI(node) &&
|
||||
PlacesUtils.nodeIsQuery(node.parent) &&
|
||||
PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
|
||||
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
|
||||
PlacesUtils.nodeIsQuery(node.parent) &&
|
||||
PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
|
||||
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
|
||||
// This is a uri node inside an history query.
|
||||
PlacesUtils.history.remove(node.uri).catch(Cu.reportError);
|
||||
// History deletes are not undoable, so we don't have a transaction.
|
||||
} else if (node.itemId == -1 &&
|
||||
PlacesUtils.nodeIsQuery(node) &&
|
||||
PlacesUtils.asQuery(node).queryOptions.queryType ==
|
||||
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
|
||||
PlacesUtils.nodeIsQuery(node) &&
|
||||
PlacesUtils.asQuery(node).queryOptions.queryType ==
|
||||
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
|
||||
// This is a dynamically generated history query, like queries
|
||||
// grouped by site, time or both. Dynamically generated queries don't
|
||||
// have an itemId even if they are descendants of a bookmark.
|
||||
|
|
|
@ -33,13 +33,12 @@ var gEditItemOverlay = {
|
|||
let isFolderShortcut = isItem &&
|
||||
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
|
||||
let isTag = node && PlacesUtils.nodeIsTagQuery(node);
|
||||
let tag = null;
|
||||
if (isTag) {
|
||||
itemId = PlacesUtils.getConcreteItemId(node);
|
||||
// For now we don't have access to the item guid synchronously for tags,
|
||||
// so we'll need to fetch it later.
|
||||
tag = PlacesUtils.asQuery(node).query.tags.length == 1 ? node.query.tags[0] : node.title;
|
||||
}
|
||||
let isURI = node && PlacesUtils.nodeIsURI(node);
|
||||
let uri = isURI ? Services.io.newURI(node.uri) : null;
|
||||
let uri = isURI || isTag ? Services.io.newURI(node.uri) : null;
|
||||
let title = node ? node.title : null;
|
||||
let isBookmark = isItem && isURI;
|
||||
let bulkTagging = !node;
|
||||
|
@ -68,7 +67,7 @@ var gEditItemOverlay = {
|
|||
isBookmark, isFolderShortcut, isParentReadOnly,
|
||||
bulkTagging, uris,
|
||||
visibleRows, postData, isTag, focusedElement,
|
||||
onPanelReady };
|
||||
onPanelReady, tag };
|
||||
},
|
||||
|
||||
get initialized() {
|
||||
|
@ -77,7 +76,7 @@ var gEditItemOverlay = {
|
|||
|
||||
// Backwards-compatibility getters
|
||||
get itemId() {
|
||||
if (!this.initialized || this._paneInfo.bulkTagging)
|
||||
if (!this.initialized || this._paneInfo.isTag || this._paneInfo.bulkTagging)
|
||||
return -1;
|
||||
return this._paneInfo.itemId;
|
||||
},
|
||||
|
@ -120,7 +119,7 @@ var gEditItemOverlay = {
|
|||
throw new Error("_initNamePicker called unexpectedly");
|
||||
|
||||
// title may by null, which, for us, is the same as an empty string.
|
||||
this._initTextField(this._namePicker, this._paneInfo.title || "");
|
||||
this._initTextField(this._namePicker, this._paneInfo.title || this._paneInfo.tag || "");
|
||||
},
|
||||
|
||||
_initLocationField() {
|
||||
|
@ -607,18 +606,28 @@ var gEditItemOverlay = {
|
|||
return;
|
||||
|
||||
// Here we update either the item title or its cached static title
|
||||
let newTitle = this._namePicker.value;
|
||||
if (!newTitle && this._paneInfo.isTag) {
|
||||
// We don't allow setting an empty title for a tag, restore the old one.
|
||||
this._initNamePicker();
|
||||
} else {
|
||||
this._mayUpdateFirstEditField("namePicker");
|
||||
|
||||
let guid = this._paneInfo.isTag
|
||||
? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
|
||||
: this._paneInfo.itemGuid;
|
||||
await PlacesTransactions.EditTitle({ guid, title: newTitle }).transact();
|
||||
if (this._paneInfo.isTag) {
|
||||
let tag = this._namePicker.value;
|
||||
if (!tag || tag.includes("&")) {
|
||||
// We don't allow setting an empty title for a tag, restore the old one.
|
||||
this._initNamePicker();
|
||||
return;
|
||||
}
|
||||
// Get all the bookmarks for the old tag, tag them with the new tag, and
|
||||
// untag them from the old tag.
|
||||
let oldTag = this._paneInfo.tag;
|
||||
this._paneInfo.tag = tag;
|
||||
let title = this._paneInfo.title;
|
||||
if (title == oldTag) {
|
||||
this._paneInfo.title = tag;
|
||||
}
|
||||
await PlacesTransactions.RenameTag({ oldTag, tag }).transact();
|
||||
return;
|
||||
}
|
||||
|
||||
this._mayUpdateFirstEditField("namePicker");
|
||||
await PlacesTransactions.EditTitle({ guid: this._paneInfo.itemGuid,
|
||||
title: this._namePicker.value }).transact();
|
||||
},
|
||||
|
||||
onDescriptionFieldChange() {
|
||||
|
@ -1020,8 +1029,8 @@ var gEditItemOverlay = {
|
|||
}
|
||||
},
|
||||
|
||||
_onItemTitleChange(aItemId, aNewTitle) {
|
||||
if (aItemId == this._paneInfo.itemId) {
|
||||
_onItemTitleChange(aItemId, aNewTitle, aGuid) {
|
||||
if (aItemId == this._paneInfo.itemId || aGuid == this._paneInfo.itemGuid) {
|
||||
this._paneInfo.title = aNewTitle;
|
||||
this._initTextField(this._namePicker, aNewTitle);
|
||||
} else if (this._paneInfo.visibleRows.has("folderRow")) {
|
||||
|
@ -1056,7 +1065,7 @@ var gEditItemOverlay = {
|
|||
}
|
||||
if (aProperty == "title" && (this._paneInfo.isItem || this._paneInfo.isTag)) {
|
||||
// This also updates titles of folders in the folder menu list.
|
||||
this._onItemTitleChange(aItemId, aValue);
|
||||
this._onItemTitleChange(aItemId, aValue, aGuid);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1125,7 +1134,6 @@ var gEditItemOverlay = {
|
|||
onItemVisited() { },
|
||||
};
|
||||
|
||||
|
||||
for (let elt of ["folderMenuList", "folderTree", "namePicker",
|
||||
"locationField", "descriptionField", "keywordField",
|
||||
"tagsField", "loadInSidebarCheckbox"]) {
|
||||
|
|
|
@ -83,8 +83,8 @@
|
|||
// We do not yet support searching into grouped queries or into
|
||||
// tag containers, so we must fall to the default case.
|
||||
if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
|
||||
PlacesUtils.nodeIsTagQuery(queryNode) ||
|
||||
options.resultType == options.RESULTS_AS_TAG_QUERY ||
|
||||
options.resultType == options.RESULTS_AS_TAG_CONTENTS ||
|
||||
options.resultType == options.RESULTS_AS_ROOTS_QUERY)
|
||||
options.resultType = options.RESULTS_AS_URI;
|
||||
|
||||
|
@ -528,13 +528,8 @@
|
|||
if (this.controller.disallowInsertion(container))
|
||||
return null;
|
||||
|
||||
// TODO (Bug 1160193): properly support dropping on a tag root.
|
||||
let tagName = null;
|
||||
if (PlacesUtils.nodeIsTagQuery(container)) {
|
||||
tagName = container.title;
|
||||
if (!tagName)
|
||||
return null;
|
||||
}
|
||||
let tagName = PlacesUtils.nodeIsTagQuery(container) ?
|
||||
PlacesUtils.asQuery(container).query.tags[0] : null;
|
||||
|
||||
return new PlacesInsertionPoint({
|
||||
parentId: PlacesUtils.getConcreteItemId(container),
|
||||
|
|
|
@ -1351,8 +1351,8 @@ PlacesTreeView.prototype = {
|
|||
// Treat non-expandable childless queries as non-containers, unless they
|
||||
// are tags.
|
||||
if (PlacesUtils.nodeIsQuery(node) && !PlacesUtils.nodeIsTagQuery(node)) {
|
||||
PlacesUtils.asQuery(node);
|
||||
return node.queryOptions.expandQueries || node.hasChildren;
|
||||
return PlacesUtils.asQuery(node).queryOptions.expandQueries ||
|
||||
node.hasChildren;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
@ -1467,13 +1467,8 @@ PlacesTreeView.prototype = {
|
|||
if (this._controller.disallowInsertion(container))
|
||||
return null;
|
||||
|
||||
// TODO (Bug 1160193): properly support dropping on a tag root.
|
||||
let tagName = null;
|
||||
if (PlacesUtils.nodeIsTagQuery(container)) {
|
||||
tagName = container.title;
|
||||
if (!tagName)
|
||||
return null;
|
||||
}
|
||||
let tagName = PlacesUtils.nodeIsTagQuery(container) ?
|
||||
PlacesUtils.asQuery(container).query.tags[0] : null;
|
||||
|
||||
return new PlacesInsertionPoint({
|
||||
parentId: PlacesUtils.getConcreteItemId(container),
|
||||
|
|
|
@ -31,8 +31,11 @@ add_task(async function() {
|
|||
Assert.ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
|
||||
"'placesCmd_show:info' on current selected node is enabled");
|
||||
|
||||
let promiseTitleResetNotification = PlacesTestUtils.waitForNotification(
|
||||
"onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag1");
|
||||
let promiseTagResetNotification = PlacesTestUtils.waitForNotification(
|
||||
"onItemChanged", (itemId, prop) => {
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(uri);
|
||||
return prop == "tags" && tags.length == 1 && tags[0] == "tag1";
|
||||
});
|
||||
|
||||
await withBookmarksDialog(
|
||||
true,
|
||||
|
@ -47,22 +50,24 @@ add_task(async function() {
|
|||
let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
|
||||
Assert.ok(!namepicker.readOnly, "Name field should not be read-only");
|
||||
Assert.equal(namepicker.value, "tag1", "Node title is correct");
|
||||
|
||||
let promiseTitleChangeNotification = PlacesTestUtils.waitForNotification(
|
||||
"onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag2");
|
||||
let promiseTagChangeNotification = PlacesTestUtils.waitForNotification(
|
||||
"onItemChanged", (itemId, prop) => {
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(uri);
|
||||
return prop == "tags" && tags.length == 1 && tags[0] == "tag2";
|
||||
});
|
||||
|
||||
fillBookmarkTextField("editBMPanel_namePicker", "tag2", dialogWin);
|
||||
|
||||
await promiseTitleChangeNotification;
|
||||
await promiseTagChangeNotification;
|
||||
|
||||
Assert.equal(namepicker.value, "tag2", "Node title has been properly edited");
|
||||
Assert.equal(namepicker.value, "tag2", "The title field has been changed");
|
||||
|
||||
// Check the shortcut's title.
|
||||
Assert.equal(tree.selectedNode.title, "tag2", "The node has the correct title");
|
||||
|
||||
// Try to set an empty title, it should restore the previous one.
|
||||
fillBookmarkTextField("editBMPanel_namePicker", "", dialogWin);
|
||||
Assert.equal(namepicker.value, "tag2", "Title has not been changed");
|
||||
Assert.equal(namepicker.value, "tag2", "The title field has been changed");
|
||||
Assert.equal(tree.selectedNode.title, "tag2", "The node has the correct title");
|
||||
|
||||
// Check the tags have been edited.
|
||||
|
@ -75,7 +80,7 @@ add_task(async function() {
|
|||
}
|
||||
);
|
||||
|
||||
await promiseTitleResetNotification;
|
||||
await promiseTagResetNotification;
|
||||
|
||||
// Check the tag change has been reverted.
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(uri);
|
||||
|
|
|
@ -116,14 +116,12 @@ function synthesizeDragWithDirection(aElement, aExpectedDragData, aDirection) {
|
|||
|
||||
function getToolbarNodeForItemId(itemGuid) {
|
||||
var children = document.getElementById("PlacesToolbarItems").childNodes;
|
||||
var node = null;
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if (itemGuid == children[i]._placesNode.bookmarkGuid) {
|
||||
node = children[i];
|
||||
break;
|
||||
for (let child of children) {
|
||||
if (itemGuid == child._placesNode.bookmarkGuid) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
return null;
|
||||
}
|
||||
|
||||
function getExpectedDataForPlacesNode(aNode) {
|
||||
|
|
|
@ -36,8 +36,8 @@ add_task(async function test_tags() {
|
|||
children,
|
||||
});
|
||||
|
||||
for (let i = 0; i < uris.length; i++) {
|
||||
PlacesUtils.tagging.tagURI(uris[i], ["test"]);
|
||||
for (let uri of uris) {
|
||||
PlacesUtils.tagging.tagURI(uri, ["test"]);
|
||||
}
|
||||
|
||||
let library = await promiseLibrary();
|
||||
|
|
|
@ -908,7 +908,7 @@ DefineTransaction.defineInputProps(["guid", "parentGuid", "newParentGuid"],
|
|||
DefineTransaction.guidValidate);
|
||||
DefineTransaction.defineInputProps(["title", "postData"],
|
||||
DefineTransaction.strOrNullValidate, null);
|
||||
DefineTransaction.defineInputProps(["keyword", "oldKeyword", "tag",
|
||||
DefineTransaction.defineInputProps(["keyword", "oldKeyword", "oldTag", "tag",
|
||||
"excludingAnnotation"],
|
||||
DefineTransaction.strValidate, "");
|
||||
DefineTransaction.defineInputProps(["index", "newIndex"],
|
||||
|
@ -1623,6 +1623,96 @@ PT.Untag.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction for renaming a tag.
|
||||
*
|
||||
* Required Input Properties: oldTag, tag.
|
||||
*/
|
||||
PT.RenameTag = DefineTransaction(["oldTag", "tag"]);
|
||||
PT.RenameTag.prototype = {
|
||||
async execute({ oldTag, tag }) {
|
||||
// For now this is implemented by untagging and tagging all the bookmarks.
|
||||
// We should create a specialized bookmarking API to just rename the tag.
|
||||
let onUndo = [], onRedo = [];
|
||||
let urls = PlacesUtils.tagging.getURIsForTag(oldTag);
|
||||
if (urls.length > 0) {
|
||||
let tagTxn = TransactionsHistory.getRawTransaction(
|
||||
PT.Tag({ urls, tags: [tag] })
|
||||
);
|
||||
await tagTxn.execute();
|
||||
onUndo.unshift(tagTxn.undo.bind(tagTxn));
|
||||
onRedo.push(tagTxn.redo.bind(tagTxn));
|
||||
let untagTxn = TransactionsHistory.getRawTransaction(
|
||||
PT.Untag({ urls, tags: [oldTag] })
|
||||
);
|
||||
await untagTxn.execute();
|
||||
onUndo.unshift(untagTxn.undo.bind(untagTxn));
|
||||
onRedo.push(untagTxn.redo.bind(untagTxn));
|
||||
|
||||
// Update all the place: queries that refer to this tag.
|
||||
let db = await PlacesUtils.promiseDBConnection();
|
||||
let rows = await db.executeCached(`
|
||||
SELECT h.url, b.guid, b.title
|
||||
FROM moz_places h
|
||||
JOIN moz_bookmarks b ON b.fk = h.id
|
||||
WHERE url_hash BETWEEN hash("place", "prefix_lo")
|
||||
AND hash("place", "prefix_hi")
|
||||
AND url LIKE :tagQuery
|
||||
`, { tagQuery: "%tag=%" });
|
||||
for (let row of rows) {
|
||||
let url = row.getResultByName("url");
|
||||
try {
|
||||
url = new URL(url);
|
||||
let urlParams = new URLSearchParams(url.pathname);
|
||||
let tags = urlParams.getAll("tag");
|
||||
if (!tags.includes(oldTag))
|
||||
continue;
|
||||
if (tags.length > 1) {
|
||||
// URLSearchParams cannot set more than 1 same-named param.
|
||||
urlParams.delete("tag");
|
||||
urlParams.set("tag", tag);
|
||||
url = new URL(url.protocol + urlParams +
|
||||
"&tag=" + tags.filter(t => t != oldTag).join("&tag="));
|
||||
} else {
|
||||
urlParams.set("tag", tag);
|
||||
url = new URL(url.protocol + urlParams);
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError("Invalid bookmark url: " + row.getResultByName("url") + ": " + ex);
|
||||
continue;
|
||||
}
|
||||
let guid = row.getResultByName("guid");
|
||||
let title = row.getResultByName("title");
|
||||
|
||||
let editUrlTxn = TransactionsHistory.getRawTransaction(
|
||||
PT.EditUrl({ guid, url })
|
||||
);
|
||||
await editUrlTxn.execute();
|
||||
onUndo.unshift(editUrlTxn.undo.bind(editUrlTxn));
|
||||
onRedo.push(editUrlTxn.redo.bind(editUrlTxn));
|
||||
if (title == oldTag) {
|
||||
let editTitleTxn = TransactionsHistory.getRawTransaction(
|
||||
PT.EditTitle({ guid, title: tag })
|
||||
);
|
||||
await editTitleTxn.execute();
|
||||
onUndo.unshift(editTitleTxn.undo.bind(editTitleTxn));
|
||||
onRedo.push(editTitleTxn.redo.bind(editTitleTxn));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.undo = async function() {
|
||||
for (let f of onUndo) {
|
||||
await f();
|
||||
}
|
||||
};
|
||||
this.redo = async function() {
|
||||
for (let f of onRedo) {
|
||||
await f();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transaction for copying an item.
|
||||
*
|
||||
|
|
|
@ -782,9 +782,20 @@ var PlacesUtils = {
|
|||
* @returns true if the node is a tag container, false otherwise
|
||||
*/
|
||||
nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) {
|
||||
return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
|
||||
asQuery(aNode).queryOptions.resultType ==
|
||||
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS;
|
||||
if (aNode.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY)
|
||||
return false;
|
||||
// Direct child of RESULTS_AS_TAG_QUERY.
|
||||
let parent = aNode.parent;
|
||||
if (parent && PlacesUtils.asQuery(parent).queryOptions.resultType ==
|
||||
Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY)
|
||||
return true;
|
||||
// We must also support the right pane of the Library, when the tag query
|
||||
// is the root node. Unfortunately this is also valid for any tag query
|
||||
// selected in the left pane that is not a direct child of RESULTS_AS_TAG_QUERY.
|
||||
if (!parent && aNode == aNode.parentResult.root &&
|
||||
PlacesUtils.asQuery(aNode).query.tags.length == 1)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -822,15 +833,8 @@ var PlacesUtils = {
|
|||
* node.itemId, but for folder-shortcuts that's node.folderItemId.
|
||||
*/
|
||||
getConcreteItemId: function PU_getConcreteItemId(aNode) {
|
||||
if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
|
||||
return asQuery(aNode).folderItemId;
|
||||
else if (PlacesUtils.nodeIsTagQuery(aNode)) {
|
||||
// RESULTS_AS_TAG_CONTENTS queries are similar to folder shortcuts
|
||||
// so we can still get the concrete itemId for them.
|
||||
let folders = aNode.query.getFolders();
|
||||
return folders[0];
|
||||
}
|
||||
return aNode.itemId;
|
||||
return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT ?
|
||||
asQuery(aNode).folderItemId : aNode.itemId;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1130,7 +1130,8 @@ class PlacesSQLQueryBuilder
|
|||
{
|
||||
public:
|
||||
PlacesSQLQueryBuilder(const nsCString& aConditions,
|
||||
nsNavHistoryQueryOptions* aOptions,
|
||||
const RefPtr<nsNavHistoryQuery>& aQuery,
|
||||
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
|
||||
bool aUseLimit,
|
||||
nsNavHistory::StringHash& aAddParams,
|
||||
bool aHasSearchTerms);
|
||||
|
@ -1173,12 +1174,14 @@ private:
|
|||
nsCString mGroupBy;
|
||||
bool mHasDateColumns;
|
||||
bool mSkipOrderBy;
|
||||
|
||||
nsNavHistory::StringHash& mAddParams;
|
||||
};
|
||||
|
||||
PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
|
||||
const nsCString& aConditions,
|
||||
nsNavHistoryQueryOptions* aOptions,
|
||||
const RefPtr<nsNavHistoryQuery>& aQuery,
|
||||
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
|
||||
bool aUseLimit,
|
||||
nsNavHistory::StringHash& aAddParams,
|
||||
bool aHasSearchTerms)
|
||||
|
@ -1194,6 +1197,11 @@ PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
|
|||
, mAddParams(aAddParams)
|
||||
{
|
||||
mHasDateColumns = (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
|
||||
// Force the default sorting mode for tag queries.
|
||||
if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE &&
|
||||
aQuery->Tags().Length() > 0) {
|
||||
mSortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -1654,6 +1662,9 @@ PlacesSQLQueryBuilder::SelectAsTag()
|
|||
// other history queries.
|
||||
mHasDateColumns = true;
|
||||
|
||||
// TODO (Bug 1449939): This is likely wrong, since the tag name should
|
||||
// probably be urlencoded, and we have no util for that in SQL, yet.
|
||||
// We could encode the tag when the user sets it though.
|
||||
mQueryString = nsPrintfCString(
|
||||
"SELECT null, 'place:tag=' || title, "
|
||||
"title, null, null, null, null, null, dateAdded, "
|
||||
|
@ -2019,7 +2030,7 @@ nsNavHistory::ConstructQueryString(
|
|||
// using FilterResultSet()
|
||||
bool useLimitClause = !NeedToFilterResultSet(aQuery, aOptions);
|
||||
|
||||
PlacesSQLQueryBuilder queryStringBuilder(conditions, aOptions,
|
||||
PlacesSQLQueryBuilder queryStringBuilder(conditions, aQuery, aOptions,
|
||||
useLimitClause, aAddParams,
|
||||
hasSearchTerms);
|
||||
rv = queryStringBuilder.GetQueryString(queryString);
|
||||
|
@ -3040,8 +3051,9 @@ nsNavHistory::QueryToSelectClause(const RefPtr<nsNavHistoryQuery>& aQuery,
|
|||
clause.Str(",");
|
||||
}
|
||||
clause.Str(")");
|
||||
if (!aQuery->TagsAreNot())
|
||||
if (!aQuery->TagsAreNot()) {
|
||||
clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count");
|
||||
}
|
||||
clause.Str(")");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
add_task(async function test_tag_updates() {
|
||||
const url = "http://foo.bar/";
|
||||
let lastModified = new Date(Date.now() - 10000);
|
||||
|
||||
// create 2 bookmarks
|
||||
let bm1 = await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
dateAdded: lastModified,
|
||||
lastModified: new Date(),
|
||||
title: "title 1",
|
||||
url,
|
||||
});
|
||||
let bm2 = await PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
dateAdded: lastModified,
|
||||
lastModified,
|
||||
title: "title 2",
|
||||
url,
|
||||
});
|
||||
|
||||
// add a new tag
|
||||
PlacesUtils.tagging.tagURI(uri(url), ["foo"]);
|
||||
|
||||
// get tag folder id
|
||||
let options = PlacesUtils.history.getNewQueryOptions();
|
||||
let query = PlacesUtils.history.getNewQuery();
|
||||
query.setFolders([PlacesUtils.tagsFolderId], 1);
|
||||
let result = PlacesUtils.history.executeQuery(query, options);
|
||||
let tagRoot = result.root;
|
||||
tagRoot.containerOpen = true;
|
||||
let tagNode = tagRoot.getChild(0)
|
||||
.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
let tagItemGuid = tagNode.bookmarkGuid;
|
||||
let tagItemId = tagNode.itemId;
|
||||
tagRoot.containerOpen = false;
|
||||
|
||||
// change bookmark 1 title
|
||||
await PlacesUtils.bookmarks.update({
|
||||
guid: bm1.guid,
|
||||
title: "new title 1",
|
||||
});
|
||||
|
||||
// Workaround timers resolution and time skews.
|
||||
bm2 = await PlacesUtils.bookmarks.fetch(bm2.guid);
|
||||
|
||||
lastModified = new Date(lastModified.getTime() + 1000);
|
||||
|
||||
await PlacesUtils.bookmarks.update({
|
||||
guid: bm1.guid,
|
||||
lastModified,
|
||||
});
|
||||
|
||||
// Query the tag.
|
||||
options = PlacesUtils.history.getNewQueryOptions();
|
||||
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
|
||||
options.resultType = options.RESULTS_AS_TAG_QUERY;
|
||||
|
||||
query = PlacesUtils.history.getNewQuery();
|
||||
result = PlacesUtils.history.executeQuery(query, options);
|
||||
let root = result.root;
|
||||
root.containerOpen = true;
|
||||
Assert.equal(root.childCount, 1);
|
||||
|
||||
let theTag = root.getChild(0)
|
||||
.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
// Bug 524219: Check that renaming the tag shows up in the result.
|
||||
Assert.equal(theTag.title, "foo");
|
||||
|
||||
await PlacesUtils.bookmarks.update({
|
||||
guid: tagItemGuid,
|
||||
title: "bar",
|
||||
});
|
||||
|
||||
// Check that the item has been replaced
|
||||
Assert.notEqual(theTag, root.getChild(0));
|
||||
theTag = root.getChild(0)
|
||||
.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
Assert.equal(theTag.title, "bar");
|
||||
|
||||
// Check that tag container contains new title
|
||||
theTag.containerOpen = true;
|
||||
Assert.equal(theTag.childCount, 1);
|
||||
let node = theTag.getChild(0);
|
||||
Assert.equal(node.title, "new title 1");
|
||||
theTag.containerOpen = false;
|
||||
root.containerOpen = false;
|
||||
|
||||
// Change bookmark 2 title.
|
||||
PlacesUtils.bookmarks.update({
|
||||
guid: bm2.guid,
|
||||
title: "new title 2"
|
||||
});
|
||||
|
||||
// Workaround timers resolution and time skews.
|
||||
lastModified = new Date(lastModified.getTime() + 1000);
|
||||
|
||||
await PlacesUtils.bookmarks.update({
|
||||
guid: bm2.guid,
|
||||
lastModified,
|
||||
});
|
||||
|
||||
// Check that tag container contains new title
|
||||
options = PlacesUtils.history.getNewQueryOptions();
|
||||
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
|
||||
options.resultType = options.RESULTS_AS_TAG_CONTENTS;
|
||||
|
||||
query = PlacesUtils.history.getNewQuery();
|
||||
query.setFolders([tagItemId], 1);
|
||||
result = PlacesUtils.history.executeQuery(query, options);
|
||||
root = result.root;
|
||||
|
||||
root.containerOpen = true;
|
||||
Assert.equal(root.childCount, 1);
|
||||
node = root.getChild(0);
|
||||
Assert.equal(node.title, "new title 2");
|
||||
root.containerOpen = false;
|
||||
});
|
|
@ -1859,3 +1859,67 @@ add_task(async function test_remove_multiple() {
|
|||
await PT.clearTransactionsHistory();
|
||||
observer.reset();
|
||||
});
|
||||
|
||||
add_task(async function test_renameTag() {
|
||||
let url = "http://test.edit.keyword/";
|
||||
await PT.Tag({ url, tags: ["t1", "t2"] }).transact();
|
||||
ensureTagsForURI(url, ["t1", "t2"]);
|
||||
|
||||
// Create bookmark queries that point to the modified tag.
|
||||
let bm1 = await PlacesUtils.bookmarks.insert({
|
||||
url: "place:tag=t2",
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid
|
||||
});
|
||||
let bm2 = await PlacesUtils.bookmarks.insert({
|
||||
url: "place:tag=t2&sort=1",
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid
|
||||
});
|
||||
// This points to 2 tags, and as such won't be touched.
|
||||
let bm3 = await PlacesUtils.bookmarks.insert({
|
||||
url: "place:tag=t2&tag=t1",
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid
|
||||
});
|
||||
|
||||
await PT.RenameTag({ oldTag: "t2", tag: "t3" }).transact();
|
||||
ensureTagsForURI(url, ["t1", "t3"]);
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t3",
|
||||
"The fitst bookmark has been updated");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t3&sort=1",
|
||||
"The second bookmark has been updated");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t3&tag=t1",
|
||||
"The third bookmark has been updated");
|
||||
|
||||
await PT.undo();
|
||||
ensureTagsForURI(url, ["t1", "t2"]);
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t2",
|
||||
"The fitst bookmark has been restored");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t2&sort=1",
|
||||
"The second bookmark has been restored");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t2&tag=t1",
|
||||
"The third bookmark has been restored");
|
||||
|
||||
await PT.redo();
|
||||
ensureTagsForURI(url, ["t1", "t3"]);
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t3",
|
||||
"The fitst bookmark has been updated");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t3&sort=1",
|
||||
"The second bookmark has been updated");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t3&tag=t1",
|
||||
"The third bookmark has been updated");
|
||||
|
||||
await PT.undo();
|
||||
ensureTagsForURI(url, ["t1", "t2"]);
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm1.guid)).url.href, "place:tag=t2",
|
||||
"The fitst bookmark has been restored");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm2.guid)).url.href, "place:tag=t2&sort=1",
|
||||
"The second bookmark has been restored");
|
||||
Assert.equal((await PlacesUtils.bookmarks.fetch(bm3.guid)).url.href, "place:tag=t2&tag=t1",
|
||||
"The third bookmark has been restored");
|
||||
|
||||
await PT.undo();
|
||||
ensureTagsForURI(url, []);
|
||||
|
||||
await PT.clearTransactionsHistory();
|
||||
ensureUndoState();
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
add_task(async function test_resolveNullBookmarkTitles() {
|
||||
add_task(async function() {
|
||||
let uri1 = uri("http://foo.tld/");
|
||||
let uri2 = uri("https://bar.tld/");
|
||||
|
||||
|
@ -25,7 +25,9 @@ add_task(async function test_resolveNullBookmarkTitles() {
|
|||
|
||||
PlacesUtils.tagging.tagURI(uri1, ["tag 1"]);
|
||||
PlacesUtils.tagging.tagURI(uri2, ["tag 2"]);
|
||||
});
|
||||
|
||||
add_task(async function testAsTagQuery() {
|
||||
let options = PlacesUtils.history.getNewQueryOptions();
|
||||
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
|
||||
options.resultType = options.RESULTS_AS_TAG_CONTENTS;
|
||||
|
@ -42,3 +44,14 @@ add_task(async function test_resolveNullBookmarkTitles() {
|
|||
Assert.equal(root.getChild(1).title, "");
|
||||
root.containerOpen = false;
|
||||
});
|
||||
|
||||
add_task(async function testTagQuery() {
|
||||
let options = PlacesUtils.history.getNewQueryOptions();
|
||||
let query = PlacesUtils.history.getNewQuery();
|
||||
query.tags = ["tag 1"];
|
||||
let root = PlacesUtils.history.executeQuery(query, options).root;
|
||||
root.containerOpen = true;
|
||||
Assert.equal(root.childCount, 1);
|
||||
Assert.equal(root.getChild(0).title, "");
|
||||
root.containerOpen = false;
|
||||
});
|
||||
|
|
|
@ -33,7 +33,6 @@ skip-if = os == "linux"
|
|||
[test_413784.js]
|
||||
[test_415460.js]
|
||||
[test_415757.js]
|
||||
[test_419731.js]
|
||||
[test_419792_node_tags_property.js]
|
||||
[test_425563.js]
|
||||
[test_429505_remove_shortcuts.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче