зеркало из https://github.com/mozilla/pjs.git
Add a tag selector to Composer's status bar. It allows to view the whole hierarchy of the current selection, from the deepest element container up to the body.
Each element in the bar is selectable and a context menu allows to select the element, remove the element preserving its contents, changing the element into another one (regardless of DTD), and open the advanced properties dialog for the element. When "Change tag" is requested, a textbox appears in the status bar. Changes are discarded if Escape key is pressed, and performed if CR/Enter key is pressed. b=173319, r=brade, sr=peterv, a=rjesup@wgate.com
This commit is contained in:
Родитель
ca2aece886
Коммит
7ec6d23893
|
@ -3043,12 +3043,6 @@ nsresult nsEditorShell::EndPageLoad(nsIDOMWindow *aDOMWindow,
|
|||
return NS_ERROR_ABORT;
|
||||
}
|
||||
|
||||
if (!mMailCompose) {
|
||||
nsAutoString doneText;
|
||||
GetBundleString(NS_LITERAL_STRING("LoadingDone"), doneText);
|
||||
SetChromeAttribute(mDocShell, "statusText", NS_LITERAL_STRING("label"), doneText);
|
||||
}
|
||||
|
||||
//
|
||||
// By this time, we know that the page did not contain any frames
|
||||
// (since mCloseWindowWhenLoaded was PR_FALSE)... So, make an
|
||||
|
|
|
@ -206,6 +206,7 @@ function SetupComposerWindowCommands()
|
|||
commandManager.registerCommand("cmd_PreviewMode", nsPreviewModeCommand);
|
||||
commandManager.registerCommand("cmd_FinishHTMLSource", nsFinishHTMLSource);
|
||||
commandManager.registerCommand("cmd_CancelHTMLSource", nsCancelHTMLSource);
|
||||
commandManager.registerCommand("cmd_updateStructToolbar", nsUpdateStructToolbarCommand);
|
||||
}
|
||||
|
||||
windowControllers.insertControllerAt(0, editorController);
|
||||
|
@ -407,6 +408,7 @@ function goUpdateCommandState(cmdController, command)
|
|||
case "cmd_backgroundColor":
|
||||
case "cmd_fontColor":
|
||||
case "cmd_fontFace":
|
||||
case "cmd_updateStructToolbar":
|
||||
pokeMultiStateUI(command, params);
|
||||
break;
|
||||
|
||||
|
@ -484,6 +486,7 @@ function goDoCommandParams(command, params)
|
|||
{
|
||||
controller.doCommand(command);
|
||||
}
|
||||
ResetStructToolbar();
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
|
@ -529,6 +532,8 @@ function doStyleUICommand(cmdStr)
|
|||
goDoCommandParams(cmdStr, cmdParams);
|
||||
if (cmdParams)
|
||||
pokeStyleUI(cmdStr, cmdParams.getBooleanValue("state_all"));
|
||||
|
||||
ResetStructToolbar();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
|
@ -571,6 +576,8 @@ function doStatefulCommand(commandID, newState)
|
|||
goDoCommandParams(commandID, cmdParams);
|
||||
|
||||
pokeMultiStateUI(commandID, cmdParams);
|
||||
|
||||
ResetStructToolbar();
|
||||
} catch(e) { dump("error thrown in doStatefulCommand: "+e+"\n"); }
|
||||
}
|
||||
|
||||
|
@ -661,6 +668,21 @@ var nsOpenCommand =
|
|||
}
|
||||
};
|
||||
|
||||
// STRUCTURE TOOLBAR
|
||||
//
|
||||
var nsUpdateStructToolbarCommand =
|
||||
{
|
||||
isCommandEnabled: function(aCommand, dummy)
|
||||
{
|
||||
UpdateStructToolbar();
|
||||
return true;
|
||||
},
|
||||
|
||||
getCommandStateParams: function(aCommand, aParams, aRefCon) {},
|
||||
doCommandParams: function(aCommand, aParams, aRefCon) {},
|
||||
doCommand: function(aCommand) {}
|
||||
}
|
||||
|
||||
// ******* File output commands and utilities ******** //
|
||||
//-----------------------------------------------------------------------------------
|
||||
var nsSaveCommand =
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/x-javascript" src="chrome://editor/content/EditorContextMenu.js"/>
|
||||
<script type="application/x-javascript" src="chrome://editor/content/StructBarContextMenu.js"/>
|
||||
|
||||
<popupset id="editorContentContextSet">
|
||||
<popup id="editorContentContext"
|
||||
|
@ -109,6 +110,24 @@
|
|||
<menuitem id="splitTableCell_cm" label="&tableSplitCell.label;" accesskey="&tablesplitcell.accesskey;" observes="cmd_SplitTableCell"/>
|
||||
<menuitem id="tableOrCellColor_cm" label="&tableOrCellColor.label;" accesskey="&tableOrCellColor.accesskey;" observes="cmd_TableOrCellColor"/>
|
||||
</popup>
|
||||
|
||||
<popup id="structToolbarContext">
|
||||
<menuitem id="structSelect" label="&structSelect.label;"
|
||||
accesskey="&structSelect.accesskey;"
|
||||
oncommand="StructSelectTag()"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="structRemoveTag" label="&structRemoveTag.label;"
|
||||
accesskey="&structRemoveTag.accesskey;"
|
||||
oncommand="StructRemoveTag()"/>
|
||||
<menuitem id="structChangeTag" label="&structChangeTag.label;"
|
||||
accesskey="&structChangeTag.accesskey;"
|
||||
oncommand="StructChangeTag()"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="advancedPropsTag" label="&advancedPropertiesCmd.label;"
|
||||
accesskey="&advancedproperties.accesskey;"
|
||||
oncommand="OpenAdvancedProperties()"/>
|
||||
</popup>
|
||||
|
||||
</popupset>
|
||||
|
||||
</overlay>
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Netscape Communications Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2002
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Daniel Glazman (glazman@netscape.com), original author
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
var gContextMenuNode;
|
||||
var gContextMenuFiringDocumentElement;
|
||||
|
||||
function InitStructBarContextMenu(button, docElement)
|
||||
{
|
||||
gContextMenuFiringDocumentElement = docElement;
|
||||
gContextMenuNode = button;
|
||||
|
||||
var tag = docElement.nodeName.toLowerCase();
|
||||
|
||||
var structRemoveTag = document.getElementById("structRemoveTag");
|
||||
var enableRemove;
|
||||
|
||||
switch (tag) {
|
||||
case "body":
|
||||
case "tbody":
|
||||
case "thead":
|
||||
case "tfoot":
|
||||
case "col":
|
||||
case "colgroup":
|
||||
case "tr":
|
||||
case "th":
|
||||
case "td":
|
||||
case "caption":
|
||||
enableRemove = false;
|
||||
break;
|
||||
default:
|
||||
enableRemove = true;
|
||||
break;
|
||||
}
|
||||
SetElementEnabled(structRemoveTag, enableRemove);
|
||||
|
||||
var structChangeTag = document.getElementById("structChangeTag");
|
||||
SetElementEnabled(structChangeTag, (tag != "body"));
|
||||
}
|
||||
|
||||
function TableCellFilter(node)
|
||||
{
|
||||
switch (node.nodeName.toLowerCase())
|
||||
{
|
||||
case "td":
|
||||
case "th":
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
break;
|
||||
default:
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
break;
|
||||
}
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
}
|
||||
|
||||
function StructRemoveTag()
|
||||
{
|
||||
var editor = GetCurrentEditor();
|
||||
if (!editor) return;
|
||||
|
||||
var element = gContextMenuFiringDocumentElement;
|
||||
var offset = 0;
|
||||
var childNodes = element.parentNode.childNodes;
|
||||
|
||||
while (childNodes.item(offset) != element) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
editor.beginTransaction();
|
||||
|
||||
try {
|
||||
|
||||
var tag = element.nodeName.toLowerCase();
|
||||
if (tag != "table") {
|
||||
MoveChildNodesAfterElement(editor, element, element, offset);
|
||||
}
|
||||
else {
|
||||
|
||||
var nodeIterator = document.createTreeWalker(element,
|
||||
NodeFilter.SHOW_ELEMENT,
|
||||
TableCellFilter,
|
||||
true);
|
||||
var node = nodeIterator.nextNode();
|
||||
while (node) {
|
||||
MoveChildNodesAfterElement(editor, node, element, offset);
|
||||
node = nodeIterator.nextNode();
|
||||
}
|
||||
|
||||
}
|
||||
editor.deleteNode(element);
|
||||
}
|
||||
catch (e) {};
|
||||
|
||||
editor.endTransaction();
|
||||
}
|
||||
|
||||
function MoveChildNodesAfterElement(editor, element, targetElement, targetOffset)
|
||||
{
|
||||
var childNodes = element.childNodes;
|
||||
var childNodesLength = childNodes.length;
|
||||
var i;
|
||||
for (i = 0; i < childNodesLength; i++) {
|
||||
var clone = childNodes.item(i).cloneNode(true);
|
||||
editor.insertNode(clone, targetElement.parentNode, targetOffset + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function StructChangeTag()
|
||||
{
|
||||
var textbox = document.createElementNS(XUL_NS, "textbox");
|
||||
textbox.setAttribute("value", gContextMenuNode.getAttribute("value"));
|
||||
textbox.setAttribute("size", 5);
|
||||
|
||||
gContextMenuNode.parentNode.insertBefore(textbox, gContextMenuNode);
|
||||
|
||||
textbox.addEventListener("keypress", OnKeyPress, false);
|
||||
|
||||
textbox.setAttribute("style", "font-size: smaller");
|
||||
gContextMenuNode.parentNode.removeChild(gContextMenuNode);
|
||||
|
||||
textbox.select();
|
||||
}
|
||||
|
||||
function StructSelectTag()
|
||||
{
|
||||
SelectFocusNodeAncestor(gContextMenuFiringDocumentElement);
|
||||
}
|
||||
|
||||
function OpenAdvancedProperties()
|
||||
{
|
||||
doAdvancedProperties(gContextMenuFiringDocumentElement);
|
||||
}
|
||||
|
||||
function OnKeyPress(event)
|
||||
{
|
||||
var editor = GetCurrentEditor();
|
||||
|
||||
var keyCode = event.keyCode;
|
||||
if (keyCode == 13) {
|
||||
var newTag = event.target.value;
|
||||
|
||||
var element = gContextMenuFiringDocumentElement;
|
||||
|
||||
var offset = 0;
|
||||
var childNodes = element.parentNode.childNodes;
|
||||
while (childNodes.item(offset) != element) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
editor.beginTransaction();
|
||||
|
||||
try {
|
||||
var newElt = editor.document.createElement(newTag);
|
||||
if (newElt) {
|
||||
childNodes = element.childNodes;
|
||||
var childNodesLength = childNodes.length;
|
||||
var i;
|
||||
for (i = 0; i < childNodesLength; i++) {
|
||||
var clone = childNodes.item(i).cloneNode(true);
|
||||
newElt.appendChild(clone);
|
||||
}
|
||||
editor.insertNode(newElt, element.parentNode, offset+1);
|
||||
editor.deleteNode(element);
|
||||
editor.selectElement(newElt);
|
||||
|
||||
window._content.focus();
|
||||
|
||||
ResetStructToolbar();
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
editor.endTransaction();
|
||||
|
||||
}
|
||||
else if (keyCode == 27) {
|
||||
// if the user hits Escape, we discard the changes
|
||||
ResetStructToolbar();
|
||||
}
|
||||
}
|
|
@ -81,6 +81,9 @@ var gDefaultBackgroundColor = "";
|
|||
var gCSSPrefListener;
|
||||
var gPrefs;
|
||||
|
||||
var gLastFocusNode = null;
|
||||
var gLastFocusNodeWasSelected = false;
|
||||
|
||||
// These must be kept in synch with the XUL <options> lists
|
||||
var gFontSizeNames = ["xx-small","x-small","small","medium","large","x-large","xx-large"];
|
||||
|
||||
|
@ -1683,6 +1686,7 @@ function SetDisplayMode(mode)
|
|||
var previousMode = gEditorDisplayMode;
|
||||
gEditorDisplayMode = mode;
|
||||
|
||||
ResetStructToolbar();
|
||||
if (mode == kDisplayModeSource)
|
||||
{
|
||||
// Switch to the sourceWindow (second in the deck)
|
||||
|
@ -2967,3 +2971,169 @@ function SwitchInsertCharToAnotherEditorOrClose()
|
|||
window.InsertCharWindow.close();
|
||||
}
|
||||
}
|
||||
|
||||
function ResetStructToolbar()
|
||||
{
|
||||
gLastFocusNode = null;
|
||||
UpdateStructToolbar();
|
||||
}
|
||||
|
||||
function newCommandListener(element)
|
||||
{
|
||||
return function() { return SelectFocusNodeAncestor(element); };
|
||||
}
|
||||
|
||||
function newContextmenuListener(button, element)
|
||||
{
|
||||
return function() { return InitStructBarContextMenu(button, element); };
|
||||
}
|
||||
|
||||
function UpdateStructToolbar()
|
||||
{
|
||||
var editor = GetCurrentEditor();
|
||||
if (!editor) return;
|
||||
|
||||
var mixed = GetSelectionContainer();
|
||||
if (!mixed) return;
|
||||
var element = mixed.node;
|
||||
var oneElementSelected = mixed.oneElementSelected;
|
||||
|
||||
if (!element) return;
|
||||
|
||||
if (element == gLastFocusNode &&
|
||||
oneElementSelected == gLastFocusNodeWasSelected)
|
||||
return;
|
||||
|
||||
gLastFocusNode = element;
|
||||
gLastFocusNodeWasSelected = mixed.oneElementSelected;
|
||||
|
||||
var toolbar = document.getElementById("statusText");
|
||||
if (!toolbar) return;
|
||||
var childNodes = toolbar.childNodes;
|
||||
var childNodesLength = childNodes.length;
|
||||
var i;
|
||||
for (i = childNodesLength-1; i >= 0; i--) {
|
||||
toolbar.removeChild(childNodes.item(i));
|
||||
}
|
||||
|
||||
toolbar.removeAttribute("label");
|
||||
|
||||
if ( IsInHTMLSourceMode() ) {
|
||||
// we have destroyed the contents of the status bar and are
|
||||
// about to recreate it ; but we don't want to do that in
|
||||
// Source mode
|
||||
return;
|
||||
}
|
||||
|
||||
var tag, button;
|
||||
var bodyElement = GetBodyElement();
|
||||
var isFocusNode = true;
|
||||
var tmp;
|
||||
do {
|
||||
tag = element.nodeName.toLowerCase();
|
||||
|
||||
button = document.createElementNS(XUL_NS, "toolbarbutton");
|
||||
button.setAttribute("label", "<" + tag + ">");
|
||||
button.setAttribute("value", tag);
|
||||
button.setAttribute("context", "structToolbarContext");
|
||||
|
||||
toolbar.insertBefore(button, toolbar.firstChild);
|
||||
|
||||
button.addEventListener("command", newCommandListener(element), false);
|
||||
|
||||
button.addEventListener("contextmenu", newContextmenuListener(button, element), false);
|
||||
|
||||
if (isFocusNode && oneElementSelected) {
|
||||
button.setAttribute("style", "font-weight: bold");
|
||||
isFocusNode = false;
|
||||
}
|
||||
|
||||
tmp = element;
|
||||
element = element.parentNode;
|
||||
|
||||
} while (tmp != bodyElement);
|
||||
}
|
||||
|
||||
function SelectFocusNodeAncestor(element)
|
||||
{
|
||||
var editor = GetCurrentEditor();
|
||||
if (editor) {
|
||||
if (element == GetBodyElement())
|
||||
editor.selectAll();
|
||||
else
|
||||
editor.selectElement(element);
|
||||
}
|
||||
ResetStructToolbar();
|
||||
}
|
||||
|
||||
function GetSelectionContainer()
|
||||
{
|
||||
var editor = GetCurrentEditor();
|
||||
if (!editor) return null;
|
||||
|
||||
var selection = editor.selection;
|
||||
if (!selection) return null;
|
||||
|
||||
var result = { oneElementSelected:false };
|
||||
|
||||
if (selection.isCollapsed) {
|
||||
result.node = selection.focusNode;
|
||||
}
|
||||
else {
|
||||
var rangeCount = selection.rangeCount;
|
||||
if (rangeCount == 1) {
|
||||
result.node = editor.getSelectedElement("");
|
||||
var range = selection.getRangeAt(0);
|
||||
|
||||
// check for a weird case : when we select a piece of text inside
|
||||
// a text node and apply an inline style to it, the selection starts
|
||||
// at the end of the text node preceding the style and ends after the
|
||||
// last char of the style. Assume the style element is selected for
|
||||
// user's pleasure
|
||||
if (!result.node &&
|
||||
range.startContainer.nodeType == Node.TEXT_NODE &&
|
||||
range.startOffset == range.startContainer.length &&
|
||||
range.endContainer.nodeType == Node.TEXT_NODE &&
|
||||
range.endOffset == range.endContainer.length &&
|
||||
range.endContainer.nextSibling == null &&
|
||||
range.startContainer.nextSibling == range.endContainer.parentNode)
|
||||
result.node = range.endContainer.parentNode;
|
||||
|
||||
if (!result.node) {
|
||||
// let's rely on the common ancestor of the selection
|
||||
result.node = range.commonAncestorContainer;
|
||||
}
|
||||
else {
|
||||
result.oneElementSelected = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// assume table cells !
|
||||
var i, container = null;
|
||||
for (i = 0; i < rangeCount; i++) {
|
||||
range = selection.getRangeAt(i);
|
||||
if (!container) {
|
||||
container = range.startContainer;
|
||||
}
|
||||
else if (container != range.startContainer) {
|
||||
// all table cells don't belong to same row so let's
|
||||
// select the parent of all rows
|
||||
result.node = container.parentNode;
|
||||
break;
|
||||
}
|
||||
result.node = container;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we have an element here
|
||||
while (result.node.nodeType != Node.ELEMENT_NODE)
|
||||
result.node = result.node.parentNode;
|
||||
|
||||
// and make sure the element is not a special editor node like
|
||||
// the <br> we insert in blank lines
|
||||
while (result.node.hasAttribute("_moz_editor_bogus_node"))
|
||||
result.node = result.node.parentNode;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<script type="application/x-javascript" src="chrome://editor/content/editor.js"/>
|
||||
<script type="application/x-javascript" src="chrome://editor/content/EditorCommandsDebug.js"/>
|
||||
<script type="application/x-javascript" src="chrome://editor/content/EditorContextMenu.js"/>
|
||||
<script type="application/x-javascript" src="chrome://editor/content/StructBarContextMenu.js"/>
|
||||
<script type="application/x-javascript" src="chrome://editor/content/editorApplicationOverlay.js"/>
|
||||
<script type="application/x-javascript" src="chrome://editor/content/publishprefs.js"/>
|
||||
<script type="application/x-javascript" src="chrome://communicator/content/contentAreaDD.js"/>
|
||||
|
@ -268,7 +269,10 @@
|
|||
<!-- Some of this is from globarOverlay.xul -->
|
||||
<statusbar class="chromeclass-status" id="status-bar">
|
||||
<statusbarpanel id="component-bar"/>
|
||||
<statusbarpanel id="statusText" label="&statusText.label;" flex="1"/>
|
||||
<statusbarpanel id="statusText" flex="1" align="left" style="min-height: 20px"
|
||||
observes="cmd_structToolBar">
|
||||
<label value=""/>
|
||||
</statusbarpanel>
|
||||
<statusbarpanel class="statusbarpanel-iconic" id="offline-status"/>
|
||||
</statusbar>
|
||||
|
||||
|
|
|
@ -233,6 +233,7 @@
|
|||
<command id="cmd_removeStyles" oncommand="goDoCommand('cmd_removeStyles')"/>
|
||||
<command id="cmd_removeLinks" oncommand="goDoCommand('cmd_removeLinks')"/>
|
||||
<command id="cmd_removeNamedAnchors" oncommand="goDoCommand('cmd_removeNamedAnchors')"/>
|
||||
<command id="cmd_updateStructToolbar" oncommand="goDoCommand('cmd_updateStructToolbar')"/>
|
||||
</commandset>
|
||||
|
||||
<!-- commands updated only when the menu gets created -->
|
||||
|
|
|
@ -585,5 +585,10 @@
|
|||
<!ENTITY smiley7Cmd.label "Undecided">
|
||||
<!ENTITY SmileButton.tooltip "Choose smiley face">
|
||||
|
||||
|
||||
|
||||
<!-- Structure Toolbar Context Menu items -->
|
||||
<!ENTITY structSelect.label "Select">
|
||||
<!ENTITY structSelect.accesskey "s">
|
||||
<!ENTITY structRemoveTag.label "Remove tag">
|
||||
<!ENTITY structRemoveTag.accesskey "r">
|
||||
<!ENTITY structChangeTag.label "Change tag">
|
||||
<!ENTITY structChangeTag.accesskey "c">
|
||||
|
|
|
@ -29,6 +29,7 @@ comm.jar:
|
|||
content/editor/editorMailOverlay.xul (composer/content/editorMailOverlay.xul)
|
||||
content/editor/editorTasksOverlay.xul (composer/content/editorTasksOverlay.xul)
|
||||
content/editor/editorApplicationOverlay.js (composer/content/editorApplicationOverlay.js)
|
||||
content/editor/StructBarContextMenu.js (composer/content/StructBarContextMenu.js)
|
||||
content/editor/images/tag-anchor.gif (composer/content/images/tag-anchor.gif)
|
||||
content/editor/images/tag-abr.gif (composer/content/images/tag-abr.gif)
|
||||
content/editor/images/tag-acr.gif (composer/content/images/tag-acr.gif)
|
||||
|
|
Загрузка…
Ссылка в новой задаче