зеркало из https://github.com/mozilla/pjs.git
Bug 370959 - places frontend code cleanup. r=sspitzer.
This commit is contained in:
Родитель
7f917d232a
Коммит
e9a784ec55
|
@ -1960,9 +1960,7 @@ function getShortcutOrURI(aURL, aPostDataRef)
|
|||
try {
|
||||
var shortcutURL = null;
|
||||
#ifdef MOZ_PLACES_BOOKMARKS
|
||||
var bookmarkService = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
|
||||
.getService(nsCI.nsINavBookmarksService);
|
||||
var shortcutURI = bookmarkService.getURIForKeyword(aURL);
|
||||
var shortcutURI = PlacesUtils.bookmarks.getURIForKeyword(aURL);
|
||||
if (shortcutURI)
|
||||
shortcutURL = shortcutURI.spec;
|
||||
#else
|
||||
|
|
|
@ -82,32 +82,24 @@ const NEWLINE = "\r\n";
|
|||
// batching (rather than letting them do incremental drawing).
|
||||
const MIN_TRANSACTIONS_FOR_BATCH = 5;
|
||||
|
||||
function STACK(args) {
|
||||
var temp = arguments.callee.caller;
|
||||
while (temp) {
|
||||
LOG("NAME: " + temp.name + "\n");
|
||||
temp = temp.arguments.callee.caller;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an insertion point within a container where we can insert
|
||||
* items.
|
||||
* @param folderId
|
||||
* @param aFolderId
|
||||
* The folderId of the parent container
|
||||
* @param index
|
||||
* @param aIndex
|
||||
* The index within the container where we should insert
|
||||
* @param orientation
|
||||
* @param aOrientation
|
||||
* The orientation of the insertion. NOTE: the adjustments to the
|
||||
* insertion point to accommodate the orientation should be done by
|
||||
* the person who constructs the IP, not the user. The orientation
|
||||
* is provided for informational purposes only!
|
||||
* @constructor
|
||||
*/
|
||||
function InsertionPoint(folderId, index, orientation) {
|
||||
this.folderId = folderId;
|
||||
this.index = index;
|
||||
this.orientation = orientation;
|
||||
function InsertionPoint(aFolderId, aIndex, aOrientation) {
|
||||
this.folderId = aFolderId;
|
||||
this.index = aIndex;
|
||||
this.orientation = aOrientation;
|
||||
}
|
||||
InsertionPoint.prototype.toString = function IP_toString() {
|
||||
return "[object InsertionPoint(folder:" + this.folderId + ",index:" + this.index + ",orientation:" + this.orientation + ")]";
|
||||
|
@ -139,14 +131,14 @@ var ViewConfigurator = {
|
|||
"folder=1": new ViewConfig([TYPE_X_MOZ_PLACE_CONTAINER],
|
||||
ViewConfig.GENERIC_DROP_TYPES, 4)
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Applies rules to a specific view.
|
||||
*/
|
||||
configure: function PC_configure(view) {
|
||||
// Determine what place the view is showing.
|
||||
var place = view.place;
|
||||
|
||||
|
||||
// Find a ruleset that matches the current place.
|
||||
var rules = null;
|
||||
for (var test in this.rules) {
|
||||
|
@ -155,14 +147,11 @@ var ViewConfigurator = {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If rules are found, apply them.
|
||||
if (rules) {
|
||||
view.peerDropTypes = rules.peerDropTypes;
|
||||
view.childDropTypes = rules.childDropTypes;
|
||||
view.excludeItems = rules.excludeItems;
|
||||
view.excludeQueries = rules.excludeQueries;
|
||||
view.expandQueries = rules.expandQueries;
|
||||
view.peerDropIndex = rules.peerDropIndex;
|
||||
}
|
||||
}
|
||||
|
@ -181,9 +170,9 @@ PlacesController.prototype = {
|
|||
* The places view.
|
||||
*/
|
||||
_view: null,
|
||||
|
||||
isCommandEnabled: function PC_isCommandEnabled(command) {
|
||||
switch (command) {
|
||||
|
||||
isCommandEnabled: function PC_isCommandEnabled(aCommand) {
|
||||
switch (aCommand) {
|
||||
case "cmd_undo":
|
||||
return PlacesUtils.tm.numberOfUndoItems > 0;
|
||||
case "cmd_redo":
|
||||
|
@ -263,16 +252,12 @@ PlacesController.prototype = {
|
|||
case "placesCmd_show:info":
|
||||
if (this._view.hasSingleSelection) {
|
||||
var selectedNode = this._view.selectedNode;
|
||||
if (PlacesUtils.nodeIsBookmark(selectedNode)) {
|
||||
var uri = PlacesUtils._uri(selectedNode.uri);
|
||||
if (!PlacesUtils.annotations
|
||||
.hasAnnotation(uri, "livemark/bookmarkFeedURI"))
|
||||
return true;
|
||||
}
|
||||
else if (PlacesUtils.nodeIsFolder(selectedNode)) {
|
||||
if (!this._selectionOverlapsSystemArea())
|
||||
return true;
|
||||
}
|
||||
if (PlacesUtils.nodeIsBookmark(selectedNode) &&
|
||||
!PlacesUtils.nodeIsLivemarkItem(selectedNode))
|
||||
return true;
|
||||
else if (PlacesUtils.nodeIsFolder(selectedNode) &&
|
||||
!this._selectionOverlapsSystemArea())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case "placesCmd_reload":
|
||||
|
@ -290,12 +275,9 @@ PlacesController.prototype = {
|
|||
|
||||
// children of a live bookmark (legacy bookmarks UI doesn't support
|
||||
// this)
|
||||
if (PlacesUtils.nodeIsURI(selectedNode)) {
|
||||
var uri = PlacesUtils._uri(selectedNode.uri);
|
||||
if (PlacesUtils.annotations
|
||||
.hasAnnotation(uri, "livemark/bookmarkFeedURI"))
|
||||
return true;
|
||||
}
|
||||
if (PlacesUtils.nodeIsURI() &&
|
||||
PlacesUtils.nodeIsLivemarkItem(selectedNode))
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
|
@ -384,26 +366,8 @@ PlacesController.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
onEvent: function PC_onEvent(eventName) {
|
||||
//LOG("onEvent: " + eventName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the enabled state of a command element.
|
||||
* @param commandId
|
||||
* The id of the command element to update
|
||||
* @param enabled
|
||||
* Whether or not the command element should be enabled.
|
||||
*/
|
||||
_setEnabled: function PC__setEnabled(commandId, enabled) {
|
||||
var command = document.getElementById(commandId);
|
||||
// Prevents excessive setAttributes
|
||||
var disabled = command.hasAttribute("disabled");
|
||||
if (enabled && disabled)
|
||||
command.removeAttribute("disabled");
|
||||
else if (!enabled && !disabled)
|
||||
command.setAttribute("disabled", "true");
|
||||
},
|
||||
onEvent: function PC_onEvent(eventName) { },
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not the selection can be removed, either by the
|
||||
|
@ -432,15 +396,12 @@ PlacesController.prototype = {
|
|||
// children, e.g. live bookmark folder children. It doesn't make sense
|
||||
// to delete a child of a live bookmark folder, since when the folder
|
||||
// refreshes, the child will return.
|
||||
if (PlacesUtils.nodeIsFolder(parent)) {
|
||||
var readOnly = PlacesUtils.bookmarks.getFolderReadonly(asFolder(parent).folderId);
|
||||
if (readOnly)
|
||||
return false;
|
||||
}
|
||||
if (PlacesUtils.isReadonlyFolder(parent))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not the clipboard contains data that the active
|
||||
* view can support in a paste operation.
|
||||
|
@ -465,7 +426,7 @@ PlacesController.prototype = {
|
|||
return clipboard.hasDataMatchingFlavors(flavors,
|
||||
Ci.nsIClipboard.kGlobalClipboard);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not nodes can be inserted relative to the selection.
|
||||
*/
|
||||
|
@ -517,7 +478,7 @@ PlacesController.prototype = {
|
|||
return true;
|
||||
},
|
||||
|
||||
#ifdef MOZ_PLACES_BOOKMARKS
|
||||
#ifdef BROKEN_SORT_CODE
|
||||
/**
|
||||
* Updates commands for persistent sorting
|
||||
* @param inSysArea
|
||||
|
@ -554,7 +515,7 @@ PlacesController.prototype = {
|
|||
sortFolder = selectedNode;
|
||||
sortingChildren = true;
|
||||
}
|
||||
|
||||
|
||||
// Count the children of the container. If there aren't at least two, we
|
||||
// don't want to enable the command since there's nothing to be sorted.
|
||||
// We need to get the unfiltered contents of the container to make this
|
||||
|
@ -582,18 +543,6 @@ PlacesController.prototype = {
|
|||
},
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Determines if the active view can support inserting items of a certain type.
|
||||
*/
|
||||
_viewSupportsInsertingType: function PC__viewSupportsInsertingType(type) {
|
||||
var types = this._view.peerDropTypes;
|
||||
for (var i = 0; i < types.length; ++i) {
|
||||
if (types[i] == type.value)
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Looks at the data on the clipboard to see if it is paste-able.
|
||||
* Paste-able data is:
|
||||
|
@ -611,22 +560,22 @@ PlacesController.prototype = {
|
|||
xferable.addDataFlavor(TYPE_X_MOZ_PLACE_SEPARATOR);
|
||||
xferable.addDataFlavor(TYPE_X_MOZ_PLACE);
|
||||
xferable.addDataFlavor(TYPE_X_MOZ_URL);
|
||||
|
||||
|
||||
var clipboard = Cc["@mozilla.org/widget/clipboard;1"].
|
||||
getService(Ci.nsIClipboard);
|
||||
clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
|
||||
|
||||
|
||||
try {
|
||||
// getAnyTransferData can throw if no data is available.
|
||||
var data = { }, type = { };
|
||||
xferable.getAnyTransferData(type, data, { });
|
||||
data = data.value.QueryInterface(Ci.nsISupportsString).data;
|
||||
if (!this._viewSupportsInsertingType(type.value))
|
||||
if (this._view.peerDropTypes.indexOf(type.value) == -1)
|
||||
return false;
|
||||
|
||||
|
||||
// unwrapNodes will throw if the data blob is malformed.
|
||||
var nodes = PlacesUtils.unwrapNodes(data, type.value);
|
||||
|
||||
|
||||
var ip = this._view.insertionPoint;
|
||||
if (!ip)
|
||||
return false;
|
||||
|
@ -651,7 +600,7 @@ PlacesController.prototype = {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Since the bookmarks data model enforces only one instance of a URI per
|
||||
// folder, it is not possible to paste a selection into a folder where
|
||||
// all of the URIs already exist. Thus we need to return false to disable
|
||||
|
@ -752,7 +701,7 @@ PlacesController.prototype = {
|
|||
// aren't reorderable can have items removed from them, e.g. a history
|
||||
// list.
|
||||
if (!PlacesUtils.nodeIsReadOnly(node) &&
|
||||
!PlacesUtils.folderIsReadonly(node.parent || root))
|
||||
!PlacesUtils.isReadonlyFolder(node.parent || root))
|
||||
nodeData["mutable"] = true;
|
||||
|
||||
// annotations
|
||||
|
@ -761,6 +710,7 @@ PlacesController.prototype = {
|
|||
for (var j = 0; j < names.length; ++j)
|
||||
nodeData[names[i]] = true;
|
||||
}
|
||||
#ifdef EXTENDED_LIVEBOOKMARKS_UI
|
||||
else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
|
||||
// Various queries might live in the left-hand side of the organizer
|
||||
// window. If this one happens to have collected all the livemark feeds,
|
||||
|
@ -773,6 +723,7 @@ PlacesController.prototype = {
|
|||
if (uri.spec == ORGANIZER_SUBSCRIPTIONS_QUERY)
|
||||
nodeData["allLivemarks"] = true;
|
||||
}
|
||||
#endif
|
||||
metadata.push(nodeData);
|
||||
}
|
||||
|
||||
|
@ -788,7 +739,7 @@ PlacesController.prototype = {
|
|||
* @returns true if the conditions (see buildContextMenu) are satisfied
|
||||
* and the item can be displayed, false otherwise.
|
||||
*/
|
||||
_shouldShowMenuItem: function(aMenuItem, aMetaData) {
|
||||
_shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
|
||||
var selectiontype = aMenuItem.getAttribute("selectiontype");
|
||||
if (selectiontype == "multiple" && aMetaData.length == 1)
|
||||
return false;
|
||||
|
@ -896,7 +847,7 @@ PlacesController.prototype = {
|
|||
/**
|
||||
* Select all links in the current view.
|
||||
*/
|
||||
selectAll: function() {
|
||||
selectAll: function PC_selectAll() {
|
||||
this._view.selectAll();
|
||||
},
|
||||
|
||||
|
@ -967,11 +918,8 @@ PlacesController.prototype = {
|
|||
folder = asFolder(selectedNode);
|
||||
}
|
||||
#ifdef EXTENDED_LIVEBOOKMARKS_UI
|
||||
else if (PlacesUtils.nodeIsURI(selectedNode)) {
|
||||
var uri = PlacesUtils._uri(selectedNode.uri);
|
||||
var isLivemarkItem =
|
||||
PlacesUtils.annotations.hasAnnotation(uri, "livemark/bookmarkFeedURI");
|
||||
if (isLivemarkItem)
|
||||
else if (PlacesUtils.nodeIsURI()) {
|
||||
if (PlacesUtils.nodeIsLivemarkItem(selectedNode))
|
||||
folder = asFolder(selectedNode.parent);
|
||||
}
|
||||
#endif
|
||||
|
@ -1026,7 +974,7 @@ PlacesController.prototype = {
|
|||
}
|
||||
return reallyOpen;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Opens the links in the selected folder, or the selected links in new tabs.
|
||||
* XXXben this needs to handle the case when there are no open browser windows
|
||||
|
@ -1128,29 +1076,26 @@ PlacesController.prototype = {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Create a new Bookmark folder somewhere. Prompts the user for the name
|
||||
* of the folder.
|
||||
*/
|
||||
newFolder: function PC_newFolder() {
|
||||
var view = this._view;
|
||||
|
||||
view.saveSelection(view.SAVE_SELECTION_INSERT);
|
||||
var ps =
|
||||
Cc["@mozilla.org/embedcomp/prompt-service;1"].
|
||||
getService(Ci.nsIPromptService);
|
||||
this._view.saveSelection(this._view.SAVE_SELECTION_INSERT);
|
||||
var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
|
||||
getService(Ci.nsIPromptService);
|
||||
var title = PlacesUtils.getString("newFolderTitle");
|
||||
var text = PlacesUtils.getString("newFolderMessage");
|
||||
var value = { value: PlacesUtils.getString("newFolderDefault") };
|
||||
if (ps.prompt(window, title, text, value, null, { })) {
|
||||
var ip = view.insertionPoint;
|
||||
var ip = this._view.insertionPoint;
|
||||
if (!ip)
|
||||
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
||||
var txn = new PlacesCreateFolderTransaction(value.value, ip.folderId,
|
||||
ip.index);
|
||||
PlacesUtils.tm.doTransaction(txn);
|
||||
view.restoreSelection();
|
||||
this._view.restoreSelection();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1185,9 +1130,9 @@ PlacesController.prototype = {
|
|||
_removeRange: function PC__removeRange(range, transactions) {
|
||||
NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
|
||||
var index = PlacesUtils.getIndexOfNode(range[0]);
|
||||
|
||||
|
||||
var removedFolders = [];
|
||||
|
||||
|
||||
/**
|
||||
* Determines if a node is contained by another node within a resultset.
|
||||
* @param node
|
||||
|
@ -1205,7 +1150,7 @@ PlacesController.prototype = {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Walk the list of folders we're removing in this delete operation, and
|
||||
* see if the selected node specified is already implicitly being removed
|
||||
|
@ -1221,12 +1166,12 @@ PlacesController.prototype = {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
for (var i = 0; i < range.length; ++i) {
|
||||
var node = range[i];
|
||||
if (shouldSkipNode(node))
|
||||
continue;
|
||||
|
||||
|
||||
if (PlacesUtils.nodeIsFolder(node)) {
|
||||
// TODO -- node.parent might be a query and not a folder. See bug 324948
|
||||
var folder = asFolder(node);
|
||||
|
@ -1245,7 +1190,7 @@ PlacesController.prototype = {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Removes the set of selected ranges from bookmarks.
|
||||
* @param txnName
|
||||
|
@ -1261,7 +1206,7 @@ PlacesController.prototype = {
|
|||
PlacesUtils.tm.doTransaction(txn);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Removes the set of selected ranges from history.
|
||||
*/
|
||||
|
@ -1278,28 +1223,28 @@ PlacesController.prototype = {
|
|||
bhist.removePage(PlacesUtils._uri(node.uri));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Removes the selection
|
||||
* @param txnName
|
||||
* @param aTxnName
|
||||
* A name for the transaction if this is being performed
|
||||
* as part of another operation.
|
||||
*/
|
||||
remove: function PC_remove(txnName) {
|
||||
NS_ASSERT(txnName !== undefined, "Must supply Transaction Name");
|
||||
remove: function PC_remove(aTxnName) {
|
||||
NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name");
|
||||
this._view.saveSelection(this._view.SAVE_SELECTION_REMOVE);
|
||||
|
||||
// Delete the selected rows. Do this by walking the selection backward, so
|
||||
// that when undo is performed they are re-inserted in the correct order.
|
||||
var type = this._view.getResult().root.type;
|
||||
if (PlacesUtils.nodeIsFolder(this._view.getResult().root))
|
||||
this._removeRowsFromBookmarks(txnName);
|
||||
this._removeRowsFromBookmarks(aTxnName);
|
||||
else
|
||||
this._removeRowsFromHistory();
|
||||
|
||||
this._view.restoreSelection();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Get a TransferDataSet containing the content of the selection that can be
|
||||
* dropped elsewhere.
|
||||
|
@ -1360,7 +1305,7 @@ PlacesController.prototype = {
|
|||
/**
|
||||
* Copy Bookmarks and Folders to the clipboard
|
||||
*/
|
||||
copy: function() {
|
||||
copy: function PC_copy() {
|
||||
var nodes = this._view.getCopyableSelection();
|
||||
|
||||
var xferable =
|
||||
|
@ -1416,7 +1361,7 @@ PlacesController.prototype = {
|
|||
addData(TYPE_UNICODE, unicodeString);
|
||||
if (htmlString)
|
||||
addData(TYPE_HTML, htmlString);
|
||||
|
||||
|
||||
if (pcString || psString || placeString || unicodeString || htmlString ||
|
||||
mozURLString) {
|
||||
var clipboard =
|
||||
|
@ -1428,7 +1373,7 @@ PlacesController.prototype = {
|
|||
/**
|
||||
* Cut Bookmarks and Folders to the clipboard
|
||||
*/
|
||||
cut: function() {
|
||||
cut: function PC_cut() {
|
||||
this.copy();
|
||||
this.remove("Cut Selection");
|
||||
},
|
||||
|
@ -1551,7 +1496,7 @@ var PlacesControllerDragHelper = {
|
|||
getService(Ci.nsIDragService);
|
||||
return dragService.getCurrentSession();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not the data currently being dragged can be dropped
|
||||
* on the specified view.
|
||||
|
|
|
@ -239,13 +239,7 @@
|
|||
if (PlacesUtils.nodeIsLivemarkContainer(child)) {
|
||||
element.setAttribute("livemark", "true");
|
||||
var folder = asFolder(child).folderId;
|
||||
var bms =
|
||||
Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
||||
getService(Ci.nsINavBookmarksService);
|
||||
var lms =
|
||||
Cc["@mozilla.org/browser/livemark-service;2"].
|
||||
getService(Ci.nsILivemarkService);
|
||||
var siteURI = lms.getSiteURI(folder);
|
||||
var siteURI = PlacesUtils.livemarks.getSiteURI(folder);
|
||||
if (siteURI) {
|
||||
element.setAttribute("siteURI", siteURI.spec);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
|
@ -55,11 +55,11 @@ var PlacesOrganizer = {
|
|||
|
||||
init: function PO_init() {
|
||||
this._places = document.getElementById("placesList");
|
||||
this._content = document.getElementById("placeContent");
|
||||
|
||||
this._content = document.getElementById("placeContent");
|
||||
|
||||
OptionsFilter.init(Groupers);
|
||||
Groupers.init();
|
||||
|
||||
|
||||
// Select the specified place in the places list.
|
||||
var placeURI = "place:";
|
||||
if ("arguments" in window)
|
||||
|
@ -74,19 +74,19 @@ var PlacesOrganizer = {
|
|||
|
||||
// Set up the search UI.
|
||||
PlacesSearchBox.init();
|
||||
|
||||
|
||||
// Set up the advanced query builder UI
|
||||
PlacesQueryBuilder.init();
|
||||
},
|
||||
|
||||
|
||||
destroy: function PO_destroy() {
|
||||
OptionsFilter.destroy();
|
||||
},
|
||||
|
||||
|
||||
HEADER_TYPE_SHOWING: 1,
|
||||
HEADER_TYPE_SEARCH: 2,
|
||||
HEADER_TYPE_ADVANCED_SEARCH: 3,
|
||||
|
||||
|
||||
/**
|
||||
* Updates the text shown in the heading banner above the content view.
|
||||
* @param type
|
||||
|
@ -101,7 +101,7 @@ var PlacesOrganizer = {
|
|||
var prefix = document.getElementById("showingPrefix");
|
||||
prefix.setAttribute("value",
|
||||
PlacesUtils.getString("headerTextPrefix" + type));
|
||||
|
||||
|
||||
var contentTitle = document.getElementById("contentTitle");
|
||||
contentTitle.setAttribute("value", text);
|
||||
|
||||
|
@ -109,16 +109,17 @@ var PlacesOrganizer = {
|
|||
var searchModifiers = document.getElementById("searchModifiers");
|
||||
searchModifiers.hidden = type == this.HEADER_TYPE_SHOWING;
|
||||
},
|
||||
|
||||
onPlaceURIKeypress: function PO_onPlaceURIKeypress(event) {
|
||||
if (event.keyCode == 13)
|
||||
|
||||
onPlaceURIKeypress: function PO_onPlaceURIKeypress(aEvent) {
|
||||
var keyCode = aEvent.keyCode;
|
||||
if (keyCode == KeyEvent.DOM_VK_RETURN)
|
||||
this.loadPlaceURI();
|
||||
else if (event.keyCode == 27) {
|
||||
else if (keyCode == KeyEvent.DOM_VK_ESCAPE) {
|
||||
event.target.value = "";
|
||||
this.onPlaceSelected(true);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Shows or hides the debug panel.
|
||||
*/
|
||||
|
@ -128,7 +129,7 @@ var PlacesOrganizer = {
|
|||
if (!dp.hidden)
|
||||
document.getElementById("placeURI").focus();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Loads the place URI entered in the debug
|
||||
*/
|
||||
|
@ -146,14 +147,14 @@ var PlacesOrganizer = {
|
|||
else
|
||||
options = optionsRef.value;
|
||||
this._content.load(queriesRef.value, options);
|
||||
|
||||
|
||||
this.setHeaderText(this.HEADER_TYPE_SHOWING, "Debug results for: " + placeURI.value);
|
||||
|
||||
|
||||
this.updateLoadedURI();
|
||||
|
||||
|
||||
placeURI.focus();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Updates the URI displayed in the debug panel.
|
||||
*/
|
||||
|
@ -166,7 +167,7 @@ var PlacesOrganizer = {
|
|||
PlacesUtils.history.queriesToQueryString(queries, queries.length,
|
||||
options);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Called when a place folder is selected in the left pane.
|
||||
* @param resetSearchBox
|
||||
|
@ -194,9 +195,9 @@ var PlacesOrganizer = {
|
|||
}
|
||||
|
||||
this.setHeaderText(this.HEADER_TYPE_SHOWING, node.title);
|
||||
|
||||
|
||||
this.updateLoadedURI();
|
||||
|
||||
|
||||
// Update the "Find in <current collection>" command and the gray text in
|
||||
// the search box in the toolbar if the active collection is the current
|
||||
// collection.
|
||||
|
@ -208,27 +209,28 @@ var PlacesOrganizer = {
|
|||
PlacesSearchBox.syncGrayText();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Handle clicks on the tree. If the user middle clicks on a URL, load that
|
||||
* URL according to rules. Single clicks or modified clicks do not result in
|
||||
* any special action, since they're related to selection.
|
||||
* @param event
|
||||
* @param aEvent
|
||||
* The mouse event.
|
||||
*/
|
||||
onTreeClick: function PO_onTreeClicked(event) {
|
||||
var currentView = event.currentTarget;
|
||||
onTreeClick: function PO_onTreeClicked(aEvent) {
|
||||
var currentView = aEvent.currentTarget;
|
||||
var controller = currentView.controller;
|
||||
|
||||
// If the user clicked on a tree column header, update the sorting
|
||||
// preferences to reflect their choices.
|
||||
if (event.target.localName == "treecol") {
|
||||
// XXXmano: should be done in tree.xml
|
||||
if (aEvent.target.localName == "treecol") {
|
||||
OptionsFilter.update(this._content.getResult());
|
||||
return;
|
||||
}
|
||||
if (currentView.hasSingleSelection && event.button == 1) {
|
||||
if (currentView.hasSingleSelection && aEvent.button == 1) {
|
||||
if (PlacesUtils.nodeIsURI(currentView.selectedNode))
|
||||
controller.openSelectedNodeWithEvent(event);
|
||||
controller.openSelectedNodeWithEvent(aEvent);
|
||||
else if (PlacesUtils.nodeIsContainer(currentView.selectedNode)) {
|
||||
// The command execution function will take care of seeing the
|
||||
// selection is a folder/container and loading its contents in
|
||||
|
@ -237,7 +239,7 @@ var PlacesOrganizer = {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Returns the options associated with the query currently loaded in the
|
||||
* main places pane.
|
||||
|
@ -258,20 +260,17 @@ var PlacesOrganizer = {
|
|||
/**
|
||||
* Allows simple exporting of bookmarks.
|
||||
*/
|
||||
exportBookmarks: function PO_export() {
|
||||
exportBookmarks: function PO_exportBookmarks() {
|
||||
var fp = Cc["@mozilla.org/filepicker;1"].
|
||||
createInstance(Ci.nsIFilePicker);
|
||||
createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, "", Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterHTML);
|
||||
fp.defaultString = "bookmarks.html";
|
||||
if (fp.show() != Ci.nsIFilePicker.returnCancel) {
|
||||
var bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
||||
getService(Ci.nsINavBookmarksService);
|
||||
bms.exportBookmarksHTML(fp.file);
|
||||
}
|
||||
if (fp.show() != Ci.nsIFilePicker.returnCancel)
|
||||
PlacesUtils.exportBookmarksHTML(fp.file);
|
||||
},
|
||||
|
||||
updateStatusBarForView: function G_updateStatusBarForView(aView) {
|
||||
updateStatusBarForView: function PO_updateStatusBarForView(aView) {
|
||||
var statusText = "";
|
||||
var selectedNode = aView.selectedNode;
|
||||
if (selectedNode) {
|
||||
|
@ -300,7 +299,7 @@ var PlacesSearchBox = {
|
|||
get searchFilter() {
|
||||
return document.getElementById("searchFilter");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Run a search for the specified text, over the collection specified by
|
||||
* the dropdown arrow. The default is all bookmarks and history, but can be
|
||||
|
@ -314,10 +313,10 @@ var PlacesSearchBox = {
|
|||
// update the view.
|
||||
if (filterString == "" || this.searchFilter.hasAttribute("empty"))
|
||||
return;
|
||||
|
||||
|
||||
var content = PlacesOrganizer._content;
|
||||
var PO = PlacesOrganizer;
|
||||
|
||||
|
||||
switch (PlacesSearchBox.filterCollection) {
|
||||
case "collection":
|
||||
var folderId = asFolder(content.getResult().root).folderId;
|
||||
|
@ -332,7 +331,7 @@ var PlacesSearchBox = {
|
|||
|
||||
this.searchFilter.setAttribute("filtered", "true");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Finds across all bookmarks and history.
|
||||
*/
|
||||
|
@ -340,7 +339,7 @@ var PlacesSearchBox = {
|
|||
this.filterCollection = "all";
|
||||
this.focus();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Finds in the currently selected Place.
|
||||
*/
|
||||
|
@ -348,7 +347,7 @@ var PlacesSearchBox = {
|
|||
this.filterCollection = "collection";
|
||||
this.focus();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Updates the display with the title of the current collection.
|
||||
* @param title
|
||||
|
@ -362,14 +361,14 @@ var PlacesSearchBox = {
|
|||
else
|
||||
this.searchFilter.grayText = PlacesUtils.getString("searchDefault");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Updates the display with the current gray text.
|
||||
*/
|
||||
syncGrayText: function PSB_syncGrayText() {
|
||||
this.searchFilter.value = this.searchFilter.grayText;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gets/sets the active collection from the dropdown menu.
|
||||
*/
|
||||
|
@ -384,14 +383,14 @@ var PlacesSearchBox = {
|
|||
this.updateCollectionTitle(newGrayText);
|
||||
return collectionName;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Focus the search box
|
||||
*/
|
||||
focus: function PSB_focus() {
|
||||
this.searchFilter.focus();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Set up the gray text in the search bar as the Places View loads.
|
||||
*/
|
||||
|
@ -418,7 +417,7 @@ var PlacesSearchBox = {
|
|||
var PlacesQueryBuilder = {
|
||||
|
||||
_numRows: 0,
|
||||
|
||||
|
||||
/**
|
||||
* The maximum number of terms that can be added.
|
||||
* XXXben - this should be generated dynamically based on the contents of a
|
||||
|
@ -426,7 +425,7 @@ var PlacesQueryBuilder = {
|
|||
* a hard coded number.
|
||||
*/
|
||||
_maxRows: 3,
|
||||
|
||||
|
||||
_keywordSearch: {
|
||||
advancedSearch_N_Subject: "advancedSearch_N_SubjectKeyword",
|
||||
advancedSearch_N_LocationMenulist: false,
|
||||
|
@ -461,7 +460,7 @@ var PlacesQueryBuilder = {
|
|||
},
|
||||
_nextSearch: null,
|
||||
_queryBuilders: null,
|
||||
|
||||
|
||||
init: function PQB_init() {
|
||||
// Initialize advanced search
|
||||
this._nextSearch = {
|
||||
|
@ -475,11 +474,11 @@ var PlacesQueryBuilder = {
|
|||
"visited": this.setVisitedQuery,
|
||||
"location": this.setLocationQuery
|
||||
};
|
||||
|
||||
|
||||
this._dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
|
||||
getService(Ci.nsIScriptableDateFormat);
|
||||
getService(Ci.nsIScriptableDateFormat);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Hides the query builder, and the match rule UI if visible.
|
||||
*/
|
||||
|
@ -517,14 +516,14 @@ var PlacesQueryBuilder = {
|
|||
this._setRowId(element.childNodes[i], rowId);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
_updateUIForRowChange: function PQB__updateUIForRowChange() {
|
||||
// Titlebar should show "match any/all" iff there are > 1 queries visible.
|
||||
var titleDeck = document.getElementById("titleDeck");
|
||||
titleDeck.setAttribute("selectedIndex", (this._numRows <= 1) ? 0 : 1);
|
||||
const asType = PlacesOrganizer.HEADER_TYPE_ADVANCED_SEARCH;
|
||||
PlacesOrganizer.setHeaderText(asType, "");
|
||||
|
||||
|
||||
// Update the "can add more criteria" command to make sure various +
|
||||
// buttons are disabled.
|
||||
var command = document.getElementById("OrganizerCommand_find:moreCriteria");
|
||||
|
@ -533,7 +532,7 @@ var PlacesQueryBuilder = {
|
|||
else
|
||||
command.removeAttribute("disabled");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Adds a row to the view, prefilled with the next query subject. If the
|
||||
* query builder is not visible, it will be shown.
|
||||
|
@ -543,7 +542,7 @@ var PlacesQueryBuilder = {
|
|||
// of search query subjects.
|
||||
if (this._numRows >= this._maxRows)
|
||||
return;
|
||||
|
||||
|
||||
// Clone the template row and unset the hidden attribute.
|
||||
var gridRows = document.getElementById("advancedSearchRows");
|
||||
var newRow = gridRows.firstChild.cloneNode(true);
|
||||
|
@ -567,13 +566,13 @@ var PlacesQueryBuilder = {
|
|||
// determined, since this will interfere with the computation.
|
||||
gridRows.appendChild(newRow);
|
||||
this._setRowId(newRow, ++this._numRows);
|
||||
|
||||
|
||||
// Ensure the Advanced Search container is visible, if this is the first
|
||||
// row being added.
|
||||
var advancedSearch = document.getElementById("advancedSearch");
|
||||
if (advancedSearch.collapsed) {
|
||||
this.show();
|
||||
|
||||
|
||||
// Update the header.
|
||||
const asType = PlacesOrganizer.HEADER_TYPE_ADVANCED_SEARCH;
|
||||
PlacesOrganizer.setHeaderText(asType, "");
|
||||
|
@ -585,7 +584,7 @@ var PlacesQueryBuilder = {
|
|||
var searchTermsField = document.getElementById("advancedSearch1Textbox");
|
||||
if (searchTermsField)
|
||||
setTimeout(function() { searchTermsField.value = PlacesSearchBox.value; }, 10);
|
||||
|
||||
|
||||
// Call ourselves again to add a second row so that the user is presented
|
||||
// with more than just "Keyword Search" which they already performed from
|
||||
// the toolbar.
|
||||
|
@ -596,7 +595,7 @@ var PlacesQueryBuilder = {
|
|||
this.showSearch(this._numRows, searchType);
|
||||
this._updateUIForRowChange();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Remove a row from the set of terms
|
||||
* @param row
|
||||
|
@ -608,10 +607,10 @@ var PlacesQueryBuilder = {
|
|||
row = document.getElementById("advancedSearch" + this._numRows + "Row");
|
||||
row.parentNode.removeChild(row);
|
||||
--this._numRows;
|
||||
|
||||
|
||||
if (this._numRows < 1) {
|
||||
this.hide();
|
||||
|
||||
|
||||
// Re-do the original toolbar-search-box search that the user used to
|
||||
// spawn the advanced UI... this effectively "reverts" the UI to the
|
||||
// point it was in before they began monkeying with advanced search.
|
||||
|
@ -622,7 +621,7 @@ var PlacesQueryBuilder = {
|
|||
this.doSearch();
|
||||
this._updateUIForRowChange();
|
||||
},
|
||||
|
||||
|
||||
onDateTyped: function PQB_onDateTyped(event, row) {
|
||||
var textbox = document.getElementById("advancedSearch" + row + "TimePicker");
|
||||
var dateString = textbox.value;
|
||||
|
@ -650,7 +649,7 @@ var PlacesQueryBuilder = {
|
|||
if (d0 == null || d0 == "Invalid Date") {
|
||||
d0 = new Date(dateString);
|
||||
}
|
||||
|
||||
|
||||
if (d0 != null && d0 != "Invalid Date") {
|
||||
// Parsing succeeded -- update the calendar.
|
||||
var calendar = document.getElementById("advancedSearch" + row + "Calendar");
|
||||
|
@ -664,12 +663,12 @@ var PlacesQueryBuilder = {
|
|||
else {
|
||||
calendar.updateSelection(d0, d0);
|
||||
}
|
||||
|
||||
|
||||
// And update the search.
|
||||
this.doSearch();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onCalendarChanged: function PQB_onCalendarChanged(event, row) {
|
||||
var calendar = document.getElementById("advancedSearch" + row + "Calendar");
|
||||
var begin = calendar.beginrange;
|
||||
|
@ -716,7 +715,7 @@ var PlacesQueryBuilder = {
|
|||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
handleTimePickerClick: function PQB_handleTimePickerClick(event, row) {
|
||||
var popup = document.getElementById("advancedSearch" + row + "DatePopup");
|
||||
if (popup.showing)
|
||||
|
@ -726,7 +725,7 @@ var PlacesQueryBuilder = {
|
|||
popup.showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
showSearch: function PQB_showSearch(row, values) {
|
||||
for (val in values) {
|
||||
var id = val.replace("_N_", row);
|
||||
|
@ -764,11 +763,11 @@ var PlacesQueryBuilder = {
|
|||
|
||||
this.doSearch();
|
||||
},
|
||||
|
||||
|
||||
setKeywordQuery: function PQB_setKeywordQuery(query, prefix) {
|
||||
query.searchTerms += document.getElementById(prefix + "Textbox").value + " ";
|
||||
},
|
||||
|
||||
|
||||
setLocationQuery: function PQB_setLocationQuery(query, prefix) {
|
||||
var type = document.getElementById(prefix + "LocationMenulist").selectedItem.value;
|
||||
if (type == "onsite") {
|
||||
|
@ -795,7 +794,7 @@ var PlacesQueryBuilder = {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setVisitedQuery: function PQB_setVisitedQuery(query, prefix) {
|
||||
var searchType = document.getElementById(prefix + "TimeMenulist").selectedItem.value;
|
||||
const DAY_MSEC = 86400000;
|
||||
|
@ -835,7 +834,7 @@ var PlacesQueryBuilder = {
|
|||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
doSearch: function PQB_doSearch() {
|
||||
// Create the individual queries.
|
||||
var queryType = document.getElementById("advancedSearchType").selectedItem.value;
|
||||
|
@ -845,7 +844,7 @@ var PlacesQueryBuilder = {
|
|||
var updated = 0;
|
||||
for (var i = 1; updated < this._numRows; ++i) {
|
||||
var prefix = "advancedSearch" + i;
|
||||
|
||||
|
||||
// The user can remove rows from the middle and start of the list, not
|
||||
// just from the end, so we need to make sure that this row actually
|
||||
// exists before attempting to construct a query for it.
|
||||
|
@ -931,7 +930,7 @@ var ViewMenu = {
|
|||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Fills a menupopup with a list of columns
|
||||
* @param event
|
||||
|
@ -956,7 +955,7 @@ var ViewMenu = {
|
|||
fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, propertyPrefix) {
|
||||
var popup = event.target;
|
||||
var pivot = this._clean(popup, startID, endID);
|
||||
|
||||
|
||||
// If no column is "sort-active", the "Unsorted" item needs to be checked,
|
||||
// so track whether or not we find a column that is sort-active.
|
||||
var isSorted = false;
|
||||
|
@ -1006,7 +1005,7 @@ var ViewMenu = {
|
|||
*/
|
||||
populate: function VM_populate(event) {
|
||||
this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.");
|
||||
|
||||
|
||||
var sortColumn = this._getSortColumn();
|
||||
var viewSortAscending = document.getElementById("viewSortAscending");
|
||||
var viewSortDescending = document.getElementById("viewSortDescending");
|
||||
|
@ -1055,7 +1054,7 @@ var ViewMenu = {
|
|||
splitter.setAttribute("hidden", "true");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Gets the last column that was sorted.
|
||||
* @returns the currently sorted column, null if there is no sorted column.
|
||||
|
@ -1071,7 +1070,7 @@ var ViewMenu = {
|
|||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Sorts the view by the specified key.
|
||||
* @param aColumnID
|
||||
|
@ -1173,7 +1172,7 @@ function GroupingConfig(substr, onLabel, onAccesskey, offLabel, offAccesskey,
|
|||
var Groupers = {
|
||||
defaultGrouper: null,
|
||||
annotationGroupers: [],
|
||||
|
||||
|
||||
/**
|
||||
* Initializes groupings for various vie types.
|
||||
*/
|
||||
|
@ -1194,7 +1193,7 @@ var Groupers = {
|
|||
"Groupers.groupByPost()", false);
|
||||
this.annotationGroupers.push(subscriptionConfig);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Get the most appropriate GroupingConfig for the set of queries that are
|
||||
* about to be executed.
|
||||
|
@ -1207,13 +1206,13 @@ var Groupers = {
|
|||
_getConfig: function G__getConfig(queries, handler) {
|
||||
if (!handler)
|
||||
handler = OptionsFilter.getHandler(queries);
|
||||
|
||||
|
||||
// If the queries will generate a bookmarks folder, there is no "grouper
|
||||
// config" since all of the grouping UI should be hidden (there is only one
|
||||
// grouping mode - group by folder).
|
||||
if (handler == OptionsFilter.bookmarksHandler)
|
||||
return null;
|
||||
|
||||
|
||||
var query = queries[0];
|
||||
for (var i = 0; i < this.annotationGroupers.length; ++i) {
|
||||
var config = this.annotationGroupers[i];
|
||||
|
@ -1221,10 +1220,10 @@ var Groupers = {
|
|||
!config.disabled)
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
return this.defaultGrouper;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Updates the grouping broadcasters for the given result.
|
||||
* @param queries
|
||||
|
@ -1285,7 +1284,7 @@ var Groupers = {
|
|||
groupingOn.removeAttribute("checked");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Shows visited pages grouped by site.
|
||||
*/
|
||||
|
@ -1302,7 +1301,7 @@ var Groupers = {
|
|||
this._updateBroadcasters(true);
|
||||
OptionsFilter.update(content.getResult());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Shows visited pages without grouping.
|
||||
*/
|
||||
|
@ -1335,7 +1334,7 @@ var Groupers = {
|
|||
this._updateBroadcasters(false);
|
||||
OptionsFilter.update(content.getResult());
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Shows all subscribed feed (Live Bookmarks) content in a flat list
|
||||
*/
|
||||
|
@ -1353,4 +1352,3 @@ var Groupers = {
|
|||
}
|
||||
};
|
||||
|
||||
#include ../../../../toolkit/content/debug.js
|
||||
|
|
|
@ -645,9 +645,6 @@
|
|||
// Items that are "static" - i.e. above the user-configurable area
|
||||
// of the view - can not be moved.
|
||||
var nodes = this.getSelectionNodes();
|
||||
var bms =
|
||||
Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
||||
getService(Ci.nsINavBookmarksService);
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
var node = nodes[i];
|
||||
|
||||
|
|
|
@ -319,13 +319,26 @@ var PlacesUtils = {
|
|||
"@mozilla.org/browser/livemark-service;2");
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether a ResultNode is a live-bookmark item
|
||||
* @param aNode
|
||||
* A NavHistory Result Node
|
||||
* @returns true if the node is a livemark container item
|
||||
*/
|
||||
nodeIsLivemarkItem: function PU_nodeIsLivemarkItem(aNode) {
|
||||
NS_ASSERT(aNode, "null node");
|
||||
|
||||
return this.annotations.hasAnnotation(this._uri(aNode.uri),
|
||||
"livemark/bookmarkFeedURI");
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether or not a node is a readonly folder.
|
||||
* @param aNode
|
||||
* The node to test.
|
||||
* @returns true if the node is a readonly folder.
|
||||
*/
|
||||
folderIsReadonly: function(aNode) {
|
||||
isReadonlyFolder: function(aNode) {
|
||||
NS_ASSERT(aNode, "null node");
|
||||
|
||||
return this.nodeIsFolder(aNode) &&
|
||||
|
|
Загрузка…
Ссылка в новой задаче