зеркало из https://github.com/mozilla/pjs.git
329269, more command handling cleanup for places views. make sure the right commands are enabled for items in the readonly area of the places list, and generally. make sure reload commands are enabled appropriately. also, some spit and polish for the organizer window r=annie.sullivan@gmail.com
This commit is contained in:
Родитель
b14449f98a
Коммит
e21b0adb6f
|
@ -66,7 +66,9 @@
|
||||||
oncommand="PlacesController.newSeparator()"/>
|
oncommand="PlacesController.newSeparator()"/>
|
||||||
</commandset>
|
</commandset>
|
||||||
<commandset type="container|feed" readonly="true">
|
<commandset type="container|feed" readonly="true">
|
||||||
<command id="placesCmd_reload" label="&cmd.reload.label;" accesskey="&cmd.reload.accesskey;"/>
|
<command id="placesCmd_reload"
|
||||||
|
label="&cmd.reload.label;" accesskey="&cmd.reload.accesskey;"
|
||||||
|
disabled="true"/>
|
||||||
</commandset>
|
</commandset>
|
||||||
</commandset>
|
</commandset>
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,48 @@
|
||||||
<popup id="placesContext"
|
<!-- if you make changes to this file you need to rebuild in both browser/base
|
||||||
onpopupshowing="return PlacesController.buildContextMenu(this);">
|
and browser/components/places for the effect to appear in both the toolbar/
|
||||||
<!-- the rules defined in the selection attribute control whether or not
|
menu and the places organizer. -->
|
||||||
the menu item is _visible_ regardless of whether or not the command
|
<popup id="placesContext"
|
||||||
is enabled. -->
|
onpopupshowing="return PlacesController.buildContextMenu(this);">
|
||||||
<menuitem id="placesContext_open" command="placesCmd_open" default="true"
|
<!-- the rules defined in the selection attribute control whether or not
|
||||||
selection="link"/>
|
the menu item is _visible_ regardless of whether or not the command
|
||||||
<menuitem id="placesContext_open:tabs" command="placesCmd_open:tabs"
|
is enabled. -->
|
||||||
selection="folder|links"/>
|
<menuitem id="placesContext_open" command="placesCmd_open" default="true"
|
||||||
<menuitem id="placesContext_open:newwindow" command="placesCmd_open:window"
|
selection="link"/>
|
||||||
selection="link"/>
|
<menuitem id="placesContext_open:tabs" command="placesCmd_open:tabs"
|
||||||
<menuitem id="placesContext_open:newtab" command="placesCmd_open:tab"
|
selection="folder|links"/>
|
||||||
selection="link"/>
|
<menuitem id="placesContext_open:newwindow" command="placesCmd_open:window"
|
||||||
<menuseparator id="placesContext_openSeparator"
|
selection="link"/>
|
||||||
selection="link|links|folder"/>
|
<menuitem id="placesContext_open:newtab" command="placesCmd_open:tab"
|
||||||
<menuitem id="placesContext_new:folder" command="placesCmd_new:folder"
|
selection="link"/>
|
||||||
selection="mutable"/>
|
<menuseparator id="placesContext_openSeparator"
|
||||||
<menuitem id="placesContext_new:separator" command="placesCmd_new:separator"
|
selection="link|links|folder"/>
|
||||||
selection="mutable"/>
|
<menuitem id="placesContext_new:folder" command="placesCmd_new:folder"
|
||||||
<menuseparator id="placesContext_newSeparator"
|
selection="mutable"/>
|
||||||
selection="mutable"/>
|
<menuitem id="placesContext_new:separator" command="placesCmd_new:separator"
|
||||||
<menuitem id="placesContext_edit:cut" command="placesCmd_edit:cut"
|
selection="mutable"/>
|
||||||
selection="mixed"/>
|
<menuseparator id="placesContext_newSeparator"
|
||||||
<menuitem id="placesContext_edit:copy" command="placesCmd_edit:copy"
|
selection="mutable"/>
|
||||||
selection="mixed"/>
|
<menuitem id="placesContext_edit:cut" command="placesCmd_edit:cut"
|
||||||
<menuitem id="placesContext_edit:paste" command="placesCmd_edit:paste"
|
selection="link|links|folder|mixed"/>
|
||||||
selection="mixed"/>
|
<menuitem id="placesContext_edit:copy" command="placesCmd_edit:copy"
|
||||||
<menuitem id="placesContext_edit:delete" command="placesCmd_edit:delete"
|
selection="link|links|folder|mixed"/>
|
||||||
selection="mixed"/>
|
<menuitem id="placesContext_edit:paste" command="placesCmd_edit:paste"
|
||||||
<menuseparator id="placesContext_editSeparator"
|
selection="link|links|folder|mixed"/>
|
||||||
selection="mixed"/>
|
<menuitem id="placesContext_edit:delete" command="placesCmd_edit:delete"
|
||||||
<menuitem id="placesContext_select:all" command="placesCmd_select:all"
|
selection="link|links|folder|mixed"/>
|
||||||
selection="multiselect"/>
|
<menuseparator id="placesContext_editSeparator"
|
||||||
<menuseparator id="placesContext_selectSeparator"
|
selection="link|links|folder|mixed"/>
|
||||||
selection="multiselect"/>
|
<menuitem id="placesContext_select:all" command="placesCmd_select:all"
|
||||||
<menuitem id="placesContext_reload" command="placesCmd_reload"
|
selection="multiselect"/>
|
||||||
selection="livemark"/>
|
<menuseparator id="placesContext_selectSeparator"
|
||||||
<menuitem id="placesContext_sortby:name" command="placesCmd_sortby:name"
|
selection="multiselect"/>
|
||||||
selection="mutable"/>
|
<menuitem id="placesContext_reload" command="placesCmd_reload"
|
||||||
<menuseparator id="placesContext_sortSeparator"
|
selection="livemark/feedURI|livemark/bookmarkFeedURI"/>
|
||||||
selection="mutable|livemark"/>
|
<menuitem id="placesContext_sortby:name" command="placesCmd_sortby:name"
|
||||||
<menuitem id="placesContext_show:info" command="placesCmd_show:info"
|
selection="mutable"/>
|
||||||
selection="link|folder"/>
|
<menuseparator id="placesContext_sortSeparator"
|
||||||
</popup>
|
selection="mutable|livemark/feedURI|livemark/bookmarkFeedURI"/>
|
||||||
|
<menuitem id="placesContext_show:info" command="placesCmd_show:info"
|
||||||
|
selection="link|folder|query"/>
|
||||||
|
</popup>
|
||||||
|
|
||||||
|
|
|
@ -45,14 +45,6 @@ const Cr = Components.results;
|
||||||
|
|
||||||
const NHRVO = Ci.nsINavHistoryResultViewObserver;
|
const NHRVO = Ci.nsINavHistoryResultViewObserver;
|
||||||
|
|
||||||
const SELECTION_CONTAINS_URI = 0x01;
|
|
||||||
const SELECTION_CONTAINS_CONTAINER = 0x02;
|
|
||||||
const SELECTION_IS_OPEN_CONTAINER = 0x04;
|
|
||||||
const SELECTION_IS_CLOSED_CONTAINER = 0x08;
|
|
||||||
const SELECTION_IS_CHANGEABLE = 0x10;
|
|
||||||
const SELECTION_IS_REMOVABLE = 0x20;
|
|
||||||
const SELECTION_IS_MOVABLE = 0x40;
|
|
||||||
|
|
||||||
// These need to be kept in sync with the meaning of these roots in
|
// These need to be kept in sync with the meaning of these roots in
|
||||||
// default_places.html!
|
// default_places.html!
|
||||||
const ORGANIZER_ROOT_HISTORY = "place:&beginTime=-2592000000000&beginTimeRef=1&endTime=7200000000&endTimeRef=2&sort=4&type=1";
|
const ORGANIZER_ROOT_HISTORY = "place:&beginTime=-2592000000000&beginTimeRef=1&endTime=7200000000&endTimeRef=2&sort=4&type=1";
|
||||||
|
@ -389,11 +381,7 @@ var PlacesController = {
|
||||||
|
|
||||||
isCommandEnabled: function PC_isCommandEnabled(command) {
|
isCommandEnabled: function PC_isCommandEnabled(command) {
|
||||||
//LOG("isCommandEnabled: " + command);
|
//LOG("isCommandEnabled: " + command);
|
||||||
if (command == "cmd_undo")
|
return document.getElementById(command).getAttribute("disabled") != "true";
|
||||||
return this.tm.numberOfUndoItems > 0;
|
|
||||||
if (command == "cmd_redo")
|
|
||||||
return this.tm.numberOfRedoItems > 0;
|
|
||||||
return document.getElementById(command).getAttribute("disabled") == "true";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
supportsCommand: function PC_supportsCommand(command) {
|
supportsCommand: function PC_supportsCommand(command) {
|
||||||
|
@ -452,8 +440,20 @@ var PlacesController = {
|
||||||
var nodes = this._activeView.getSelectionNodes();
|
var nodes = this._activeView.getSelectionNodes();
|
||||||
for (var i = 0; i < nodes.length; ++i) {
|
for (var i = 0; i < nodes.length; ++i) {
|
||||||
var parent = nodes[i].parent || this._activeView.getResult().root;
|
var parent = nodes[i].parent || this._activeView.getResult().root;
|
||||||
if (this.nodeIsReadOnly(parent))
|
// We don't call nodeIsReadOnly here, because nodeIsReadOnly means that
|
||||||
return false;
|
// a node has children that cannot be edited, reordered or removed. Here,
|
||||||
|
// we don't care if a node's children can't be reordered or edited, just
|
||||||
|
// that they're removable. All history results have removable children
|
||||||
|
// (based on the principle that any URL in the history table should be
|
||||||
|
// removable), but some special bookmark folders may have non-removable
|
||||||
|
// 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 (this.nodeIsFolder(parent)) {
|
||||||
|
var readOnly = this.bookmarks.getFolderReadonly(asFolder(parent).folderId);
|
||||||
|
if (readOnly)
|
||||||
|
return !readOnly;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -499,16 +499,6 @@ var PlacesController = {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether or not the paste command is enabled, based on the
|
|
||||||
* content of the clipboard and the selection within the active view.
|
|
||||||
*/
|
|
||||||
_canPaste: function PC__canPaste() {
|
|
||||||
if (this._canInsert())
|
|
||||||
return this._hasClipboardData();
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether or not a ResultNode is a Bookmark folder or not.
|
* Determines whether or not a ResultNode is a Bookmark folder or not.
|
||||||
* @param node
|
* @param node
|
||||||
|
@ -571,7 +561,7 @@ var PlacesController = {
|
||||||
return asQuery(node).childrenReadOnly;
|
return asQuery(node).childrenReadOnly;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether or not a ResultNode is a host folder or not
|
* Determines whether or not a ResultNode is a host folder or not
|
||||||
* @param node
|
* @param node
|
||||||
|
@ -604,7 +594,30 @@ var PlacesController = {
|
||||||
*/
|
*/
|
||||||
nodeIsRemoteContainer: function PC_nodeIsRemoteContainer(node) {
|
nodeIsRemoteContainer: function PC_nodeIsRemoteContainer(node) {
|
||||||
const NHRN = Ci.nsINavHistoryResultNode;
|
const NHRN = Ci.nsINavHistoryResultNode;
|
||||||
return (node.type == NHRN.RESULT_TYPE_REMOTE_CONTAINER);
|
return node.type == NHRN.RESULT_TYPE_REMOTE_CONTAINER;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates undo/redo commands.
|
||||||
|
*/
|
||||||
|
updateTMCommands: function PC_updateTMCommands() {
|
||||||
|
this._setEnabled("cmd_undo", this.tm.numberOfUndoItems > 0);
|
||||||
|
this._setEnabled("cmd_redo", this.tm.numberOfRedoItems > 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the selection intersects the read only "system"
|
||||||
|
* portion of the display.
|
||||||
|
* @returns true if the selection intersects, false otherwise.
|
||||||
|
*/
|
||||||
|
_selectionOverlapsSystemArea: function PC__selectionOverlapsSystemArea() {
|
||||||
|
var v = this.activeView;
|
||||||
|
var nodes = v.getSelectionNodes();
|
||||||
|
for (var i = 0; i < nodes.length; ++i) {
|
||||||
|
if (this.getIndexOfNode(nodes[i]) >= v.peerDropIndex)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -612,53 +625,248 @@ var PlacesController = {
|
||||||
* disabledness of commands in relation to the state of the selection.
|
* disabledness of commands in relation to the state of the selection.
|
||||||
*/
|
*/
|
||||||
onCommandUpdate: function PC_onCommandUpdate() {
|
onCommandUpdate: function PC_onCommandUpdate() {
|
||||||
if (!this._activeView) {
|
var v = this._activeView;
|
||||||
|
if (!v) {
|
||||||
// Initial update, no view is set yet
|
// Initial update, no view is set yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select All
|
var inSysArea = this._selectionOverlapsSystemArea();
|
||||||
this._setEnabled("placesCmd_select:all",
|
var selectedNode = v.selectedNode;
|
||||||
this._activeView.getAttribute("seltype") != "single");
|
var canInsert = this._canInsert();
|
||||||
// Show Info
|
|
||||||
var hasSingleSelection = this._activeView.hasSingleSelection;
|
|
||||||
this._setEnabled("placesCmd_show:info", hasSingleSelection);
|
|
||||||
// Cut
|
|
||||||
var removableSelection = this._hasRemovableSelection();
|
|
||||||
this._setEnabled("placesCmd_edit:cut", removableSelection);
|
|
||||||
this._setEnabled("placesCmd_edit:delete", removableSelection);
|
|
||||||
// Copy
|
|
||||||
this._setEnabled("placesCmd_edit:copy", this._activeView.hasSelection);
|
|
||||||
// Paste
|
|
||||||
this._setEnabled("placesCmd_edit:paste", this._canPaste());
|
|
||||||
// Open
|
|
||||||
var hasSelectedURI = this._activeView.selectedURINode != null;
|
|
||||||
this._setEnabled("placesCmd_open", hasSelectedURI);
|
|
||||||
this._setEnabled("placesCmd_open:window", hasSelectedURI);
|
|
||||||
this._setEnabled("placesCmd_open:tab", hasSelectedURI);
|
|
||||||
|
|
||||||
|
// Select All
|
||||||
|
this._setEnabled("placesCmd_select:all", v.selType != "single");
|
||||||
|
// Show Info
|
||||||
|
var hasSingleSelection = v.hasSingleSelection;
|
||||||
|
this._setEnabled("placesCmd_show:info", !inSysArea && hasSingleSelection);
|
||||||
|
this._updateEditCommands(inSysArea, canInsert);
|
||||||
|
this._updateOpenCommands(inSysArea, hasSingleSelection, selectedNode);
|
||||||
|
this._updateSortCommands(inSysArea, hasSingleSelection, selectedNode, canInsert);
|
||||||
|
this._updateCreateCommands(inSysArea, canInsert);
|
||||||
|
this._updateLivemarkCommands(hasSingleSelection, selectedNode);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates commands for persistent sorting
|
||||||
|
* @param inSysArea
|
||||||
|
* true if the selection intersects the read only "system" area.
|
||||||
|
* @param hasSingleSelection
|
||||||
|
* true if only one item is selected in the view
|
||||||
|
* @param selectedNode
|
||||||
|
* The selected nsINavHistoryResultNode
|
||||||
|
* @param canInsert
|
||||||
|
* true if the item is a writable container that can be inserted
|
||||||
|
* into
|
||||||
|
*/
|
||||||
|
_updateSortCommands:
|
||||||
|
function PC__updateSortCommands(inSysArea, hasSingleSelection, selectedNode,
|
||||||
|
canInsert) {
|
||||||
|
// Some views, like menupopups, destroy their result as they hide, but they
|
||||||
|
// are still the "last-active" view. Don't barf.
|
||||||
|
var result = this.activeView.getResult();
|
||||||
|
var viewIsFolder = result ? this.nodeIsFolder(result.root) : false;
|
||||||
|
|
||||||
|
// Depending on the selection, the persistent sort command sorts the
|
||||||
|
// contents of the current folder (when the selection is mixed or leaf
|
||||||
|
// items like individual bookmarks are selected) or the contents of the
|
||||||
|
// selected folder (if a single folder is selected).
|
||||||
|
var sortingChildren = false;
|
||||||
|
var name = result.root.title;
|
||||||
|
if (selectedNode)
|
||||||
|
name = selectedNode.parent.title;
|
||||||
|
if (hasSingleSelection && this.nodeIsFolder(selectedNode)) {
|
||||||
|
name = selectedNode.title;
|
||||||
|
sortingChildren = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata = this._buildSelectionMetadata();
|
||||||
|
this._setEnabled("placesCmd_sortby:name",
|
||||||
|
(!inSysArea || sortingChildren) && canInsert && viewIsFolder &&
|
||||||
|
!("mixed" in metadata));
|
||||||
|
|
||||||
|
var strings = document.getElementById("placeBundle");
|
||||||
|
var command = document.getElementById("placesCmd_sortby:name");
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
command.setAttribute("label",
|
||||||
|
strings.getFormattedString("sortByName", [name]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
command.setAttribute("label", strings.getString("sortByNameGeneric"));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates commands for opening links
|
||||||
|
* @param inSysArea
|
||||||
|
* true if the selection intersects the read only "system" area.
|
||||||
|
* @param hasSingleSelection
|
||||||
|
* true if only one item is selected in the view
|
||||||
|
* @param selectedNode
|
||||||
|
* The selected nsINavHistoryResultNode
|
||||||
|
*/
|
||||||
|
_updateOpenCommands:
|
||||||
|
function PC__updateOpenCommands(inSysArea, hasSingleSelection, selectedNode) {
|
||||||
|
// Open
|
||||||
|
var hasSelectedURI = this.activeView.selectedURINode != null;
|
||||||
|
this._setEnabled("placesCmd_open", !inSysArea && hasSelectedURI);
|
||||||
|
this._setEnabled("placesCmd_open:window", !inSysArea && hasSelectedURI);
|
||||||
|
this._setEnabled("placesCmd_open:tab", !inSysArea && hasSelectedURI);
|
||||||
|
|
||||||
// We can open multiple links in tabs if there is either:
|
// We can open multiple links in tabs if there is either:
|
||||||
// a) a single folder selected
|
// a) a single folder selected
|
||||||
// b) many links or folders selected
|
// b) many links or folders selected
|
||||||
var singleFolderSelected = hasSingleSelection &&
|
var singleFolderSelected = hasSingleSelection &&
|
||||||
this.nodeIsFolder(this._activeView.selectedNode);
|
this.nodeIsFolder(selectedNode);
|
||||||
this._setEnabled("placesCmd_open:tabs",
|
this._setEnabled("placesCmd_open:tabs",
|
||||||
singleFolderSelected || !hasSingleSelection);
|
!inSysArea && (singleFolderSelected ||
|
||||||
|
!hasSingleSelection));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the active view can support inserting items of a certain type.
|
||||||
|
*/
|
||||||
|
_viewSupportsInsertingType: function PC__viewSupportsInsertingType(type) {
|
||||||
|
var types = this.activeView.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.
|
||||||
|
* @returns true if the data is paste-able, false if the clipboard data
|
||||||
|
* cannot be pasted
|
||||||
|
*/
|
||||||
|
_canPaste: function PC__canPaste() {
|
||||||
|
var xferable =
|
||||||
|
Cc["@mozilla.org/widget/transferable;1"].
|
||||||
|
createInstance(Ci.nsITransferable);
|
||||||
|
xferable.addDataFlavor(TYPE_X_MOZ_PLACE_CONTAINER);
|
||||||
|
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);
|
||||||
|
|
||||||
|
var data = { }, type = { };
|
||||||
|
xferable.getAnyTransferData(type, data, { });
|
||||||
|
data = data.value.QueryInterface(Ci.nsISupportsString).data;
|
||||||
|
if (!this._viewSupportsInsertingType(type.value))
|
||||||
|
return false;
|
||||||
|
try {
|
||||||
|
this.unwrapNodes(data, type.value);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// Unwrap nodes failed, possibly because a field that should have
|
||||||
|
// contained a URI did not actually contain something that is
|
||||||
|
// parse-able as a URI.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates commands for edit operations (cut, copy, paste, delete)
|
||||||
|
* @param inSysArea
|
||||||
|
* true if the selection intersects the read only "system" area.
|
||||||
|
* @param canInsert
|
||||||
|
* true if the item is a writable container that can be inserted
|
||||||
|
* into
|
||||||
|
*/
|
||||||
|
_updateEditCommands: function PC__updateEditCommands(inSysArea, canInsert) {
|
||||||
|
// Cut: only if there's a removable selection. cannot remove system items.
|
||||||
|
var removableSelection = this._hasRemovableSelection();
|
||||||
|
this._setEnabled("placesCmd_edit:cut", !inSysArea && removableSelection);
|
||||||
|
this._setEnabled("placesCmd_edit:delete",
|
||||||
|
!inSysArea && removableSelection);
|
||||||
|
// Copy: only if there's a selection. cannot copy system items.
|
||||||
|
this._setEnabled("placesCmd_edit:copy",
|
||||||
|
!inSysArea && this.activeView.hasSelection);
|
||||||
|
// Paste: cannot paste adjacent to system items. only if the containing
|
||||||
|
// folder is not read only. only if there is clipboard data. only
|
||||||
|
// if that data is a valid place format.
|
||||||
|
this._setEnabled("placesCmd_edit:paste", !inSysArea && canInsert &&
|
||||||
|
this._hasClipboardData() && this._canPaste());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates commands for creating new bookmarks, folders and separators.
|
||||||
|
* @param inSysArea
|
||||||
|
* true if the selection intersects the read only "system" area.
|
||||||
|
* @param canInsert
|
||||||
|
* true if the item is a writable container that can be inserted
|
||||||
|
* into
|
||||||
|
*/
|
||||||
|
_updateCreateCommands:
|
||||||
|
function PC__updateCreateCommands(inSysArea, canInsert) {
|
||||||
|
var canInsertFolders = canInsertSeparators = canInsertURLs = false;
|
||||||
|
var peerTypes = this.activeView.peerDropTypes;
|
||||||
|
for (var i = 0; i < peerTypes.length; ++i) {
|
||||||
|
switch(peerTypes[i]) {
|
||||||
|
case TYPE_X_MOZ_PLACE_CONTAINER:
|
||||||
|
canInsertFolders = true;
|
||||||
|
break;
|
||||||
|
case TYPE_X_MOZ_PLACE_SEPARATOR:
|
||||||
|
canInsertSeparators = true;
|
||||||
|
break;
|
||||||
|
case TYPE_X_MOZ_URL:
|
||||||
|
canInsertURLs = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Some views, like menupopups, destroy their result as they hide, but they
|
|
||||||
// are still the "last-active" view. Don't barf.
|
|
||||||
var result = this._activeView.getResult();
|
|
||||||
var viewIsFolder = result ? this.nodeIsFolder(result.root) : false;
|
|
||||||
var canInsert = this._canInsert();
|
|
||||||
|
|
||||||
// Persistent Sort
|
|
||||||
this._setEnabled("placesCmd_sortby:name", viewIsFolder && canInsert);
|
|
||||||
// New Folder
|
// New Folder
|
||||||
this._setEnabled("placesCmd_new:folder", viewIsFolder && canInsert);
|
this._setEnabled("placesCmd_new:folder", !inSysArea && canInsertFolders && canInsert);
|
||||||
|
|
||||||
|
// New Bookmark
|
||||||
|
this._setEnabled("placesCmd_new:bookmark", !inSysArea && canInsertURLs && canInsert);
|
||||||
// New Separator
|
// New Separator
|
||||||
this._setEnabled("placesCmd_new:separator", viewIsFolder && canInsert);
|
this._setEnabled("placesCmd_new:separator",
|
||||||
// Feed Reload
|
!inSysArea && canInsertSeparators && canInsert);
|
||||||
this._setEnabled("placesCmd_reload", false);
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates Livemark Commands: Reload
|
||||||
|
* @param hasSingleSelection
|
||||||
|
* true if only one item is selected in the view
|
||||||
|
* @param selectedNode
|
||||||
|
* The selected nsINavHistoryResultNode
|
||||||
|
*/
|
||||||
|
_updateLivemarkCommands:
|
||||||
|
function PC__updateLivemarkCommands(hasSingleSelection, selectedNode) {
|
||||||
|
var isLivemarkItem = false;
|
||||||
|
var strings = document.getElementById("placeBundle");
|
||||||
|
var command = document.getElementById("placesCmd_reload");
|
||||||
|
if (hasSingleSelection) {
|
||||||
|
if (selectedNode.uri.indexOf("livemark%2F") != -1) {
|
||||||
|
isLivemarkItem = true;
|
||||||
|
command.setAttribute("label", strings.getString("livemarkReloadAll"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var uri = this._uri(selectedNode.uri);
|
||||||
|
isLivemarkItem =
|
||||||
|
this.annotations.hasAnnotation(uri, "livemark/bookmarkFeedURI");
|
||||||
|
if (isLivemarkItem)
|
||||||
|
var name = selectedNode.parent.title;
|
||||||
|
if (!isLivemarkItem && this.nodeIsFolder(selectedNode)) {
|
||||||
|
var folderId = asFolder(selectedNode).folderId;
|
||||||
|
uri = this.bookmarks.getFolderURI(folderId);
|
||||||
|
isLivemarkItem = this.annotations.hasAnnotation(uri, "livemark/feedURI");
|
||||||
|
name = selectedNode.title;
|
||||||
|
}
|
||||||
|
command.setAttribute("label",
|
||||||
|
strings.getFormattedString("livemarkReloadOne", [name]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isLivemarkItem)
|
||||||
|
command.setAttribute("label", strings.getString("livemarkReload"));
|
||||||
|
|
||||||
|
this._setEnabled("placesCmd_reload", isLivemarkItem);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -669,14 +877,17 @@ var PlacesController = {
|
||||||
* is-links "links"
|
* is-links "links"
|
||||||
* is-folder "folder"
|
* is-folder "folder"
|
||||||
* is-mutable "mutable"
|
* is-mutable "mutable"
|
||||||
|
* is-mixed "mixed"
|
||||||
* is-removable "removable"
|
* is-removable "removable"
|
||||||
* is-multiselect"multiselect"
|
* is-multiselect"multiselect"
|
||||||
* is-container "remotecontainer"
|
* is-container "remotecontainer"
|
||||||
|
* is-query "query"
|
||||||
* @returns an object with each of the properties above set if the selection
|
* @returns an object with each of the properties above set if the selection
|
||||||
* matches that rule.
|
* matches that rule.
|
||||||
|
* Note: This can be slow, so don't call it anywhere performance critical!
|
||||||
*/
|
*/
|
||||||
_buildSelectionMetadata: function PC__buildSelectionMetadata() {
|
_buildSelectionMetadata: function PC__buildSelectionMetadata() {
|
||||||
var metadata = { mixed: true };
|
var metadata = { };
|
||||||
|
|
||||||
var hasSingleSelection = this._activeView.hasSingleSelection;
|
var hasSingleSelection = this._activeView.hasSingleSelection;
|
||||||
if (this._activeView.selectedURINode && hasSingleSelection)
|
if (this._activeView.selectedURINode && hasSingleSelection)
|
||||||
|
@ -685,24 +896,39 @@ var PlacesController = {
|
||||||
var selectedNode = this._activeView.selectedNode;
|
var selectedNode = this._activeView.selectedNode;
|
||||||
if (this.nodeIsFolder(selectedNode))
|
if (this.nodeIsFolder(selectedNode))
|
||||||
metadata["folder"] = true;
|
metadata["folder"] = true;
|
||||||
|
if (this.nodeIsQuery(selectedNode))
|
||||||
|
metadata["query"] = true;
|
||||||
if (this.nodeIsRemoteContainer(selectedNode))
|
if (this.nodeIsRemoteContainer(selectedNode))
|
||||||
metadata["remotecontainer"] = true;
|
metadata["remotecontainer"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var foundNonLeaf = false;
|
var foundNonLeaf = false;
|
||||||
var nodes = this._activeView.getSelectionNodes();
|
var nodes = this._activeView.getSelectionNodes();
|
||||||
|
if (nodes.length)
|
||||||
|
var lastParent = nodes[0].parent, lastType = nodes[0].type;
|
||||||
for (var i = 0; i < nodes.length; ++i) {
|
for (var i = 0; i < nodes.length; ++i) {
|
||||||
var node = nodes[i];
|
var node = nodes[i];
|
||||||
if (node.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_URI)
|
if (!this.nodeIsURI(node))
|
||||||
foundNonLeaf = true;
|
foundNonLeaf = true;
|
||||||
if (!node.readonly &&
|
if (!this.nodeIsReadOnly(node) && !this.nodeIsReadOnly(node.parent))
|
||||||
node.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_REMOTE_CONTAINER)
|
|
||||||
metadata["mutable"] = true;
|
metadata["mutable"] = true;
|
||||||
|
|
||||||
|
var uri = this._uri(node.uri);
|
||||||
|
if (this.nodeIsFolder(node))
|
||||||
|
uri = this.bookmarks.getFolderURI(asFolder(node).folderId);
|
||||||
|
var names = this.annotations.getPageAnnotationNames(uri, { });
|
||||||
|
for (var j = 0; j < names.length; ++j)
|
||||||
|
metadata[names[i]] = true;
|
||||||
|
|
||||||
|
if (nodes[i].parent != lastParent || nodes[i].type != lastType)
|
||||||
|
metadata["mixed"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._activeView.selType != "single")
|
if (this._activeView.selType != "single")
|
||||||
metadata["multiselect"] = true;
|
metadata["multiselect"] = true;
|
||||||
if (!foundNonLeaf && nodes.length > 1)
|
if (!foundNonLeaf && nodes.length > 1)
|
||||||
metadata["links"] = true;
|
metadata["links"] = true;
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -742,7 +968,7 @@ var PlacesController = {
|
||||||
var lastVisible = null;
|
var lastVisible = null;
|
||||||
for (var i = 0; i < popup.childNodes.length; ++i) {
|
for (var i = 0; i < popup.childNodes.length; ++i) {
|
||||||
var item = popup.childNodes[i];
|
var item = popup.childNodes[i];
|
||||||
var rules = item.getAttribute("selection")
|
var rules = item.getAttribute("selection");
|
||||||
item.hidden = !this._shouldShowMenuItem(metadata, rules.split("|"));
|
item.hidden = !this._shouldShowMenuItem(metadata, rules.split("|"));
|
||||||
if (!item.hidden)
|
if (!item.hidden)
|
||||||
lastVisible = item;
|
lastVisible = item;
|
||||||
|
@ -914,6 +1140,7 @@ var PlacesController = {
|
||||||
* Loads the selected URL in the current window, replacing the Places page.
|
* Loads the selected URL in the current window, replacing the Places page.
|
||||||
*/
|
*/
|
||||||
openLinkInCurrentWindow: function PC_openLinkInCurrentWindow() {
|
openLinkInCurrentWindow: function PC_openLinkInCurrentWindow() {
|
||||||
|
LOG("openLinkInCurrentWindow");
|
||||||
var node = this._activeView.selectedURINode;
|
var node = this._activeView.selectedURINode;
|
||||||
if (node)
|
if (node)
|
||||||
this.browserWindow.loadURI(node.uri, null, null);
|
this.browserWindow.loadURI(node.uri, null, null);
|
||||||
|
@ -1114,8 +1341,7 @@ var PlacesController = {
|
||||||
// Delete the selected rows. Do this by walking the selection backward, so
|
// 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.
|
// that when undo is performed they are re-inserted in the correct order.
|
||||||
var type = this._activeView.getResult().root.type;
|
var type = this._activeView.getResult().root.type;
|
||||||
LOG("TYPE: " + type);
|
if (this.nodeIsFolder(this._activeView.getResult().root))
|
||||||
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER)
|
|
||||||
this._removeRowsFromBookmarks(txnName);
|
this._removeRowsFromBookmarks(txnName);
|
||||||
else
|
else
|
||||||
this._removeRowsFromHistory();
|
this._removeRowsFromHistory();
|
||||||
|
@ -1274,7 +1500,7 @@ var PlacesController = {
|
||||||
var node = kids.getChild(i);
|
var node = kids.getChild(i);
|
||||||
if (self.nodeIsFolder(node))
|
if (self.nodeIsFolder(node))
|
||||||
createTransactions(node.folderId, folderId, i);
|
createTransactions(node.folderId, folderId, i);
|
||||||
else if (this.nodeIsURI(node)) {
|
else if (self.nodeIsURI(node)) {
|
||||||
var uri = self._uri(node.uri);
|
var uri = self._uri(node.uri);
|
||||||
transactions.push(self._getItemCopyTransaction(uri, container,
|
transactions.push(self._getItemCopyTransaction(uri, container,
|
||||||
index));
|
index));
|
||||||
|
|
|
@ -699,21 +699,28 @@ var ViewMenu = {
|
||||||
* the type of the menuitem, e.g. "radio" or "checkbox".
|
* the type of the menuitem, e.g. "radio" or "checkbox".
|
||||||
* Can be null (no-type).
|
* Can be null (no-type).
|
||||||
* Checkboxes are checked if the column is visible.
|
* Checkboxes are checked if the column is visible.
|
||||||
|
* @param labelFormat
|
||||||
|
* A format string to be applied to item labels. If null, no format
|
||||||
|
* is used.
|
||||||
*/
|
*/
|
||||||
fillWithColumns: function VM_fillWithColumns(event, startID, endID, type) {
|
fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, labelFormat) {
|
||||||
var popup = event.target;
|
var popup = event.target;
|
||||||
var pivot = this._clean(popup, startID, endID);
|
var pivot = this._clean(popup, startID, endID);
|
||||||
|
|
||||||
// If no column is "sort-active", the "Unsorted" item needs to be checked,
|
// 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.
|
// so track whether or not we find a column that is sort-active.
|
||||||
var isSorted = false;
|
var isSorted = false;
|
||||||
var content = document.getElementById("placeContent");
|
var content = document.getElementById("placeContent");
|
||||||
|
var strings = document.getElementById("placeBundle");
|
||||||
var columns = content.columns;
|
var columns = content.columns;
|
||||||
for (var i = 0; i < columns.count; ++i) {
|
for (var i = 0; i < columns.count; ++i) {
|
||||||
var column = columns.getColumnAt(i).element;
|
var column = columns.getColumnAt(i).element;
|
||||||
var menuitem = document.createElementNS(XUL_NS, "menuitem");
|
var menuitem = document.createElementNS(XUL_NS, "menuitem");
|
||||||
menuitem.id = "menucol_" + column.id;
|
menuitem.id = "menucol_" + column.id;
|
||||||
menuitem.setAttribute("label", column.getAttribute("label"));
|
var label = column.getAttribute("label");
|
||||||
|
if (labelFormat)
|
||||||
|
label = strings.getFormattedString(labelFormat, [label]);
|
||||||
|
menuitem.setAttribute("label", label);
|
||||||
if (type == "radio") {
|
if (type == "radio") {
|
||||||
menuitem.setAttribute("type", "radio");
|
menuitem.setAttribute("type", "radio");
|
||||||
menuitem.setAttribute("name", "columns");
|
menuitem.setAttribute("name", "columns");
|
||||||
|
@ -744,7 +751,7 @@ var ViewMenu = {
|
||||||
* Set up the content of the view menu.
|
* Set up the content of the view menu.
|
||||||
*/
|
*/
|
||||||
populate: function VM_populate(event) {
|
populate: function VM_populate(event) {
|
||||||
this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio");
|
this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "sortByPrefix");
|
||||||
|
|
||||||
var sortColumn = this._getSortColumn();
|
var sortColumn = this._getSortColumn();
|
||||||
var viewSortAscending = document.getElementById("viewSortAscending");
|
var viewSortAscending = document.getElementById("viewSortAscending");
|
||||||
|
|
|
@ -31,6 +31,14 @@
|
||||||
<stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
|
<stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
|
||||||
</stringbundleset>
|
</stringbundleset>
|
||||||
|
|
||||||
|
<commandset id="TMCommandSet"
|
||||||
|
commandupdater="true" events="*"
|
||||||
|
oncommandupdate="PlacesController.updateTMCommands()">
|
||||||
|
<command id="cmd_undo" label="&cmd.edit_undo.label;" accesskey="&cmd.edit_undo.accesskey;"
|
||||||
|
oncommand="PlacesController.doCommand(this.id);" disabled="true"/>
|
||||||
|
<command id="cmd_redo" label="&cmd.edit_redo.label;" accesskey="&cmd.edit_redo.accesskey;"
|
||||||
|
oncommand="PlacesController.doCommand(this.id);" disabled="true"/>
|
||||||
|
</commandset>
|
||||||
#include commands.inc
|
#include commands.inc
|
||||||
|
|
||||||
<keyset id="placesOrganizerKeyset">
|
<keyset id="placesOrganizerKeyset">
|
||||||
|
@ -46,8 +54,11 @@
|
||||||
<key id="placesKey_edit:paste" command="placesCmd_edit:paste" key="&cmd.edit_paste.key;" modifiers="accel"/>
|
<key id="placesKey_edit:paste" command="placesCmd_edit:paste" key="&cmd.edit_paste.key;" modifiers="accel"/>
|
||||||
<key id="placesKey_edit:delete" command="placesCmd_edit:delete" keycode="VK_DELETE"/>
|
<key id="placesKey_edit:delete" command="placesCmd_edit:delete" keycode="VK_DELETE"/>
|
||||||
<key id="placesKey_open" command="placesCmd_open" keycode="VK_ENTER"/>
|
<key id="placesKey_open" command="placesCmd_open" keycode="VK_ENTER"/>
|
||||||
|
<key id="placesKey_open" command="placesCmd_open" keycode="VK_RETURN"/>
|
||||||
<key id="placesKey_open:window" command="placesCmd_open:window" keycode="VK_ENTER" modifiers="shift"/>
|
<key id="placesKey_open:window" command="placesCmd_open:window" keycode="VK_ENTER" modifiers="shift"/>
|
||||||
|
<key id="placesKey_open:window" command="placesCmd_open:window" keycode="VK_RETURN" modifiers="shift"/>
|
||||||
<key id="placesKey_open:tab" command="placesCmd_open:tab" keycode="VK_ENTER" modifiers="accel"/>
|
<key id="placesKey_open:tab" command="placesCmd_open:tab" keycode="VK_ENTER" modifiers="accel"/>
|
||||||
|
<key id="placesKey_open:tab" command="placesCmd_open:tab" keycode="VK_RETURN" modifiers="accel"/>
|
||||||
<key id="placesKey_show:info" command="placesCmd_show:info" key="&cmd.show_info.key;" modifiers="accel"/>
|
<key id="placesKey_show:info" command="placesCmd_show:info" key="&cmd.show_info.key;" modifiers="accel"/>
|
||||||
<key id="placesKey_rename" command="placesCmd_rename" keycode="VK_F2"/>
|
<key id="placesKey_rename" command="placesCmd_rename" keycode="VK_F2"/>
|
||||||
<key id="placesKey_reload" command="placesCmd_reload" key="&cmd.reload.key;" modifiers="accel"/>
|
<key id="placesKey_reload" command="placesCmd_reload" key="&cmd.reload.key;" modifiers="accel"/>
|
||||||
|
@ -79,10 +90,8 @@
|
||||||
</menu>
|
</menu>
|
||||||
<menu id="editMenu" label="&edit.label;" accesskey="&edit.accesskey;">
|
<menu id="editMenu" label="&edit.label;" accesskey="&edit.accesskey;">
|
||||||
<menupopup>
|
<menupopup>
|
||||||
<menuitem id="editUndo" command="cmd_undo"
|
<menuitem id="editUndo" command="cmd_undo"/>
|
||||||
label="&edit.undo.label;" accesskey="&edit.undo.accesskey;"/>
|
<menuitem id="editRedo" command="cmd_redo"/>
|
||||||
<menuitem id="editRedo" command="cmd_redo"
|
|
||||||
label="&edit.redo.label;" accesskey="&edit.redo.label;"/>
|
|
||||||
<menuseparator/>
|
<menuseparator/>
|
||||||
<menuitem id="editCut" command="placesCmd_edit:cut"/>
|
<menuitem id="editCut" command="placesCmd_edit:cut"/>
|
||||||
<menuitem id="editCopy" command="placesCmd_edit:copy"/>
|
<menuitem id="editCopy" command="placesCmd_edit:copy"/>
|
||||||
|
@ -91,6 +100,8 @@
|
||||||
<menuseparator/>
|
<menuseparator/>
|
||||||
<menuitem id="editSelectAll" command="placesCmd_select:all"/>
|
<menuitem id="editSelectAll" command="placesCmd_select:all"/>
|
||||||
<menuseparator/>
|
<menuseparator/>
|
||||||
|
<menuitem id="editSortByName" command="placesCmd_sortby:name"/>
|
||||||
|
<menuseparator/>
|
||||||
<menuitem id="editFind" command="placesCmd_find"/>
|
<menuitem id="editFind" command="placesCmd_find"/>
|
||||||
#ifdef XP_MACOSX
|
#ifdef XP_MACOSX
|
||||||
<menuseparator/>
|
<menuseparator/>
|
||||||
|
@ -108,7 +119,7 @@
|
||||||
label="&view.toolbar.label;" accesskey="&view.toolbar.accesskey;"/>
|
label="&view.toolbar.label;" accesskey="&view.toolbar.accesskey;"/>
|
||||||
<menu id="viewColumns"
|
<menu id="viewColumns"
|
||||||
label="&view.columns.label;" accesskey="&view.columns.accesskey;">
|
label="&view.columns.label;" accesskey="&view.columns.accesskey;">
|
||||||
<menupopup onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox');"
|
<menupopup onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
|
||||||
oncommand="ViewMenu.showHideColumn(event.target); event.preventBubble();"/>
|
oncommand="ViewMenu.showHideColumn(event.target); event.preventBubble();"/>
|
||||||
</menu>
|
</menu>
|
||||||
<menuseparator id="groupingSeparator" observes="placesBC_grouping:separator"/>
|
<menuseparator id="groupingSeparator" observes="placesBC_grouping:separator"/>
|
||||||
|
|
|
@ -14,14 +14,6 @@
|
||||||
"Edit">
|
"Edit">
|
||||||
<!ENTITY edit.accesskey
|
<!ENTITY edit.accesskey
|
||||||
"E">
|
"E">
|
||||||
<!ENTITY edit.undo.label
|
|
||||||
"Undo">
|
|
||||||
<!ENTITY edit.undo.accesskey
|
|
||||||
"U">
|
|
||||||
<!ENTITY edit.redo.label
|
|
||||||
"Redo">
|
|
||||||
<!ENTITY edit.redo.accesskey
|
|
||||||
"R">
|
|
||||||
<!ENTITY view.label
|
<!ENTITY view.label
|
||||||
"View">
|
"View">
|
||||||
<!ENTITY view.accesskey
|
<!ENTITY view.accesskey
|
||||||
|
@ -99,6 +91,14 @@
|
||||||
"Delete">
|
"Delete">
|
||||||
<!ENTITY cmd.edit_delete.accesskey
|
<!ENTITY cmd.edit_delete.accesskey
|
||||||
"D">
|
"D">
|
||||||
|
<!ENTITY cmd.edit_undo.label
|
||||||
|
"Undo">
|
||||||
|
<!ENTITY cmd.edit_undo.accesskey
|
||||||
|
"U">
|
||||||
|
<!ENTITY cmd.edit_redo.label
|
||||||
|
"Redo">
|
||||||
|
<!ENTITY cmd.edit_redo.accesskey
|
||||||
|
"R">
|
||||||
<!ENTITY cmd.open.label
|
<!ENTITY cmd.open.label
|
||||||
"Open">
|
"Open">
|
||||||
<!ENTITY cmd.open.accesskey
|
<!ENTITY cmd.open.accesskey
|
||||||
|
|
|
@ -50,4 +50,13 @@ defaultGroupOffAccesskey=N
|
||||||
livemarkGroupOnLabel=Group By Feed
|
livemarkGroupOnLabel=Group By Feed
|
||||||
livemarkGroupOnAccesskey=F
|
livemarkGroupOnAccesskey=F
|
||||||
livemarkGroupOffLabel=No Grouping
|
livemarkGroupOffLabel=No Grouping
|
||||||
livemarkGroupOffAccesskey=N
|
livemarkGroupOffAccesskey=N
|
||||||
|
|
||||||
|
livemarkReload=Reload
|
||||||
|
livemarkReloadAll=Reload All Live Bookmarks
|
||||||
|
livemarkReloadOne=Reload %S
|
||||||
|
|
||||||
|
sortByName=Sort '%S' by Name
|
||||||
|
sortByNameGeneric=Sort by Name
|
||||||
|
sortByPrefix=Sort by %S
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче