mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
2c92600954
When a selection in an inline element is to be deleted, it is first expanded to the parent block in order not to leave any empty inline element. This patch ensures that the selection cannot be expanded outside of the contentEditable element (active editing host).
9269 lines
315 KiB
C++
9269 lines
315 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 sw=2 et tw=79: */
|
|
/* ***** 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) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
|
* Daniel Glazman <glazman@netscape.com>
|
|
* Neil Deakin <neil@mozdevgroup.com>
|
|
* Mats Palmgren <matspal@gmail.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of 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 ***** */
|
|
|
|
#include "nsHTMLEditRules.h"
|
|
|
|
#include "nsEditor.h"
|
|
#include "nsTextEditUtils.h"
|
|
#include "nsHTMLEditUtils.h"
|
|
#include "nsHTMLCSSUtils.h"
|
|
#include "nsHTMLEditor.h"
|
|
|
|
#include "nsIServiceManager.h"
|
|
#include "nsCRT.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentIterator.h"
|
|
#include "nsIDOMNode.h"
|
|
#include "nsIDOMText.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsISelection.h"
|
|
#include "nsISelectionPrivate.h"
|
|
#include "nsISelectionController.h"
|
|
#include "nsIDOMRange.h"
|
|
#include "nsIDOMNSRange.h"
|
|
#include "nsIRangeUtils.h"
|
|
#include "nsIDOMCharacterData.h"
|
|
#include "nsIEnumerator.h"
|
|
#include "nsIDOMNamedNodeMap.h"
|
|
#include "nsIRange.h"
|
|
|
|
#include "nsEditorUtils.h"
|
|
#include "nsWSRunObject.h"
|
|
|
|
#include "InsertTextTxn.h"
|
|
#include "DeleteTextTxn.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsUnicharUtils.h"
|
|
|
|
#include "nsFrameSelection.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsTArray.h"
|
|
#include "nsIHTMLDocument.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
//const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
|
|
//const static char* kMOZEditorBogusNodeValue="TRUE";
|
|
|
|
enum
|
|
{
|
|
kLonely = 0,
|
|
kPrevSib = 1,
|
|
kNextSib = 2,
|
|
kBothSibs = 3
|
|
};
|
|
|
|
/********************************************************
|
|
* first some helpful funcotrs we will use
|
|
********************************************************/
|
|
|
|
static PRBool IsBlockNode(nsIDOMNode* node)
|
|
{
|
|
PRBool isBlock (PR_FALSE);
|
|
nsHTMLEditor::NodeIsBlockStatic(node, &isBlock);
|
|
return isBlock;
|
|
}
|
|
|
|
static PRBool IsInlineNode(nsIDOMNode* node)
|
|
{
|
|
return !IsBlockNode(node);
|
|
}
|
|
|
|
class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor
|
|
{
|
|
public:
|
|
virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all li's, td's & th's iterator covers
|
|
{
|
|
if (nsHTMLEditUtils::IsTableCell(aNode)) return PR_TRUE;
|
|
if (nsHTMLEditUtils::IsListItem(aNode)) return PR_TRUE;
|
|
return PR_FALSE;
|
|
}
|
|
};
|
|
|
|
class nsBRNodeFunctor : public nsBoolDomIterFunctor
|
|
{
|
|
public:
|
|
virtual PRBool operator()(nsIDOMNode* aNode)
|
|
{
|
|
if (nsTextEditUtils::IsBreak(aNode)) return PR_TRUE;
|
|
return PR_FALSE;
|
|
}
|
|
};
|
|
|
|
class nsEmptyEditableFunctor : public nsBoolDomIterFunctor
|
|
{
|
|
public:
|
|
nsEmptyEditableFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {}
|
|
virtual PRBool operator()(nsIDOMNode* aNode)
|
|
{
|
|
if (mHTMLEditor->IsEditable(aNode) &&
|
|
(nsHTMLEditUtils::IsListItem(aNode) ||
|
|
nsHTMLEditUtils::IsTableCellOrCaption(aNode)))
|
|
{
|
|
PRBool bIsEmptyNode;
|
|
nsresult res = mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, PR_FALSE, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, PR_FALSE);
|
|
if (bIsEmptyNode)
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
protected:
|
|
nsHTMLEditor* mHTMLEditor;
|
|
};
|
|
|
|
class nsEditableTextFunctor : public nsBoolDomIterFunctor
|
|
{
|
|
public:
|
|
nsEditableTextFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {}
|
|
virtual PRBool operator()(nsIDOMNode* aNode)
|
|
{
|
|
if (nsEditor::IsTextNode(aNode) && mHTMLEditor->IsEditable(aNode))
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
protected:
|
|
nsHTMLEditor* mHTMLEditor;
|
|
};
|
|
|
|
|
|
/********************************************************
|
|
* Constructor/Destructor
|
|
********************************************************/
|
|
|
|
nsHTMLEditRules::nsHTMLEditRules() :
|
|
mDocChangeRange(nsnull)
|
|
,mListenerEnabled(PR_TRUE)
|
|
,mReturnInEmptyLIKillsList(PR_TRUE)
|
|
,mDidDeleteSelection(PR_FALSE)
|
|
,mDidRangedDelete(PR_FALSE)
|
|
,mRestoreContentEditableCount(PR_FALSE)
|
|
,mUtilRange(nsnull)
|
|
,mJoinOffset(0)
|
|
{
|
|
nsString emptyString;
|
|
// populate mCachedStyles
|
|
mCachedStyles[0] = StyleCache(nsEditProperty::b, emptyString, emptyString);
|
|
mCachedStyles[1] = StyleCache(nsEditProperty::i, emptyString, emptyString);
|
|
mCachedStyles[2] = StyleCache(nsEditProperty::u, emptyString, emptyString);
|
|
mCachedStyles[3] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("face"), emptyString);
|
|
mCachedStyles[4] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("size"), emptyString);
|
|
mCachedStyles[5] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("color"), emptyString);
|
|
mCachedStyles[6] = StyleCache(nsEditProperty::tt, emptyString, emptyString);
|
|
mCachedStyles[7] = StyleCache(nsEditProperty::em, emptyString, emptyString);
|
|
mCachedStyles[8] = StyleCache(nsEditProperty::strong, emptyString, emptyString);
|
|
mCachedStyles[9] = StyleCache(nsEditProperty::dfn, emptyString, emptyString);
|
|
mCachedStyles[10] = StyleCache(nsEditProperty::code, emptyString, emptyString);
|
|
mCachedStyles[11] = StyleCache(nsEditProperty::samp, emptyString, emptyString);
|
|
mCachedStyles[12] = StyleCache(nsEditProperty::var, emptyString, emptyString);
|
|
mCachedStyles[13] = StyleCache(nsEditProperty::cite, emptyString, emptyString);
|
|
mCachedStyles[14] = StyleCache(nsEditProperty::abbr, emptyString, emptyString);
|
|
mCachedStyles[15] = StyleCache(nsEditProperty::acronym, emptyString, emptyString);
|
|
mCachedStyles[16] = StyleCache(nsEditProperty::cssBackgroundColor, emptyString, emptyString);
|
|
mCachedStyles[17] = StyleCache(nsEditProperty::sub, emptyString, emptyString);
|
|
mCachedStyles[18] = StyleCache(nsEditProperty::sup, emptyString, emptyString);
|
|
}
|
|
|
|
nsHTMLEditRules::~nsHTMLEditRules()
|
|
{
|
|
// remove ourselves as a listener to edit actions
|
|
// In some cases, we have already been removed by
|
|
// ~nsHTMLEditor, in which case we will get a null pointer here
|
|
// which we ignore. But this allows us to add the ability to
|
|
// switch rule sets on the fly if we want.
|
|
if (mHTMLEditor)
|
|
mHTMLEditor->RemoveEditActionListener(this);
|
|
}
|
|
|
|
/********************************************************
|
|
* XPCOM Cruft
|
|
********************************************************/
|
|
|
|
NS_IMPL_ADDREF_INHERITED(nsHTMLEditRules, nsTextEditRules)
|
|
NS_IMPL_RELEASE_INHERITED(nsHTMLEditRules, nsTextEditRules)
|
|
NS_IMPL_QUERY_INTERFACE_INHERITED1(nsHTMLEditRules, nsTextEditRules, nsIEditActionListener)
|
|
|
|
|
|
/********************************************************
|
|
* Public methods
|
|
********************************************************/
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::Init(nsPlaintextEditor *aEditor)
|
|
{
|
|
mHTMLEditor = static_cast<nsHTMLEditor*>(aEditor);
|
|
nsresult res;
|
|
|
|
// call through to base class Init
|
|
res = nsTextEditRules::Init(aEditor);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// cache any prefs we care about
|
|
static const char kPrefName[] =
|
|
"editor.html.typing.returnInEmptyListItemClosesList";
|
|
nsAdoptingCString returnInEmptyLIKillsList =
|
|
Preferences::GetCString(kPrefName);
|
|
|
|
// only when "false", becomes FALSE. Otherwise (including empty), TRUE.
|
|
// XXX Why was this pref designed as a string and not bool?
|
|
mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false");
|
|
|
|
// make a utility range for use by the listenter
|
|
mUtilRange = do_CreateInstance("@mozilla.org/content/range;1");
|
|
NS_ENSURE_TRUE(mUtilRange, NS_ERROR_NULL_POINTER);
|
|
|
|
// set up mDocChangeRange to be whole doc
|
|
nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
|
|
if (rootElem)
|
|
{
|
|
// temporarily turn off rules sniffing
|
|
nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
|
|
if (!mDocChangeRange)
|
|
{
|
|
mDocChangeRange = do_CreateInstance("@mozilla.org/content/range;1");
|
|
NS_ENSURE_TRUE(mDocChangeRange, NS_ERROR_NULL_POINTER);
|
|
}
|
|
mDocChangeRange->SelectNode(rootElem);
|
|
res = AdjustSpecialBreaks();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// add ourselves as a listener to edit actions
|
|
res = mHTMLEditor->AddEditActionListener(this);
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DetachEditor()
|
|
{
|
|
mHTMLEditor = nsnull;
|
|
return nsTextEditRules::DetachEditor();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::BeforeEdit(PRInt32 action, nsIEditor::EDirection aDirection)
|
|
{
|
|
if (mLockRulesSniffing) return NS_OK;
|
|
|
|
nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
|
|
mDidExplicitlySetInterline = PR_FALSE;
|
|
|
|
if (!mActionNesting++)
|
|
{
|
|
// clear our flag about if just deleted a range
|
|
mDidRangedDelete = PR_FALSE;
|
|
|
|
// remember where our selection was before edit action took place:
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// get the selection start location
|
|
nsCOMPtr<nsIDOMNode> selStartNode, selEndNode;
|
|
PRInt32 selOffset;
|
|
res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
mRangeItem.startNode = selStartNode;
|
|
mRangeItem.startOffset = selOffset;
|
|
|
|
// get the selection end location
|
|
res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
mRangeItem.endNode = selEndNode;
|
|
mRangeItem.endOffset = selOffset;
|
|
|
|
// register this range with range updater to track this as we perturb the doc
|
|
(mHTMLEditor->mRangeUpdater).RegisterRangeItem(&mRangeItem);
|
|
|
|
// clear deletion state bool
|
|
mDidDeleteSelection = PR_FALSE;
|
|
|
|
// clear out mDocChangeRange and mUtilRange
|
|
if(mDocChangeRange)
|
|
{
|
|
// clear out our accounting of what changed
|
|
nsCOMPtr<nsIRange> range = do_QueryInterface(mDocChangeRange);
|
|
range->Reset();
|
|
}
|
|
if(mUtilRange)
|
|
{
|
|
// ditto for mUtilRange.
|
|
nsCOMPtr<nsIRange> range = do_QueryInterface(mUtilRange);
|
|
range->Reset();
|
|
}
|
|
|
|
// remember current inline styles for deletion and normal insertion operations
|
|
if ((action == nsEditor::kOpInsertText) ||
|
|
(action == nsEditor::kOpInsertIMEText) ||
|
|
(action == nsEditor::kOpDeleteSelection) ||
|
|
(action == nsEditor::kOpInsertBreak))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> selNode = selStartNode;
|
|
if (aDirection == nsIEditor::eNext)
|
|
selNode = selEndNode;
|
|
res = CacheInlineStyles(selNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// Stabilize the document against contenteditable count changes
|
|
nsCOMPtr<nsIDOMDocument> doc;
|
|
res = mHTMLEditor->GetDocument(getter_AddRefs(doc));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
|
|
NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
|
|
if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
|
|
htmlDoc->ChangeContentEditableCount(nsnull, +1);
|
|
mRestoreContentEditableCount = PR_TRUE;
|
|
}
|
|
|
|
// check that selection is in subtree defined by body node
|
|
ConfirmSelectionInBody();
|
|
// let rules remember the top level action
|
|
mTheAction = action;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection)
|
|
{
|
|
if (mLockRulesSniffing) return NS_OK;
|
|
|
|
nsAutoLockRulesSniffing lockIt(this);
|
|
|
|
NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
|
|
nsresult res = NS_OK;
|
|
if (!--mActionNesting)
|
|
{
|
|
// do all the tricky stuff
|
|
res = AfterEditInner(action, aDirection);
|
|
|
|
// free up selectionState range item
|
|
(mHTMLEditor->mRangeUpdater).DropRangeItem(&mRangeItem);
|
|
|
|
// Reset the contenteditable count to its previous value
|
|
if (mRestoreContentEditableCount) {
|
|
nsCOMPtr<nsIDOMDocument> doc;
|
|
res = mHTMLEditor->GetDocument(getter_AddRefs(doc));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
|
|
NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
|
|
if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
|
|
htmlDoc->ChangeContentEditableCount(nsnull, -1);
|
|
}
|
|
mRestoreContentEditableCount = PR_FALSE;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection)
|
|
{
|
|
ConfirmSelectionInBody();
|
|
if (action == nsEditor::kOpIgnore) return NS_OK;
|
|
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
nsCOMPtr<nsIDOMNode> rangeStartParent, rangeEndParent;
|
|
PRInt32 rangeStartOffset = 0, rangeEndOffset = 0;
|
|
// do we have a real range to act on?
|
|
PRBool bDamagedRange = PR_FALSE;
|
|
if (mDocChangeRange)
|
|
{
|
|
mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent));
|
|
mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent));
|
|
mDocChangeRange->GetStartOffset(&rangeStartOffset);
|
|
mDocChangeRange->GetEndOffset(&rangeEndOffset);
|
|
if (rangeStartParent && rangeEndParent)
|
|
bDamagedRange = PR_TRUE;
|
|
}
|
|
|
|
if (bDamagedRange && !((action == nsEditor::kOpUndo) || (action == nsEditor::kOpRedo)))
|
|
{
|
|
// don't let any txns in here move the selection around behind our back.
|
|
// Note that this won't prevent explicit selection setting from working.
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
|
|
|
|
// expand the "changed doc range" as needed
|
|
res = PromoteRange(mDocChangeRange, action);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if we did a ranged deletion, make sure we have a place to put caret.
|
|
// Note we only want to do this if the overall operation was deletion,
|
|
// not if deletion was done along the way for kOpLoadHTML, kOpInsertText, etc.
|
|
// That's why this is here rather than DidDeleteSelection().
|
|
if ((action == nsEditor::kOpDeleteSelection) && mDidRangedDelete)
|
|
{
|
|
res = InsertBRIfNeeded(selection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// add in any needed <br>s, and remove any unneeded ones.
|
|
res = AdjustSpecialBreaks();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// merge any adjacent text nodes
|
|
if ( (action != nsEditor::kOpInsertText &&
|
|
action != nsEditor::kOpInsertIMEText) )
|
|
{
|
|
res = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// clean up any empty nodes in the selection
|
|
res = RemoveEmptyNodes();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// attempt to transform any unneeded nbsp's into spaces after doing various operations
|
|
if ((action == nsEditor::kOpInsertText) ||
|
|
(action == nsEditor::kOpInsertIMEText) ||
|
|
(action == nsEditor::kOpDeleteSelection) ||
|
|
(action == nsEditor::kOpInsertBreak) ||
|
|
(action == nsHTMLEditor::kOpHTMLPaste ||
|
|
(action == nsHTMLEditor::kOpLoadHTML)))
|
|
{
|
|
res = AdjustWhitespace(selection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// also do this for original selection endpoints.
|
|
nsWSRunObject(mHTMLEditor, mRangeItem.startNode, mRangeItem.startOffset).AdjustWhitespace();
|
|
// we only need to handle old selection endpoint if it was different from start
|
|
if ((mRangeItem.startNode != mRangeItem.endNode) || (mRangeItem.startOffset != mRangeItem.endOffset))
|
|
{
|
|
nsWSRunObject(mHTMLEditor, mRangeItem.endNode, mRangeItem.endOffset).AdjustWhitespace();
|
|
}
|
|
}
|
|
|
|
// if we created a new block, make sure selection lands in it
|
|
if (mNewBlock)
|
|
{
|
|
res = PinSelectionToNewBlock(selection);
|
|
mNewBlock = 0;
|
|
}
|
|
|
|
// adjust selection for insert text, html paste, and delete actions
|
|
if ((action == nsEditor::kOpInsertText) ||
|
|
(action == nsEditor::kOpInsertIMEText) ||
|
|
(action == nsEditor::kOpDeleteSelection) ||
|
|
(action == nsEditor::kOpInsertBreak) ||
|
|
(action == nsHTMLEditor::kOpHTMLPaste ||
|
|
(action == nsHTMLEditor::kOpLoadHTML)))
|
|
{
|
|
res = AdjustSelection(selection, aDirection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// check for any styles which were removed inappropriately
|
|
if ((action == nsEditor::kOpInsertText) ||
|
|
(action == nsEditor::kOpInsertIMEText) ||
|
|
(action == nsEditor::kOpDeleteSelection) ||
|
|
(action == nsEditor::kOpInsertBreak))
|
|
{
|
|
mHTMLEditor->mTypeInState->UpdateSelState(selection);
|
|
res = ReapplyCachedStyles();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = ClearCachedStyles();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
res = mHTMLEditor->HandleInlineSpellCheck(action, selection,
|
|
mRangeItem.startNode, mRangeItem.startOffset,
|
|
rangeStartParent, rangeStartOffset,
|
|
rangeEndParent, rangeEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// detect empty doc
|
|
res = CreateBogusNodeIfNeeded(selection);
|
|
|
|
// adjust selection HINT if needed
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (!mDidExplicitlySetInterline)
|
|
{
|
|
res = CheckInterlinePosition(selection);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillDoAction(nsISelection *aSelection,
|
|
nsRulesInfo *aInfo,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled)
|
|
{
|
|
NS_ENSURE_TRUE(aInfo && aCancel && aHandled, NS_ERROR_NULL_POINTER);
|
|
#if defined(DEBUG_ftang)
|
|
printf("nsHTMLEditRules::WillDoAction action = %d\n", aInfo->action);
|
|
#endif
|
|
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_FALSE;
|
|
|
|
// my kingdom for dynamic cast
|
|
nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
|
|
|
|
// Deal with actions for which we don't need to check whether the selection is
|
|
// editable.
|
|
if (info->action == kOutputText ||
|
|
info->action == kUndo ||
|
|
info->action == kRedo)
|
|
{
|
|
return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMRange> domRange;
|
|
nsresult rv = aSelection->GetRangeAt(0, getter_AddRefs(domRange));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIDOMNode> selStartNode;
|
|
rv = domRange->GetStartContainer(getter_AddRefs(selStartNode));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!mHTMLEditor->IsModifiableNode(selStartNode))
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> selEndNode;
|
|
rv = domRange->GetEndContainer(getter_AddRefs(selEndNode));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (selStartNode != selEndNode)
|
|
{
|
|
if (!mHTMLEditor->IsModifiableNode(selEndNode))
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIRange> range = do_QueryInterface(domRange);
|
|
nsCOMPtr<nsIDOMNode> ancestor =
|
|
do_QueryInterface(range->GetCommonAncestor());
|
|
if (!mHTMLEditor->IsModifiableNode(ancestor))
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
switch (info->action)
|
|
{
|
|
case kInsertText:
|
|
case kInsertTextIME:
|
|
return WillInsertText(info->action,
|
|
aSelection,
|
|
aCancel,
|
|
aHandled,
|
|
info->inString,
|
|
info->outString,
|
|
info->maxLength);
|
|
case kLoadHTML:
|
|
return WillLoadHTML(aSelection, aCancel);
|
|
case kInsertBreak:
|
|
return WillInsertBreak(aSelection, aCancel, aHandled);
|
|
case kDeleteSelection:
|
|
return WillDeleteSelection(aSelection, info->collapsedAction, aCancel, aHandled);
|
|
case kMakeList:
|
|
return WillMakeList(aSelection, info->blockType, info->entireList, info->bulletType, aCancel, aHandled);
|
|
case kIndent:
|
|
return WillIndent(aSelection, aCancel, aHandled);
|
|
case kOutdent:
|
|
return WillOutdent(aSelection, aCancel, aHandled);
|
|
case kSetAbsolutePosition:
|
|
return WillAbsolutePosition(aSelection, aCancel, aHandled);
|
|
case kRemoveAbsolutePosition:
|
|
return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
|
|
case kAlign:
|
|
return WillAlign(aSelection, info->alignType, aCancel, aHandled);
|
|
case kMakeBasicBlock:
|
|
return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled);
|
|
case kRemoveList:
|
|
return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled);
|
|
case kMakeDefListItem:
|
|
return WillMakeDefListItem(aSelection, info->blockType, info->entireList, aCancel, aHandled);
|
|
case kInsertElement:
|
|
return WillInsert(aSelection, aCancel);
|
|
case kDecreaseZIndex:
|
|
return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
|
|
case kIncreaseZIndex:
|
|
return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
|
|
}
|
|
return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidDoAction(nsISelection *aSelection,
|
|
nsRulesInfo *aInfo, nsresult aResult)
|
|
{
|
|
nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
|
|
switch (info->action)
|
|
{
|
|
case kInsertBreak:
|
|
return DidInsertBreak(aSelection, aResult);
|
|
case kDeleteSelection:
|
|
return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
|
|
case kMakeBasicBlock:
|
|
case kIndent:
|
|
case kOutdent:
|
|
case kAlign:
|
|
return DidMakeBasicBlock(aSelection, aInfo, aResult);
|
|
case kSetAbsolutePosition: {
|
|
nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
return DidAbsolutePosition();
|
|
}
|
|
}
|
|
|
|
// default: pass thru to nsTextEditRules
|
|
return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::GetListState(PRBool *aMixed, PRBool *aOL, PRBool *aUL, PRBool *aDL)
|
|
{
|
|
NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
|
|
*aMixed = PR_FALSE;
|
|
*aOL = PR_FALSE;
|
|
*aUL = PR_FALSE;
|
|
*aDL = PR_FALSE;
|
|
PRBool bNonList = PR_FALSE;
|
|
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
nsresult res = GetListActionNodes(arrayOfNodes, PR_FALSE, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// examine list type for nodes in selection
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsIDOMNode* curNode = arrayOfNodes[i];
|
|
|
|
if (nsHTMLEditUtils::IsUnorderedList(curNode))
|
|
*aUL = PR_TRUE;
|
|
else if (nsHTMLEditUtils::IsOrderedList(curNode))
|
|
*aOL = PR_TRUE;
|
|
else if (nsEditor::NodeIsType(curNode, nsEditProperty::li))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (nsHTMLEditUtils::IsUnorderedList(parent))
|
|
*aUL = PR_TRUE;
|
|
else if (nsHTMLEditUtils::IsOrderedList(parent))
|
|
*aOL = PR_TRUE;
|
|
}
|
|
else if (nsEditor::NodeIsType(curNode, nsEditProperty::dl) ||
|
|
nsEditor::NodeIsType(curNode, nsEditProperty::dt) ||
|
|
nsEditor::NodeIsType(curNode, nsEditProperty::dd) )
|
|
{
|
|
*aDL = PR_TRUE;
|
|
}
|
|
else bNonList = PR_TRUE;
|
|
}
|
|
|
|
// hokey arithmetic with booleans
|
|
if ( (*aUL + *aOL + *aDL + bNonList) > 1) *aMixed = PR_TRUE;
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::GetListItemState(PRBool *aMixed, PRBool *aLI, PRBool *aDT, PRBool *aDD)
|
|
{
|
|
NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER);
|
|
*aMixed = PR_FALSE;
|
|
*aLI = PR_FALSE;
|
|
*aDT = PR_FALSE;
|
|
*aDD = PR_FALSE;
|
|
PRBool bNonList = PR_FALSE;
|
|
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
nsresult res = GetListActionNodes(arrayOfNodes, PR_FALSE, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// examine list type for nodes in selection
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i = listCount-1; i>=0; i--)
|
|
{
|
|
nsIDOMNode* curNode = arrayOfNodes[i];
|
|
|
|
if (nsHTMLEditUtils::IsUnorderedList(curNode) ||
|
|
nsHTMLEditUtils::IsOrderedList(curNode) ||
|
|
nsEditor::NodeIsType(curNode, nsEditProperty::li) )
|
|
{
|
|
*aLI = PR_TRUE;
|
|
}
|
|
else if (nsEditor::NodeIsType(curNode, nsEditProperty::dt))
|
|
{
|
|
*aDT = PR_TRUE;
|
|
}
|
|
else if (nsEditor::NodeIsType(curNode, nsEditProperty::dd))
|
|
{
|
|
*aDD = PR_TRUE;
|
|
}
|
|
else if (nsEditor::NodeIsType(curNode, nsEditProperty::dl))
|
|
{
|
|
// need to look inside dl and see which types of items it has
|
|
PRBool bDT, bDD;
|
|
res = GetDefinitionListItemTypes(curNode, bDT, bDD);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
*aDT |= bDT;
|
|
*aDD |= bDD;
|
|
}
|
|
else bNonList = PR_TRUE;
|
|
}
|
|
|
|
// hokey arithmetic with booleans
|
|
if ( (*aDT + *aDD + bNonList) > 1) *aMixed = PR_TRUE;
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::GetAlignment(PRBool *aMixed, nsIHTMLEditor::EAlignment *aAlign)
|
|
{
|
|
// for now, just return first alignment. we'll lie about
|
|
// if it's mixed. This is for efficiency
|
|
// given that our current ui doesn't care if it's mixed.
|
|
// cmanske: NOT TRUE! We would like to pay attention to mixed state
|
|
// in Format | Align submenu!
|
|
|
|
// this routine assumes that alignment is done ONLY via divs
|
|
|
|
// default alignment is left
|
|
NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER);
|
|
*aMixed = PR_FALSE;
|
|
*aAlign = nsIHTMLEditor::eLeft;
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// get selection location
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
|
|
NS_ENSURE_TRUE(rootElem, NS_ERROR_FAILURE);
|
|
|
|
PRInt32 offset, rootOffset;
|
|
res = nsEditor::GetNodeLocation(rootElem, address_of(parent), &rootOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// is the selection collapsed?
|
|
PRBool bCollapsed;
|
|
res = selection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> nodeToExamine;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
if (bCollapsed)
|
|
{
|
|
// if it is, we want to look at 'parent' and it's ancestors
|
|
// for divs with alignment on them
|
|
nodeToExamine = parent;
|
|
}
|
|
else if (mHTMLEditor->IsTextNode(parent))
|
|
{
|
|
// if we are in a text node, then that is the node of interest
|
|
nodeToExamine = parent;
|
|
}
|
|
else if (nsEditor::NodeIsType(parent, nsEditProperty::html) &&
|
|
offset == rootOffset)
|
|
{
|
|
// if we have selected the body, let's look at the first editable node
|
|
mHTMLEditor->GetNextNode(parent, offset, PR_TRUE, address_of(nodeToExamine));
|
|
}
|
|
else
|
|
{
|
|
nsCOMArray<nsIDOMRange> arrayOfRanges;
|
|
res = GetPromotedRanges(selection, arrayOfRanges, kAlign);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// use these ranges to construct a list of nodes to act on.
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, kAlign, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nodeToExamine = arrayOfNodes.SafeObjectAt(0);
|
|
}
|
|
|
|
NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
|
|
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
NS_NAMED_LITERAL_STRING(typeAttrName, "align");
|
|
nsIAtom *dummyProperty = nsnull;
|
|
nsCOMPtr<nsIDOMNode> blockParent;
|
|
if (mHTMLEditor->IsBlockNode(nodeToExamine))
|
|
blockParent = nodeToExamine;
|
|
else
|
|
blockParent = mHTMLEditor->GetBlockNodeParent(nodeToExamine);
|
|
|
|
NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
|
|
|
|
if (useCSS)
|
|
{
|
|
nsCOMPtr<nsIContent> blockParentContent = do_QueryInterface(blockParent);
|
|
if (blockParentContent &&
|
|
mHTMLEditor->mHTMLCSSUtils->IsCSSEditableProperty(blockParent, dummyProperty, &typeAttrName))
|
|
{
|
|
// we are in CSS mode and we know how to align this element with CSS
|
|
nsAutoString value;
|
|
// let's get the value(s) of text-align or margin-left/margin-right
|
|
mHTMLEditor->mHTMLCSSUtils->GetCSSEquivalentToHTMLInlineStyleSet(blockParent,
|
|
dummyProperty,
|
|
&typeAttrName,
|
|
value,
|
|
COMPUTED_STYLE_TYPE);
|
|
if (value.EqualsLiteral("center") ||
|
|
value.EqualsLiteral("-moz-center") ||
|
|
value.EqualsLiteral("auto auto"))
|
|
{
|
|
*aAlign = nsIHTMLEditor::eCenter;
|
|
return NS_OK;
|
|
}
|
|
if (value.EqualsLiteral("right") ||
|
|
value.EqualsLiteral("-moz-right") ||
|
|
value.EqualsLiteral("auto 0px"))
|
|
{
|
|
*aAlign = nsIHTMLEditor::eRight;
|
|
return NS_OK;
|
|
}
|
|
if (value.EqualsLiteral("justify"))
|
|
{
|
|
*aAlign = nsIHTMLEditor::eJustify;
|
|
return NS_OK;
|
|
}
|
|
*aAlign = nsIHTMLEditor::eLeft;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// check up the ladder for divs with alignment
|
|
nsCOMPtr<nsIDOMNode> temp = nodeToExamine;
|
|
PRBool isFirstNodeToExamine = PR_TRUE;
|
|
while (nodeToExamine)
|
|
{
|
|
if (!isFirstNodeToExamine && nsHTMLEditUtils::IsTable(nodeToExamine))
|
|
{
|
|
// the node to examine is a table and this is not the first node
|
|
// we examine; let's break here to materialize the 'inline-block'
|
|
// behaviour of html tables regarding to text alignment
|
|
return NS_OK;
|
|
}
|
|
if (nsHTMLEditUtils::SupportsAlignAttr(nodeToExamine))
|
|
{
|
|
// check for alignment
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(nodeToExamine);
|
|
if (elem)
|
|
{
|
|
nsAutoString typeAttrVal;
|
|
res = elem->GetAttribute(NS_LITERAL_STRING("align"), typeAttrVal);
|
|
ToLowerCase(typeAttrVal);
|
|
if (NS_SUCCEEDED(res) && typeAttrVal.Length())
|
|
{
|
|
if (typeAttrVal.EqualsLiteral("center"))
|
|
*aAlign = nsIHTMLEditor::eCenter;
|
|
else if (typeAttrVal.EqualsLiteral("right"))
|
|
*aAlign = nsIHTMLEditor::eRight;
|
|
else if (typeAttrVal.EqualsLiteral("justify"))
|
|
*aAlign = nsIHTMLEditor::eJustify;
|
|
else
|
|
*aAlign = nsIHTMLEditor::eLeft;
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
isFirstNodeToExamine = PR_FALSE;
|
|
res = nodeToExamine->GetParentNode(getter_AddRefs(temp));
|
|
if (NS_FAILED(res)) temp = nsnull;
|
|
nodeToExamine = temp;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIAtom* MarginPropertyAtomForIndent(nsHTMLCSSUtils* aHTMLCSSUtils, nsIDOMNode* aNode) {
|
|
nsAutoString direction;
|
|
aHTMLCSSUtils->GetComputedProperty(aNode, nsEditProperty::cssDirection, direction);
|
|
return direction.EqualsLiteral("rtl") ?
|
|
nsEditProperty::cssMarginRight : nsEditProperty::cssMarginLeft;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::GetIndentState(PRBool *aCanIndent, PRBool *aCanOutdent)
|
|
{
|
|
NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE);
|
|
*aCanIndent = PR_TRUE;
|
|
*aCanOutdent = PR_FALSE;
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE);
|
|
|
|
// contruct a list of nodes to act on.
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetNodesFromSelection(selection, kIndent, arrayOfNodes, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// examine nodes in selection for blockquotes or list elements;
|
|
// these we can outdent. Note that we return true for canOutdent
|
|
// if *any* of the selection is outdentable, rather than all of it.
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
|
|
if (nsHTMLEditUtils::IsNodeThatCanOutdent(curNode))
|
|
{
|
|
*aCanOutdent = PR_TRUE;
|
|
break;
|
|
}
|
|
else if (useCSS) {
|
|
// we are in CSS mode, indentation is done using the margin-left (or margin-right) property
|
|
nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode);
|
|
nsAutoString value;
|
|
// retrieve its specified value
|
|
mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value);
|
|
float f;
|
|
nsCOMPtr<nsIAtom> unit;
|
|
// get its number part and its unit
|
|
mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit));
|
|
// if the number part is strictly positive, outdent is possible
|
|
if (0 < f) {
|
|
*aCanOutdent = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!*aCanOutdent)
|
|
{
|
|
// if we haven't found something to outdent yet, also check the parents
|
|
// of selection endpoints. We might have a blockquote or list item
|
|
// in the parent hierarchy.
|
|
|
|
// gather up info we need for test
|
|
nsCOMPtr<nsIDOMNode> parent, tmp, root;
|
|
nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
|
|
NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRInt32 selOffset;
|
|
root = do_QueryInterface(rootElem);
|
|
NS_ENSURE_TRUE(root, NS_ERROR_NO_INTERFACE);
|
|
res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
|
|
|
|
// test start parent hierarchy
|
|
res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
while (parent && (parent!=root))
|
|
{
|
|
if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent))
|
|
{
|
|
*aCanOutdent = PR_TRUE;
|
|
break;
|
|
}
|
|
tmp=parent;
|
|
tmp->GetParentNode(getter_AddRefs(parent));
|
|
}
|
|
|
|
// test end parent hierarchy
|
|
res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(parent), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
while (parent && (parent!=root))
|
|
{
|
|
if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent))
|
|
{
|
|
*aCanOutdent = PR_TRUE;
|
|
break;
|
|
}
|
|
tmp=parent;
|
|
tmp->GetParentNode(getter_AddRefs(parent));
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::GetParagraphState(PRBool *aMixed, nsAString &outFormat)
|
|
{
|
|
// This routine is *heavily* tied to our ui choices in the paragraph
|
|
// style popup. I can't see a way around that.
|
|
NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
|
|
*aMixed = PR_TRUE;
|
|
outFormat.Truncate(0);
|
|
|
|
PRBool bMixed = PR_FALSE;
|
|
// using "x" as an uninitialized value, since "" is meaningful
|
|
nsAutoString formatStr(NS_LITERAL_STRING("x"));
|
|
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
nsresult res = GetParagraphFormatNodes(arrayOfNodes, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// post process list. We need to replace any block nodes that are not format
|
|
// nodes with their content. This is so we only have to look "up" the hierarchy
|
|
// to find format nodes, instead of both up and down.
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
nsAutoString format;
|
|
// if it is a known format node we have it easy
|
|
if (IsBlockNode(curNode) && !nsHTMLEditUtils::IsFormatNode(curNode))
|
|
{
|
|
// arrayOfNodes.RemoveObject(curNode);
|
|
res = AppendInnerFormatNodes(arrayOfNodes, curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
// we might have an empty node list. if so, find selection parent
|
|
// and put that on the list
|
|
listCount = arrayOfNodes.Count();
|
|
if (!listCount)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> selNode;
|
|
PRInt32 selOffset;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER);
|
|
arrayOfNodes.AppendObject(selNode);
|
|
listCount = 1;
|
|
}
|
|
|
|
// remember root node
|
|
nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
|
|
NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER);
|
|
|
|
// loop through the nodes in selection and examine their paragraph format
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
nsAutoString format;
|
|
// if it is a known format node we have it easy
|
|
if (nsHTMLEditUtils::IsFormatNode(curNode))
|
|
GetFormatString(curNode, format);
|
|
else if (IsBlockNode(curNode))
|
|
{
|
|
// this is a div or some other non-format block.
|
|
// we should ignore it. It's children were appended to this list
|
|
// by AppendInnerFormatNodes() call above. We will get needed
|
|
// info when we examine them instead.
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node, tmp = curNode;
|
|
tmp->GetParentNode(getter_AddRefs(node));
|
|
while (node)
|
|
{
|
|
if (node == rootElem)
|
|
{
|
|
format.Truncate(0);
|
|
break;
|
|
}
|
|
else if (nsHTMLEditUtils::IsFormatNode(node))
|
|
{
|
|
GetFormatString(node, format);
|
|
break;
|
|
}
|
|
// else keep looking up
|
|
tmp = node;
|
|
tmp->GetParentNode(getter_AddRefs(node));
|
|
}
|
|
}
|
|
|
|
// if this is the first node, we've found, remember it as the format
|
|
if (formatStr.EqualsLiteral("x"))
|
|
formatStr = format;
|
|
// else make sure it matches previously found format
|
|
else if (format != formatStr)
|
|
{
|
|
bMixed = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*aMixed = bMixed;
|
|
outFormat = formatStr;
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray<nsIDOMNode>& aArray,
|
|
nsIDOMNode *aNode)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNodeList> childList;
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
|
|
aNode->GetChildNodes(getter_AddRefs(childList));
|
|
NS_ENSURE_TRUE(childList, NS_OK);
|
|
PRUint32 len, j=0;
|
|
childList->GetLength(&len);
|
|
|
|
// we only need to place any one inline inside this node onto
|
|
// the list. They are all the same for purposes of determining
|
|
// paragraph style. We use foundInline to track this as we are
|
|
// going through the children in the loop below.
|
|
PRBool foundInline = PR_FALSE;
|
|
while (j < len)
|
|
{
|
|
childList->Item(j, getter_AddRefs(child));
|
|
PRBool isBlock = IsBlockNode(child);
|
|
PRBool isFormat = nsHTMLEditUtils::IsFormatNode(child);
|
|
if (isBlock && !isFormat) // if it's a div, etc, recurse
|
|
AppendInnerFormatNodes(aArray, child);
|
|
else if (isFormat)
|
|
{
|
|
aArray.AppendObject(child);
|
|
}
|
|
else if (!foundInline) // if this is the first inline we've found, use it
|
|
{
|
|
foundInline = PR_TRUE;
|
|
aArray.AppendObject(child);
|
|
}
|
|
j++;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::GetFormatString(nsIDOMNode *aNode, nsAString &outFormat)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
if (nsHTMLEditUtils::IsFormatNode(aNode))
|
|
{
|
|
nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(aNode);
|
|
atom->ToString(outFormat);
|
|
}
|
|
else
|
|
outFormat.Truncate();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/********************************************************
|
|
* Protected rules methods
|
|
********************************************************/
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillInsert(nsISelection *aSelection, PRBool *aCancel)
|
|
{
|
|
nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Adjust selection to prevent insertion after a moz-BR.
|
|
// this next only works for collapsed selections right now,
|
|
// because selection is a pain to work with when not collapsed.
|
|
// (no good way to extend start or end of selection), so we ignore
|
|
// those types of selections.
|
|
PRBool bCollapsed;
|
|
res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bCollapsed) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// if we are after a mozBR in the same block, then move selection
|
|
// to be before it
|
|
nsCOMPtr<nsIDOMNode> selNode, priorNode;
|
|
PRInt32 selOffset;
|
|
// get the (collapsed) selection location
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode),
|
|
&selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// get prior node
|
|
res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset,
|
|
address_of(priorNode));
|
|
if (NS_SUCCEEDED(res) && priorNode && nsTextEditUtils::IsMozBR(priorNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> block1, block2;
|
|
if (IsBlockNode(selNode)) block1 = selNode;
|
|
else block1 = mHTMLEditor->GetBlockNodeParent(selNode);
|
|
block2 = mHTMLEditor->GetBlockNodeParent(priorNode);
|
|
|
|
if (block1 == block2)
|
|
{
|
|
// if we are here then the selection is right after a mozBR
|
|
// that is in the same block as the selection. We need to move
|
|
// the selection start to be before the mozBR.
|
|
res = nsEditor::GetNodeLocation(priorNode, address_of(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = aSelection->Collapse(selNode,selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
// we need to get the doc
|
|
nsCOMPtr<nsIDOMDocument>doc;
|
|
res = mHTMLEditor->GetDocument(getter_AddRefs(doc));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
|
|
|
|
// for every property that is set, insert a new inline style node
|
|
return CreateStyleForInsertText(aSelection, doc);
|
|
}
|
|
|
|
#ifdef XXX_DEAD_CODE
|
|
nsresult
|
|
nsHTMLEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
|
|
{
|
|
return nsTextEditRules::DidInsert(aSelection, aResult);
|
|
}
|
|
#endif
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillInsertText(PRInt32 aAction,
|
|
nsISelection *aSelection,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled,
|
|
const nsAString *inString,
|
|
nsAString *outString,
|
|
PRInt32 aMaxLength)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
|
|
|
|
if (inString->IsEmpty() && (aAction != kInsertTextIME))
|
|
{
|
|
// HACK: this is a fix for bug 19395
|
|
// I can't outlaw all empty insertions
|
|
// because IME transaction depend on them
|
|
// There is more work to do to make the
|
|
// world safe for IME.
|
|
*aCancel = PR_TRUE;
|
|
*aHandled = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> selNode;
|
|
PRInt32 selOffset;
|
|
|
|
// if the selection isn't collapsed, delete it.
|
|
PRBool bCollapsed;
|
|
res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bCollapsed)
|
|
{
|
|
res = mHTMLEditor->DeleteSelection(nsIEditor::eNone);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
|
|
// get the (collapsed) selection location
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// dont put text in places that can't have it
|
|
if (!mHTMLEditor->IsTextNode(selNode) &&
|
|
!mHTMLEditor->CanContainTag(selNode, NS_LITERAL_STRING("#text")))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// we need to get the doc
|
|
nsCOMPtr<nsIDOMDocument>doc;
|
|
res = mHTMLEditor->GetDocument(getter_AddRefs(doc));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
|
|
|
|
if (aAction == kInsertTextIME)
|
|
{
|
|
// Right now the nsWSRunObject code bails on empty strings, but IME needs
|
|
// the InsertTextImpl() call to still happen since empty strings are meaningful there.
|
|
if (inString->IsEmpty())
|
|
{
|
|
res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode), &selOffset, doc);
|
|
}
|
|
else
|
|
{
|
|
nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset);
|
|
res = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else // aAction == kInsertText
|
|
{
|
|
// find where we are
|
|
nsCOMPtr<nsIDOMNode> curNode = selNode;
|
|
PRInt32 curOffset = selOffset;
|
|
|
|
// is our text going to be PREformatted?
|
|
// We remember this so that we know how to handle tabs.
|
|
PRBool isPRE;
|
|
res = mHTMLEditor->IsPreformatted(selNode, &isPRE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// turn off the edit listener: we know how to
|
|
// build the "doc changed range" ourselves, and it's
|
|
// must faster to do it once here than to track all
|
|
// the changes one at a time.
|
|
nsAutoLockListener lockit(&mListenerEnabled);
|
|
|
|
// don't spaz my selection in subtransactions
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
|
|
nsAutoString tString(*inString);
|
|
const PRUnichar *unicodeBuf = tString.get();
|
|
nsCOMPtr<nsIDOMNode> unused;
|
|
PRInt32 pos = 0;
|
|
NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
|
|
|
|
// for efficiency, break out the pre case separately. This is because
|
|
// its a lot cheaper to search the input string for only newlines than
|
|
// it is to search for both tabs and newlines.
|
|
if (isPRE || IsPlaintextEditor())
|
|
{
|
|
while (unicodeBuf && (pos != -1) && (pos < (PRInt32)(*inString).Length()))
|
|
{
|
|
PRInt32 oldPos = pos;
|
|
PRInt32 subStrLen;
|
|
pos = tString.FindChar(nsCRT::LF, oldPos);
|
|
|
|
if (pos != -1)
|
|
{
|
|
subStrLen = pos - oldPos;
|
|
// if first char is newline, then use just it
|
|
if (subStrLen == 0)
|
|
subStrLen = 1;
|
|
}
|
|
else
|
|
{
|
|
subStrLen = tString.Length() - oldPos;
|
|
pos = tString.Length();
|
|
}
|
|
|
|
nsDependentSubstring subStr(tString, oldPos, subStrLen);
|
|
|
|
// is it a return?
|
|
if (subStr.Equals(newlineStr))
|
|
{
|
|
res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NS_NAMED_LITERAL_STRING(tabStr, "\t");
|
|
NS_NAMED_LITERAL_STRING(spacesStr, " ");
|
|
char specialChars[] = {TAB, nsCRT::LF, 0};
|
|
while (unicodeBuf && (pos != -1) && (pos < (PRInt32)inString->Length()))
|
|
{
|
|
PRInt32 oldPos = pos;
|
|
PRInt32 subStrLen;
|
|
pos = tString.FindCharInSet(specialChars, oldPos);
|
|
|
|
if (pos != -1)
|
|
{
|
|
subStrLen = pos - oldPos;
|
|
// if first char is newline, then use just it
|
|
if (subStrLen == 0)
|
|
subStrLen = 1;
|
|
}
|
|
else
|
|
{
|
|
subStrLen = tString.Length() - oldPos;
|
|
pos = tString.Length();
|
|
}
|
|
|
|
nsDependentSubstring subStr(tString, oldPos, subStrLen);
|
|
nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset);
|
|
|
|
// is it a tab?
|
|
if (subStr.Equals(tabStr))
|
|
{
|
|
res = wsObj.InsertText(spacesStr, address_of(curNode), &curOffset, doc);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
pos++;
|
|
}
|
|
// is it a return?
|
|
else if (subStr.Equals(newlineStr))
|
|
{
|
|
res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
nsCOMPtr<nsISelection> selection(aSelection);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
selPriv->SetInterlinePosition(PR_FALSE);
|
|
if (curNode) aSelection->Collapse(curNode, curOffset);
|
|
// manually update the doc changed range so that AfterEdit will clean up
|
|
// the correct portion of the document.
|
|
if (!mDocChangeRange)
|
|
{
|
|
mDocChangeRange = do_CreateInstance("@mozilla.org/content/range;1");
|
|
NS_ENSURE_TRUE(mDocChangeRange, NS_ERROR_NULL_POINTER);
|
|
}
|
|
res = mDocChangeRange->SetStart(selNode, selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (curNode)
|
|
res = mDocChangeRange->SetEnd(curNode, curOffset);
|
|
else
|
|
res = mDocChangeRange->SetEnd(selNode, selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillLoadHTML(nsISelection *aSelection, PRBool *aCancel)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);
|
|
|
|
*aCancel = PR_FALSE;
|
|
|
|
// Delete mBogusNode if it exists. If we really need one,
|
|
// it will be added during post-processing in AfterEditInner().
|
|
|
|
if (mBogusNode)
|
|
{
|
|
mEditor->DeleteNode(mBogusNode);
|
|
mBogusNode = nsnull;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_FALSE;
|
|
|
|
// if the selection isn't collapsed, delete it.
|
|
PRBool bCollapsed;
|
|
nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bCollapsed)
|
|
{
|
|
res = mHTMLEditor->DeleteSelection(nsIEditor::eNone);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
|
|
// split any mailcites in the way.
|
|
// should we abort this if we encounter table cell boundaries?
|
|
if (IsMailEditor())
|
|
{
|
|
res = SplitMailCites(aSelection, IsPlaintextEditor(), aHandled);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (*aHandled) return NS_OK;
|
|
}
|
|
|
|
// smart splitting rules
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
|
|
|
|
// identify the block
|
|
nsCOMPtr<nsIDOMNode> blockParent;
|
|
|
|
if (IsBlockNode(node))
|
|
blockParent = node;
|
|
else
|
|
blockParent = mHTMLEditor->GetBlockNodeParent(node);
|
|
|
|
NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
|
|
|
|
// do nothing if the node is read-only
|
|
if (!mHTMLEditor->IsModifiableNode(blockParent))
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// if block is empty, populate with br.
|
|
// (for example, imagine a div that contains the word "text". the user selects
|
|
// "text" and types return. "text" is deleted leaving an empty block. we want
|
|
// to put in one br to make block have a line. then code further below will put
|
|
// in a second br.)
|
|
PRBool isEmpty;
|
|
res = IsEmptyBlock(blockParent, &isEmpty);
|
|
if (isEmpty)
|
|
{
|
|
PRUint32 blockLen;
|
|
res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->CreateBR(blockParent, blockLen, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> listItem = IsInListItem(blockParent);
|
|
if (listItem)
|
|
{
|
|
res = ReturnInListItem(aSelection, listItem, node, offset);
|
|
*aHandled = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// headers: close (or split) header
|
|
else if (nsHTMLEditUtils::IsHeader(blockParent))
|
|
{
|
|
res = ReturnInHeader(aSelection, blockParent, node, offset);
|
|
*aHandled = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// paragraphs: special rules to look for <br>s
|
|
else if (nsHTMLEditUtils::IsParagraph(blockParent))
|
|
{
|
|
res = ReturnInParagraph(aSelection, blockParent, node, offset, aCancel, aHandled);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// fall through, we may not have handled it in ReturnInParagraph()
|
|
}
|
|
|
|
// if not already handled then do the standard thing
|
|
if (!(*aHandled))
|
|
{
|
|
res = StandardBreakImpl(node, offset, aSelection);
|
|
*aHandled = PR_TRUE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::StandardBreakImpl(nsIDOMNode *aNode, PRInt32 aOffset, nsISelection *aSelection)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
PRBool bAfterBlock = PR_FALSE;
|
|
PRBool bBeforeBlock = PR_FALSE;
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> node(aNode);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
|
|
|
|
if (IsPlaintextEditor())
|
|
{
|
|
res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode));
|
|
}
|
|
else
|
|
{
|
|
nsWSRunObject wsObj(mHTMLEditor, node, aOffset);
|
|
nsCOMPtr<nsIDOMNode> visNode, linkNode;
|
|
PRInt32 visOffset=0, newOffset;
|
|
PRInt16 wsType;
|
|
res = wsObj.PriorVisibleNode(node, aOffset, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (wsType & nsWSRunObject::eBlock)
|
|
bAfterBlock = PR_TRUE;
|
|
res = wsObj.NextVisibleNode(node, aOffset, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (wsType & nsWSRunObject::eBlock)
|
|
bBeforeBlock = PR_TRUE;
|
|
if (mHTMLEditor->IsInLink(node, address_of(linkNode)))
|
|
{
|
|
// split the link
|
|
nsCOMPtr<nsIDOMNode> linkParent;
|
|
res = linkNode->GetParentNode(getter_AddRefs(linkParent));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->SplitNodeDeep(linkNode, node, aOffset, &newOffset, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// reset {node,aOffset} to the point where link was split
|
|
node = linkParent;
|
|
aOffset = newOffset;
|
|
}
|
|
res = wsObj.InsertBreak(address_of(node), &aOffset, address_of(brNode), nsIEditor::eNone);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = nsEditor::GetNodeLocation(brNode, address_of(node), &aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bAfterBlock && bBeforeBlock)
|
|
{
|
|
// we just placed a br between block boundaries.
|
|
// This is the one case where we want the selection to be before
|
|
// the br we just placed, as the br will be on a new line,
|
|
// rather than at end of prior line.
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
res = aSelection->Collapse(node, aOffset);
|
|
}
|
|
else
|
|
{
|
|
nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1);
|
|
nsCOMPtr<nsIDOMNode> secondBR;
|
|
PRInt32 visOffset=0;
|
|
PRInt16 wsType;
|
|
res = wsObj.NextVisibleNode(node, aOffset+1, address_of(secondBR), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (wsType==nsWSRunObject::eBreak)
|
|
{
|
|
// the next thing after the break we inserted is another break. Move the 2nd
|
|
// break to be the first breaks sibling. This will prevent them from being
|
|
// in different inline nodes, which would break SetInterlinePosition(). It will
|
|
// also assure that if the user clicks away and then clicks back on their new
|
|
// blank line, they will still get the style from the line above.
|
|
nsCOMPtr<nsIDOMNode> brParent;
|
|
PRInt32 brOffset;
|
|
res = nsEditor::GetNodeLocation(secondBR, address_of(brParent), &brOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if ((brParent != node) || (brOffset != (aOffset+1)))
|
|
{
|
|
res = mHTMLEditor->MoveNode(secondBR, node, aOffset+1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
// SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
|
|
// We want the caret to stick to whatever is past the break. This is
|
|
// because the break is on the same line we were on, but the next content
|
|
// will be on the following line.
|
|
|
|
// An exception to this is if the break has a next sibling that is a block node.
|
|
// Then we stick to the left to avoid an uber caret.
|
|
nsCOMPtr<nsIDOMNode> siblingNode;
|
|
brNode->GetNextSibling(getter_AddRefs(siblingNode));
|
|
if (siblingNode && IsBlockNode(siblingNode))
|
|
selPriv->SetInterlinePosition(PR_FALSE);
|
|
else
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
res = aSelection->Collapse(node, aOffset+1);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::SplitMailCites(nsISelection *aSelection, PRBool aPlaintext, PRBool *aHandled)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection && aHandled, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
|
|
nsCOMPtr<nsIDOMNode> citeNode, selNode, leftCite, rightCite;
|
|
PRInt32 selOffset, newOffset;
|
|
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = GetTopEnclosingMailCite(selNode, address_of(citeNode), aPlaintext);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (citeNode)
|
|
{
|
|
// If our selection is just before a break, nudge it to be
|
|
// just after it. This does two things for us. It saves us the trouble of having to add
|
|
// a break here ourselves to preserve the "blockness" of the inline span mailquote
|
|
// (in the inline case), and :
|
|
// it means the break won't end up making an empty line that happens to be inside a
|
|
// mailquote (in either inline or block case).
|
|
// The latter can confuse a user if they click there and start typing,
|
|
// because being in the mailquote may affect wrapping behavior, or font color, etc.
|
|
nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset);
|
|
nsCOMPtr<nsIDOMNode> visNode;
|
|
PRInt32 visOffset=0;
|
|
PRInt16 wsType;
|
|
res = wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (wsType==nsWSRunObject::eBreak)
|
|
{
|
|
// ok, we are just before a break. is it inside the mailquote?
|
|
PRInt32 unused;
|
|
if (nsEditorUtils::IsDescendantOf(visNode, citeNode, &unused))
|
|
{
|
|
// it is. so lets reset our selection to be just after it.
|
|
res = mHTMLEditor->GetNodeLocation(visNode, address_of(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
++selOffset;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset,
|
|
PR_TRUE, address_of(leftCite), address_of(rightCite));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = citeNode->GetParentNode(getter_AddRefs(selNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// want selection before the break, and on same line
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
res = aSelection->Collapse(selNode, newOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// if citeNode wasn't a block, we might also want another break before it.
|
|
// We need to examine the content both before the br we just added and also
|
|
// just after it. If we don't have another br or block boundary adjacent,
|
|
// then we will need a 2nd br added to achieve blank line that user expects.
|
|
if (IsInlineNode(citeNode))
|
|
{
|
|
nsWSRunObject wsObj(mHTMLEditor, selNode, newOffset);
|
|
nsCOMPtr<nsIDOMNode> visNode;
|
|
PRInt32 visOffset=0;
|
|
PRInt16 wsType;
|
|
res = wsObj.PriorVisibleNode(selNode, newOffset, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if ((wsType==nsWSRunObject::eNormalWS) ||
|
|
(wsType==nsWSRunObject::eText) ||
|
|
(wsType==nsWSRunObject::eSpecial))
|
|
{
|
|
nsWSRunObject wsObjAfterBR(mHTMLEditor, selNode, newOffset+1);
|
|
res = wsObjAfterBR.NextVisibleNode(selNode, newOffset+1, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if ((wsType==nsWSRunObject::eNormalWS) ||
|
|
(wsType==nsWSRunObject::eText) ||
|
|
(wsType==nsWSRunObject::eSpecial))
|
|
{
|
|
res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
// delete any empty cites
|
|
PRBool bEmptyCite = PR_FALSE;
|
|
if (leftCite)
|
|
{
|
|
res = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, PR_TRUE, PR_FALSE);
|
|
if (NS_SUCCEEDED(res) && bEmptyCite)
|
|
res = mHTMLEditor->DeleteNode(leftCite);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
if (rightCite)
|
|
{
|
|
res = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, PR_TRUE, PR_FALSE);
|
|
if (NS_SUCCEEDED(res) && bEmptyCite)
|
|
res = mHTMLEditor->DeleteNode(rightCite);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
*aHandled = PR_TRUE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
|
|
nsIEditor::EDirection aAction,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled)
|
|
{
|
|
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_FALSE;
|
|
|
|
// remember that we did a selection deletion. Used by CreateStyleForInsertText()
|
|
mDidDeleteSelection = PR_TRUE;
|
|
|
|
// if there is only bogus content, cancel the operation
|
|
if (mBogusNode)
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult res = NS_OK;
|
|
PRBool bCollapsed, join = PR_FALSE;
|
|
res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// origCollapsed is used later to determine whether we should join
|
|
// blocks. We don't really care about bCollapsed because it will be
|
|
// modified by ExtendSelectionForDelete later. JoinBlocks should
|
|
// happen if the original selection is collapsed and the cursor is
|
|
// at the end of a block element, in which case ExtendSelectionForDelete
|
|
// would always make the selection not collapsed.
|
|
PRBool origCollapsed = bCollapsed;
|
|
nsCOMPtr<nsIDOMNode> startNode, selNode;
|
|
PRInt32 startOffset, selOffset;
|
|
|
|
// first check for table selection mode. If so,
|
|
// hand off to table editor.
|
|
{
|
|
nsCOMPtr<nsIDOMElement> cell;
|
|
res = mHTMLEditor->GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
|
|
if (NS_SUCCEEDED(res) && cell)
|
|
{
|
|
res = mHTMLEditor->DeleteTableCellContents();
|
|
*aHandled = PR_TRUE;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
|
|
|
|
if (bCollapsed)
|
|
{
|
|
// if we are inside an empty block, delete it.
|
|
nsCOMPtr<nsIContent> hostContent = mHTMLEditor->GetActiveEditingHost();
|
|
nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent);
|
|
NS_ENSURE_TRUE(hostNode, NS_ERROR_FAILURE);
|
|
res = CheckForEmptyBlock(startNode, hostNode, aSelection, aHandled);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (*aHandled) return NS_OK;
|
|
|
|
// Test for distance between caret and text that will be deleted
|
|
res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aAction, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (*aCancel) return NS_OK;
|
|
|
|
res = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// We should delete nothing.
|
|
if (aAction == nsIEditor::eNone)
|
|
return NS_OK;
|
|
|
|
// ExtendSelectionForDelete() may have changed the selection, update it
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
|
|
|
|
res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
if (bCollapsed)
|
|
{
|
|
// what's in the direction we are deleting?
|
|
nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset);
|
|
nsCOMPtr<nsIDOMNode> visNode;
|
|
PRInt32 visOffset;
|
|
PRInt16 wsType;
|
|
|
|
// find next visible node
|
|
if (aAction == nsIEditor::eNext)
|
|
res = wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
|
|
else
|
|
res = wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (!visNode) // can't find anything to delete!
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return res;
|
|
}
|
|
|
|
if (wsType==nsWSRunObject::eNormalWS)
|
|
{
|
|
// we found some visible ws to delete. Let ws code handle it.
|
|
if (aAction == nsIEditor::eNext)
|
|
res = wsObj.DeleteWSForward();
|
|
else
|
|
res = wsObj.DeleteWSBackward();
|
|
*aHandled = PR_TRUE;
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = InsertBRIfNeeded(aSelection);
|
|
return res;
|
|
}
|
|
else if (wsType==nsWSRunObject::eText)
|
|
{
|
|
// found normal text to delete.
|
|
PRInt32 so = visOffset;
|
|
PRInt32 eo = visOffset+1;
|
|
if (aAction == nsIEditor::ePrevious)
|
|
{
|
|
if (so == 0) return NS_ERROR_UNEXPECTED;
|
|
so--;
|
|
eo--;
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = aSelection->GetRangeAt(0, getter_AddRefs(range));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
#ifdef DEBUG
|
|
nsIDOMNode *container;
|
|
|
|
res = range->GetStartContainer(&container);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ASSERTION(container == visNode, "selection start not in visNode");
|
|
|
|
res = range->GetEndContainer(&container);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ASSERTION(container == visNode, "selection end not in visNode");
|
|
#endif
|
|
|
|
res = range->GetStartOffset(&so);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->GetEndOffset(&eo);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode), &so, address_of(visNode), &eo);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(visNode));
|
|
res = mHTMLEditor->DeleteText(nodeAsText, NS_MIN(so, eo), NS_ABS(eo - so));
|
|
*aHandled = PR_TRUE;
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = InsertBRIfNeeded(aSelection);
|
|
return res;
|
|
}
|
|
else if ( (wsType==nsWSRunObject::eSpecial) ||
|
|
(wsType==nsWSRunObject::eBreak) ||
|
|
nsHTMLEditUtils::IsHR(visNode) )
|
|
{
|
|
// short circuit for invisible breaks. delete them and recurse.
|
|
if (nsTextEditUtils::IsBreak(visNode) && !mHTMLEditor->IsVisBreak(visNode))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(visNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
return WillDeleteSelection(aSelection, aAction, aCancel, aHandled);
|
|
}
|
|
|
|
// special handling for backspace when positioned after <hr>
|
|
if (aAction == nsIEditor::ePrevious && nsHTMLEditUtils::IsHR(visNode))
|
|
{
|
|
/*
|
|
Only if the caret is positioned at the end-of-hr-line position,
|
|
we want to delete the <hr>.
|
|
|
|
In other words, we only want to delete, if
|
|
our selection position (indicated by startNode and startOffset)
|
|
is the position directly after the <hr>,
|
|
on the same line as the <hr>.
|
|
|
|
To detect this case we check:
|
|
startNode == parentOfVisNode
|
|
and
|
|
startOffset -1 == visNodeOffsetToVisNodeParent
|
|
and
|
|
interline position is false (left)
|
|
|
|
In any other case we set the position to
|
|
startnode -1 and interlineposition to false,
|
|
only moving the caret to the end-of-hr-line position.
|
|
*/
|
|
|
|
PRBool moveOnly = PR_TRUE;
|
|
|
|
res = nsEditor::GetNodeLocation(visNode, address_of(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRBool interLineIsRight;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
|
|
res = selPriv->GetInterlinePosition(&interLineIsRight);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (startNode == selNode &&
|
|
startOffset -1 == selOffset &&
|
|
!interLineIsRight)
|
|
{
|
|
moveOnly = PR_FALSE;
|
|
}
|
|
|
|
if (moveOnly)
|
|
{
|
|
// Go to the position after the <hr>, but to the end of the <hr> line
|
|
// by setting the interline position to left.
|
|
++selOffset;
|
|
res = aSelection->Collapse(selNode, selOffset);
|
|
selPriv->SetInterlinePosition(PR_FALSE);
|
|
mDidExplicitlySetInterline = PR_TRUE;
|
|
*aHandled = PR_TRUE;
|
|
|
|
// There is one exception to the move only case.
|
|
// If the <hr> is followed by a <br> we want to delete the <br>.
|
|
|
|
PRInt16 otherWSType;
|
|
nsCOMPtr<nsIDOMNode> otherNode;
|
|
PRInt32 otherOffset;
|
|
|
|
res = wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), &otherOffset, &otherWSType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (otherWSType == nsWSRunObject::eBreak)
|
|
{
|
|
// Delete the <br>
|
|
|
|
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, otherNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->DeleteNode(otherNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
// else continue with normal delete code
|
|
}
|
|
|
|
// found break or image, or hr.
|
|
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember sibling to visnode, if any
|
|
nsCOMPtr<nsIDOMNode> sibling, stepbrother;
|
|
mHTMLEditor->GetPriorHTMLSibling(visNode, address_of(sibling));
|
|
// delete the node, and join like nodes if appropriate
|
|
res = mHTMLEditor->DeleteNode(visNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// we did something, so lets say so.
|
|
*aHandled = PR_TRUE;
|
|
// is there a prior node and are they siblings?
|
|
if (sibling)
|
|
mHTMLEditor->GetNextHTMLSibling(sibling, address_of(stepbrother));
|
|
if (startNode == stepbrother)
|
|
{
|
|
// are they both text nodes?
|
|
if (mHTMLEditor->IsTextNode(startNode) && mHTMLEditor->IsTextNode(sibling))
|
|
{
|
|
// if so, join them!
|
|
res = JoinNodesSmart(sibling, startNode, address_of(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// fix up selection
|
|
res = aSelection->Collapse(selNode, selOffset);
|
|
}
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = InsertBRIfNeeded(aSelection);
|
|
return res;
|
|
}
|
|
else if (wsType==nsWSRunObject::eOtherBlock)
|
|
{
|
|
// make sure it's not a table element. If so, cancel the operation
|
|
// (translation: users cannot backspace or delete across table cells)
|
|
if (nsHTMLEditUtils::IsTableElement(visNode))
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// next to a block. See if we are between a block and a br. If so, we really
|
|
// want to delete the br. Else join content at selection to the block.
|
|
|
|
PRBool bDeletedBR = PR_FALSE;
|
|
PRInt16 otherWSType;
|
|
nsCOMPtr<nsIDOMNode> otherNode;
|
|
PRInt32 otherOffset;
|
|
|
|
// find node in other direction
|
|
if (aAction == nsIEditor::eNext)
|
|
res = wsObj.PriorVisibleNode(startNode, startOffset, address_of(otherNode), &otherOffset, &otherWSType);
|
|
else
|
|
res = wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), &otherOffset, &otherWSType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// first find the adjacent node in the block
|
|
nsCOMPtr<nsIDOMNode> leafNode, leftNode, rightNode, leftParent, rightParent;
|
|
if (aAction == nsIEditor::ePrevious)
|
|
{
|
|
res = mHTMLEditor->GetLastEditableLeaf( visNode, address_of(leafNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
leftNode = leafNode;
|
|
rightNode = startNode;
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->GetFirstEditableLeaf( visNode, address_of(leafNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
leftNode = startNode;
|
|
rightNode = leafNode;
|
|
}
|
|
|
|
if (nsTextEditUtils::IsBreak(otherNode))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(otherNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
*aHandled = PR_TRUE;
|
|
bDeletedBR = PR_TRUE;
|
|
}
|
|
|
|
// don't cross table boundaries
|
|
if (leftNode && rightNode)
|
|
{
|
|
PRBool bInDifTblElems;
|
|
res = InDifferentTableElements(leftNode, rightNode, &bInDifTblElems);
|
|
if (NS_FAILED(res) || bInDifTblElems) return res;
|
|
}
|
|
|
|
if (bDeletedBR)
|
|
{
|
|
// put selection at edge of block and we are done.
|
|
nsCOMPtr<nsIDOMNode> newSelNode;
|
|
PRInt32 newSelOffset;
|
|
res = GetGoodSelPointForNode(leafNode, aAction, address_of(newSelNode), &newSelOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
aSelection->Collapse(newSelNode, newSelOffset);
|
|
return res;
|
|
}
|
|
|
|
// else we are joining content to block
|
|
|
|
// find the relavent blocks
|
|
if (IsBlockNode(leftNode))
|
|
leftParent = leftNode;
|
|
else
|
|
leftParent = mHTMLEditor->GetBlockNodeParent(leftNode);
|
|
if (IsBlockNode(rightNode))
|
|
rightParent = rightNode;
|
|
else
|
|
rightParent = mHTMLEditor->GetBlockNodeParent(rightNode);
|
|
|
|
// sanity checks
|
|
NS_ENSURE_TRUE(leftParent && rightParent, NS_ERROR_NULL_POINTER);
|
|
if (leftParent == rightParent)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
// now join them
|
|
nsCOMPtr<nsIDOMNode> selPointNode = startNode;
|
|
PRInt32 selPointOffset = startOffset;
|
|
{
|
|
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset);
|
|
res = JoinBlocks(address_of(leftParent), address_of(rightParent), aCancel);
|
|
*aHandled = PR_TRUE;
|
|
}
|
|
aSelection->Collapse(selPointNode, selPointOffset);
|
|
return res;
|
|
}
|
|
else if (wsType==nsWSRunObject::eThisBlock)
|
|
{
|
|
// at edge of our block. Look beside it and see if we can join to an adjacent block
|
|
|
|
// make sure it's not a table element. If so, cancel the operation
|
|
// (translation: users cannot backspace or delete across table cells)
|
|
if (nsHTMLEditUtils::IsTableElement(visNode))
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// first find the relavent nodes
|
|
nsCOMPtr<nsIDOMNode> leftNode, rightNode, leftParent, rightParent;
|
|
if (aAction == nsIEditor::ePrevious)
|
|
{
|
|
res = mHTMLEditor->GetPriorHTMLNode(visNode, address_of(leftNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
rightNode = startNode;
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->GetNextHTMLNode( visNode, address_of(rightNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
leftNode = startNode;
|
|
}
|
|
|
|
// nothing to join
|
|
if (!leftNode || !rightNode)
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// don't cross table boundaries
|
|
PRBool bInDifTblElems;
|
|
res = InDifferentTableElements(leftNode, rightNode, &bInDifTblElems);
|
|
if (NS_FAILED(res) || bInDifTblElems) return res;
|
|
|
|
// find the relavent blocks
|
|
if (IsBlockNode(leftNode))
|
|
leftParent = leftNode;
|
|
else
|
|
leftParent = mHTMLEditor->GetBlockNodeParent(leftNode);
|
|
if (IsBlockNode(rightNode))
|
|
rightParent = rightNode;
|
|
else
|
|
rightParent = mHTMLEditor->GetBlockNodeParent(rightNode);
|
|
|
|
// sanity checks
|
|
NS_ENSURE_TRUE(leftParent && rightParent, NS_ERROR_NULL_POINTER);
|
|
if (leftParent == rightParent)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
// now join them
|
|
nsCOMPtr<nsIDOMNode> selPointNode = startNode;
|
|
PRInt32 selPointOffset = startOffset;
|
|
{
|
|
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset);
|
|
res = JoinBlocks(address_of(leftParent), address_of(rightParent), aCancel);
|
|
*aHandled = PR_TRUE;
|
|
}
|
|
aSelection->Collapse(selPointNode, selPointOffset);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
|
|
// else we have a non collapsed selection
|
|
// first adjust the selection
|
|
res = ExpandSelectionForDeletion(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// remember that we did a ranged delete for the benefit of AfterEditInner().
|
|
mDidRangedDelete = PR_TRUE;
|
|
|
|
// refresh start and end points
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
|
|
nsCOMPtr<nsIDOMNode> endNode;
|
|
PRInt32 endOffset;
|
|
res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(endNode), &endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
|
|
|
|
// figure out if the endpoints are in nodes that can be merged
|
|
// adjust surrounding whitespace in preperation to delete selection
|
|
if (!IsPlaintextEditor())
|
|
{
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
|
|
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
|
|
address_of(startNode), &startOffset,
|
|
address_of(endNode), &endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
{
|
|
// track end location of where we are deleting
|
|
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(endNode), &endOffset);
|
|
// we are handling all ranged deletions directly now.
|
|
*aHandled = PR_TRUE;
|
|
|
|
if (endNode == startNode)
|
|
{
|
|
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// figure out mailcite ancestors
|
|
nsCOMPtr<nsIDOMNode> endCiteNode, startCiteNode;
|
|
res = GetTopEnclosingMailCite(startNode, address_of(startCiteNode),
|
|
IsPlaintextEditor());
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = GetTopEnclosingMailCite(endNode, address_of(endCiteNode),
|
|
IsPlaintextEditor());
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if we only have a mailcite at one of the two endpoints, set the directionality
|
|
// of the deletion so that the selection will end up outside the mailcite.
|
|
if (startCiteNode && !endCiteNode)
|
|
{
|
|
aAction = nsIEditor::eNext;
|
|
}
|
|
else if (!startCiteNode && endCiteNode)
|
|
{
|
|
aAction = nsIEditor::ePrevious;
|
|
}
|
|
|
|
// figure out block parents
|
|
nsCOMPtr<nsIDOMNode> leftParent;
|
|
nsCOMPtr<nsIDOMNode> rightParent;
|
|
if (IsBlockNode(startNode))
|
|
leftParent = startNode;
|
|
else
|
|
leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
|
|
if (IsBlockNode(endNode))
|
|
rightParent = endNode;
|
|
else
|
|
rightParent = mHTMLEditor->GetBlockNodeParent(endNode);
|
|
|
|
// are endpoint block parents the same? use default deletion
|
|
if (leftParent == rightParent)
|
|
{
|
|
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
|
}
|
|
else
|
|
{
|
|
// deleting across blocks
|
|
// are the blocks of same type?
|
|
NS_ENSURE_STATE(leftParent && rightParent);
|
|
|
|
// are the blocks siblings?
|
|
nsCOMPtr<nsIDOMNode> leftBlockParent;
|
|
nsCOMPtr<nsIDOMNode> rightBlockParent;
|
|
leftParent->GetParentNode(getter_AddRefs(leftBlockParent));
|
|
rightParent->GetParentNode(getter_AddRefs(rightBlockParent));
|
|
|
|
// MOOSE: this could conceivably screw up a table.. fix me.
|
|
if ( (leftBlockParent == rightBlockParent)
|
|
&& (mHTMLEditor->NodesSameType(leftParent, rightParent)) )
|
|
{
|
|
if (nsHTMLEditUtils::IsParagraph(leftParent))
|
|
{
|
|
// first delete the selection
|
|
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// then join para's, insert break
|
|
res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// fix up selection
|
|
res = aSelection->Collapse(selNode,selOffset);
|
|
return res;
|
|
}
|
|
if (nsHTMLEditUtils::IsListItem(leftParent)
|
|
|| nsHTMLEditUtils::IsHeader(leftParent))
|
|
{
|
|
// first delete the selection
|
|
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// join blocks
|
|
res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// fix up selection
|
|
res = aSelection->Collapse(selNode,selOffset);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// else blocks not same type, or not siblings. Delete everything except
|
|
// table elements.
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(enumerator, NS_ERROR_UNEXPECTED);
|
|
|
|
join = PR_TRUE;
|
|
|
|
for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next())
|
|
{
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(currentItem, NS_ERROR_UNEXPECTED);
|
|
|
|
// build a list of nodes in the range
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
nsTrivialFunctor functor;
|
|
nsDOMSubtreeIterator iter;
|
|
res = iter.Init(range);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = iter.AppendList(functor, arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// now that we have the list, delete non table elements
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 j;
|
|
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
nsIDOMNode* somenode = arrayOfNodes[0];
|
|
res = DeleteNonTableElements(somenode);
|
|
arrayOfNodes.RemoveObjectAt(0);
|
|
// If something visible is deleted, no need to join.
|
|
// Visible means all nodes except non-visible textnodes and breaks.
|
|
if (join && origCollapsed) {
|
|
if (mHTMLEditor->IsTextNode(somenode)) {
|
|
mHTMLEditor->IsVisTextNode(somenode, &join, PR_TRUE);
|
|
}
|
|
else {
|
|
join = nsTextEditUtils::IsBreak(somenode) &&
|
|
!mHTMLEditor->IsVisBreak(somenode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check endopints for possible text deletion.
|
|
// we can assume that if text node is found, we can
|
|
// delete to end or to begining as appropriate,
|
|
// since the case where both sel endpoints in same
|
|
// text node was already handled (we wouldn't be here)
|
|
if ( mHTMLEditor->IsTextNode(startNode) )
|
|
{
|
|
// delete to last character
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
|
|
PRUint32 len;
|
|
nodeAsText = do_QueryInterface(startNode);
|
|
nodeAsText->GetLength(&len);
|
|
if (len > (PRUint32)startOffset)
|
|
{
|
|
res = mHTMLEditor->DeleteText(nodeAsText,startOffset,len-startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
if ( mHTMLEditor->IsTextNode(endNode) )
|
|
{
|
|
// delete to first character
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
|
|
nodeAsText = do_QueryInterface(endNode);
|
|
if (endOffset)
|
|
{
|
|
res = mHTMLEditor->DeleteText(nodeAsText,0,endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
if (join) {
|
|
res = JoinBlocks(address_of(leftParent), address_of(rightParent),
|
|
aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//If we're joining blocks: if deleting forward the selection should be
|
|
//collapsed to the end of the selection, if deleting backward the selection
|
|
//should be collapsed to the beginning of the selection. But if we're not
|
|
//joining then the selection should collapse to the beginning of the
|
|
//selection if we'redeleting forward, because the end of the selection will
|
|
//still be in the next block. And same thing for deleting backwards
|
|
//(selection should collapse to the end, because the beginning will still
|
|
//be in the first block). See Bug 507936
|
|
if (join ? aAction == nsIEditor::eNext : aAction == nsIEditor::ePrevious)
|
|
{
|
|
res = aSelection->Collapse(endNode,endOffset);
|
|
}
|
|
else
|
|
{
|
|
res = aSelection->Collapse(startNode,startOffset);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/*****************************************************************************************************
|
|
* InsertBRIfNeeded: determines if a br is needed for current selection to not be spastic.
|
|
* If so, it inserts one. Callers responsibility to only call with collapsed selection.
|
|
* nsISelection *aSelection the collapsed selection
|
|
*/
|
|
nsresult
|
|
nsHTMLEditRules::InsertBRIfNeeded(nsISelection *aSelection)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
|
|
|
|
// get selection
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
|
|
|
|
// inline elements don't need any br
|
|
if (!IsBlockNode(node))
|
|
return res;
|
|
|
|
// examine selection
|
|
nsWSRunObject wsObj(mHTMLEditor, node, offset);
|
|
if (((wsObj.mStartReason & nsWSRunObject::eBlock) || (wsObj.mStartReason & nsWSRunObject::eBreak))
|
|
&& (wsObj.mEndReason & nsWSRunObject::eBlock))
|
|
{
|
|
// if we are tucked between block boundaries then insert a br
|
|
// first check that we are allowed to
|
|
if (mHTMLEditor->CanContainTag(node, NS_LITERAL_STRING("br")))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->CreateBR(node, offset, address_of(brNode), nsIEditor::ePrevious);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*****************************************************************************************************
|
|
* GetGoodSelPointForNode: Finds where at a node you would want to set the selection if you were
|
|
* trying to have a caret next to it.
|
|
* nsIDOMNode *aNode the node
|
|
* nsIEditor::EDirection aAction which edge to find: eNext indicates beginning, ePrevious ending
|
|
* nsCOMPtr<nsIDOMNode> *outSelNode desired sel node
|
|
* PRInt32 *outSelOffset desired sel offset
|
|
*/
|
|
nsresult
|
|
nsHTMLEditRules::GetGoodSelPointForNode(nsIDOMNode *aNode, nsIEditor::EDirection aAction,
|
|
nsCOMPtr<nsIDOMNode> *outSelNode, PRInt32 *outSelOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aNode && outSelNode && outSelOffset, NS_ERROR_NULL_POINTER);
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
// default values
|
|
*outSelNode = aNode;
|
|
*outSelOffset = 0;
|
|
|
|
if (mHTMLEditor->IsTextNode(aNode) || mHTMLEditor->IsContainer(aNode))
|
|
{
|
|
if (aAction == nsIEditor::ePrevious)
|
|
{
|
|
PRUint32 len;
|
|
res = mHTMLEditor->GetLengthOfDOMNode(aNode, len);
|
|
*outSelOffset = PRInt32(len);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
res = nsEditor::GetNodeLocation(aNode, outSelNode, outSelOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!nsTextEditUtils::IsBreak(aNode) || mHTMLEditor->IsVisBreak(aNode))
|
|
{
|
|
if (aAction == nsIEditor::ePrevious)
|
|
(*outSelOffset)++;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/*****************************************************************************************************
|
|
* JoinBlocks: this method is used to join two block elements. The right element is always joined
|
|
* to the left element. If the elements are the same type and not nested within each other,
|
|
* JoinNodesSmart is called (example, joining two list items together into one). If the elements
|
|
* are not the same type, or one is a descendant of the other, we instead destroy the right block
|
|
* placing it's children into leftblock. DTD containment rules are followed throughout.
|
|
* nsCOMPtr<nsIDOMNode> *aLeftBlock pointer to the left block
|
|
* nsCOMPtr<nsIDOMNode> *aRightBlock pointer to the right block; will have contents moved to left block
|
|
* PRBool *aCanceled return TRUE if we had to cancel operation
|
|
*/
|
|
nsresult
|
|
nsHTMLEditRules::JoinBlocks(nsCOMPtr<nsIDOMNode> *aLeftBlock,
|
|
nsCOMPtr<nsIDOMNode> *aRightBlock,
|
|
PRBool *aCanceled)
|
|
{
|
|
NS_ENSURE_TRUE(aLeftBlock && aRightBlock && *aLeftBlock && *aRightBlock, NS_ERROR_NULL_POINTER);
|
|
if (nsHTMLEditUtils::IsTableElement(*aLeftBlock) || nsHTMLEditUtils::IsTableElement(*aRightBlock))
|
|
{
|
|
// do not try to merge table elements
|
|
*aCanceled = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// make sure we don't try to move thing's into HR's, which look like blocks but aren't containers
|
|
if (nsHTMLEditUtils::IsHR(*aLeftBlock))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> realLeft = mHTMLEditor->GetBlockNodeParent(*aLeftBlock);
|
|
*aLeftBlock = realLeft;
|
|
}
|
|
if (nsHTMLEditUtils::IsHR(*aRightBlock))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> realRight = mHTMLEditor->GetBlockNodeParent(*aRightBlock);
|
|
*aRightBlock = realRight;
|
|
}
|
|
|
|
// bail if both blocks the same
|
|
if (*aLeftBlock == *aRightBlock)
|
|
{
|
|
*aCanceled = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Joining a list item to its parent is a NOP.
|
|
if (nsHTMLEditUtils::IsList(*aLeftBlock) && nsHTMLEditUtils::IsListItem(*aRightBlock))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> rightParent;
|
|
(*aRightBlock)->GetParentNode(getter_AddRefs(rightParent));
|
|
if (rightParent == *aLeftBlock)
|
|
return NS_OK;
|
|
}
|
|
|
|
// special rule here: if we are trying to join list items, and they are in different lists,
|
|
// join the lists instead.
|
|
PRBool bMergeLists = PR_FALSE;
|
|
nsAutoString existingListStr;
|
|
PRInt32 theOffset;
|
|
nsCOMPtr<nsIDOMNode> leftList, rightList;
|
|
if (nsHTMLEditUtils::IsListItem(*aLeftBlock) && nsHTMLEditUtils::IsListItem(*aRightBlock))
|
|
{
|
|
(*aLeftBlock)->GetParentNode(getter_AddRefs(leftList));
|
|
(*aRightBlock)->GetParentNode(getter_AddRefs(rightList));
|
|
if (leftList && rightList && (leftList!=rightList))
|
|
{
|
|
// there are some special complications if the lists are descendants of
|
|
// the other lists' items. Note that it is ok for them to be descendants
|
|
// of the other lists themselves, which is the usual case for sublists
|
|
// in our impllementation.
|
|
if (!nsEditorUtils::IsDescendantOf(leftList, *aRightBlock, &theOffset) &&
|
|
!nsEditorUtils::IsDescendantOf(rightList, *aLeftBlock, &theOffset))
|
|
{
|
|
*aLeftBlock = leftList;
|
|
*aRightBlock = rightList;
|
|
bMergeLists = PR_TRUE;
|
|
mHTMLEditor->GetTagString(leftList, existingListStr);
|
|
ToLowerCase(existingListStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
|
|
|
|
nsresult res = NS_OK;
|
|
PRInt32 rightOffset = 0;
|
|
PRInt32 leftOffset = -1;
|
|
|
|
// theOffset below is where you find yourself in aRightBlock when you traverse upwards
|
|
// from aLeftBlock
|
|
if (nsEditorUtils::IsDescendantOf(*aLeftBlock, *aRightBlock, &rightOffset))
|
|
{
|
|
// tricky case. left block is inside right block.
|
|
// Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining.
|
|
rightOffset++;
|
|
res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aLeftBlock, nsWSRunObject::kBlockEnd);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aRightBlock, nsWSRunObject::kAfterBlock, &rightOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// Do br adjustment.
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = CheckForInvisibleBR(*aLeftBlock, kBlockEnd, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bMergeLists)
|
|
{
|
|
// idea here is to take all children in rightList that are past
|
|
// theOffset, and pull them into leftlist.
|
|
nsCOMPtr<nsIDOMNode> childToMove;
|
|
nsCOMPtr<nsIContent> parent(do_QueryInterface(rightList));
|
|
NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
|
|
|
|
nsIContent *child = parent->GetChildAt(theOffset);
|
|
while (child)
|
|
{
|
|
childToMove = do_QueryInterface(child);
|
|
res = mHTMLEditor->MoveNode(childToMove, leftList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
child = parent->GetChildAt(rightOffset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset);
|
|
}
|
|
if (brNode) mHTMLEditor->DeleteNode(brNode);
|
|
}
|
|
// theOffset below is where you find yourself in aLeftBlock when you traverse upwards
|
|
// from aRightBlock
|
|
else if (nsEditorUtils::IsDescendantOf(*aRightBlock, *aLeftBlock, &leftOffset))
|
|
{
|
|
// tricky case. right block is inside left block.
|
|
// Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining.
|
|
res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aRightBlock, nsWSRunObject::kBlockStart);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aLeftBlock, nsWSRunObject::kBeforeBlock, &leftOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// Do br adjustment.
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = CheckForInvisibleBR(*aLeftBlock, kBeforeBlock, address_of(brNode), leftOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bMergeLists)
|
|
{
|
|
res = MoveContents(rightList, leftList, &leftOffset);
|
|
}
|
|
else
|
|
{
|
|
res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset);
|
|
}
|
|
if (brNode) mHTMLEditor->DeleteNode(brNode);
|
|
}
|
|
else
|
|
{
|
|
// normal case. blocks are siblings, or at least close enough to siblings. An example
|
|
// of the latter is a <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The first
|
|
// li and the p are not true siblings, but we still want to join them if you backspace
|
|
// from li into p.
|
|
|
|
// adjust whitespace at block boundaries
|
|
res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, *aLeftBlock, *aRightBlock);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// Do br adjustment.
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = CheckForInvisibleBR(*aLeftBlock, kBlockEnd, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bMergeLists || mHTMLEditor->NodesSameType(*aLeftBlock, *aRightBlock))
|
|
{
|
|
// nodes are same type. merge them.
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = JoinNodesSmart(*aLeftBlock, *aRightBlock, address_of(parent), &offset);
|
|
if (NS_SUCCEEDED(res) && bMergeLists)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> newBlock;
|
|
res = ConvertListType(*aRightBlock, address_of(newBlock), existingListStr, NS_LITERAL_STRING("li"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// nodes are disimilar types.
|
|
res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset);
|
|
}
|
|
if (NS_SUCCEEDED(res) && brNode)
|
|
{
|
|
res = mHTMLEditor->DeleteNode(brNode);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/*****************************************************************************************************
|
|
* MoveBlock: this method is used to move the content from rightBlock into leftBlock
|
|
* Note that the "block" might merely be inline nodes between <br>s, or between blocks, etc.
|
|
* DTD containment rules are followed throughout.
|
|
* nsIDOMNode *aLeftBlock parent to receive moved content
|
|
* nsIDOMNode *aRightBlock parent to provide moved content
|
|
* PRInt32 aLeftOffset offset in aLeftBlock to move content to
|
|
* PRInt32 aRightOffset offset in aRightBlock to move content from
|
|
*/
|
|
nsresult
|
|
nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, PRInt32 aLeftOffset, PRInt32 aRightOffset)
|
|
{
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
// GetNodesFromPoint is the workhorse that figures out what we wnat to move.
|
|
nsresult res = GetNodesFromPoint(DOMPoint(aRightBlock,aRightOffset), kMakeList, arrayOfNodes, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// get the node to act on
|
|
nsIDOMNode* curNode = arrayOfNodes[i];
|
|
if (IsBlockNode(curNode))
|
|
{
|
|
// For block nodes, move their contents only, then delete block.
|
|
res = MoveContents(curNode, aLeftBlock, &aLeftOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
}
|
|
else
|
|
{
|
|
// otherwise move the content as is, checking against the dtd.
|
|
res = MoveNodeSmart(curNode, aLeftBlock, &aLeftOffset);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*****************************************************************************************************
|
|
* MoveNodeSmart: this method is used to move node aSource to (aDest,aOffset).
|
|
* DTD containment rules are followed throughout. aOffset is updated to point _after_
|
|
* inserted content.
|
|
* nsIDOMNode *aSource the selection.
|
|
* nsIDOMNode *aDest parent to receive moved content
|
|
* PRInt32 *aOffset offset in aDest to move content to
|
|
*/
|
|
nsresult
|
|
nsHTMLEditRules::MoveNodeSmart(nsIDOMNode *aSource, nsIDOMNode *aDest, PRInt32 *aOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER);
|
|
|
|
nsAutoString tag;
|
|
nsresult res;
|
|
res = mHTMLEditor->GetTagString(aSource, tag);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
ToLowerCase(tag);
|
|
// check if this node can go into the destination node
|
|
if (mHTMLEditor->CanContainTag(aDest, tag))
|
|
{
|
|
// if it can, move it there
|
|
res = mHTMLEditor->MoveNode(aSource, aDest, *aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (*aOffset != -1) ++(*aOffset);
|
|
}
|
|
else
|
|
{
|
|
// if it can't, move it's children, and then delete it.
|
|
res = MoveContents(aSource, aDest, aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->DeleteNode(aSource);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
/*****************************************************************************************************
|
|
* MoveContents: this method is used to move node the _contents_ of aSource to (aDest,aOffset).
|
|
* DTD containment rules are followed throughout. aOffset is updated to point _after_
|
|
* inserted content. aSource is deleted.
|
|
* nsIDOMNode *aSource the selection.
|
|
* nsIDOMNode *aDest parent to receive moved content
|
|
* PRInt32 *aOffset offset in aDest to move content to
|
|
*/
|
|
nsresult
|
|
nsHTMLEditRules::MoveContents(nsIDOMNode *aSource, nsIDOMNode *aDest, PRInt32 *aOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER);
|
|
if (aSource == aDest) return NS_ERROR_ILLEGAL_VALUE;
|
|
NS_ASSERTION(!mHTMLEditor->IsTextNode(aSource), "#text does not have contents");
|
|
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
nsAutoString tag;
|
|
nsresult res;
|
|
aSource->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
res = MoveNodeSmart(child, aDest, aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
aSource->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::DeleteNonTableElements(nsIDOMNode *aNode)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
nsresult res = NS_OK;
|
|
if (nsHTMLEditUtils::IsTableElementButNotTable(aNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNodeList> children;
|
|
aNode->GetChildNodes(getter_AddRefs(children));
|
|
if (children)
|
|
{
|
|
PRUint32 len;
|
|
children->GetLength(&len);
|
|
NS_ENSURE_TRUE(len, NS_OK);
|
|
PRInt32 j;
|
|
for (j=len-1; j>=0; j--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
children->Item(j,getter_AddRefs(node));
|
|
res = DeleteNonTableElements(node);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->DeleteNode(aNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection,
|
|
nsIEditor::EDirection aDir,
|
|
nsresult aResult)
|
|
{
|
|
if (!aSelection) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
// find where we are
|
|
nsCOMPtr<nsIDOMNode> startNode;
|
|
PRInt32 startOffset;
|
|
nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
|
|
|
|
// find any enclosing mailcite
|
|
nsCOMPtr<nsIDOMNode> citeNode;
|
|
res = GetTopEnclosingMailCite(startNode, address_of(citeNode),
|
|
IsPlaintextEditor());
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (citeNode)
|
|
{
|
|
PRBool isEmpty = PR_TRUE, seenBR = PR_FALSE;
|
|
mHTMLEditor->IsEmptyNodeImpl(citeNode, &isEmpty, PR_TRUE, PR_TRUE, PR_FALSE, &seenBR);
|
|
if (isEmpty)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, brNode;
|
|
PRInt32 offset;
|
|
nsEditor::GetNodeLocation(citeNode, address_of(parent), &offset);
|
|
res = mHTMLEditor->DeleteNode(citeNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (parent && seenBR)
|
|
{
|
|
res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
aSelection->Collapse(parent, offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
// call through to base class
|
|
return nsTextEditRules::DidDeleteSelection(aSelection, aDir, aResult);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillMakeList(nsISelection *aSelection,
|
|
const nsAString *aListType,
|
|
PRBool aEntireList,
|
|
const nsAString *aBulletType,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled,
|
|
const nsAString *aItemType)
|
|
{
|
|
if (!aSelection || !aListType || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_FALSE;
|
|
|
|
// deduce what tag to use for list items
|
|
nsAutoString itemType;
|
|
if (aItemType)
|
|
itemType = *aItemType;
|
|
else if (aListType->LowerCaseEqualsLiteral("dl"))
|
|
itemType.AssignLiteral("dd");
|
|
else
|
|
itemType.AssignLiteral("li");
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
|
|
*aHandled = PR_TRUE;
|
|
|
|
res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetListActionNodes(arrayOfNodes, aEntireList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
|
|
// check if all our nodes are <br>s, or empty inlines
|
|
PRBool bOnlyBreaks = PR_TRUE;
|
|
PRInt32 j;
|
|
for (j=0; j<listCount; j++)
|
|
{
|
|
nsIDOMNode* curNode = arrayOfNodes[j];
|
|
// if curNode is not a Break or empty inline, we're done
|
|
if ( (!nsTextEditUtils::IsBreak(curNode)) && (!IsEmptyInline(curNode)) )
|
|
{
|
|
bOnlyBreaks = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if no nodes, we make empty list. Ditto if the user tried to make a list of some # of breaks.
|
|
if (!listCount || bOnlyBreaks)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, theList, theListItem;
|
|
PRInt32 offset;
|
|
|
|
// if only breaks, delete them
|
|
if (bOnlyBreaks)
|
|
{
|
|
for (j=0; j<(PRInt32)listCount; j++)
|
|
{
|
|
res = mHTMLEditor->DeleteNode(arrayOfNodes[j]);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
// get selection location
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// make sure we can put a list here
|
|
if (!mHTMLEditor->CanContainTag(parent, *aListType)) {
|
|
*aCancel = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
res = SplitAsNeeded(aListType, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(*aListType, parent, offset, getter_AddRefs(theList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(itemType, theList, 0, getter_AddRefs(theListItem));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = theListItem;
|
|
// put selection in new list item
|
|
res = aSelection->Collapse(theListItem,0);
|
|
selectionResetter.Abort(); // to prevent selection reseter from overriding us.
|
|
*aHandled = PR_TRUE;
|
|
return res;
|
|
}
|
|
|
|
// if there is only one node in the array, and it is a list, div, or blockquote,
|
|
// then look inside of it until we find inner list or content.
|
|
|
|
res = LookInsideDivBQandList(arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Ok, now go through all the nodes and put then in the list,
|
|
// or whatever is approriate. Wohoo!
|
|
|
|
listCount = arrayOfNodes.Count();
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
nsCOMPtr<nsIDOMNode> curList;
|
|
nsCOMPtr<nsIDOMNode> prevListItem;
|
|
|
|
PRInt32 i;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsIDOMNode> newBlock;
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// make sure we don't assemble content that is in different table cells into the same list.
|
|
// respect table cell boundaries when listifying.
|
|
if (curList)
|
|
{
|
|
PRBool bInDifTblElems;
|
|
res = InDifferentTableElements(curList, curNode, &bInDifTblElems);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bInDifTblElems)
|
|
curList = nsnull;
|
|
}
|
|
|
|
// if curNode is a Break, delete it, and quit remembering prev list item
|
|
if (nsTextEditUtils::IsBreak(curNode))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
prevListItem = 0;
|
|
continue;
|
|
}
|
|
// if curNode is an empty inline container, delete it
|
|
else if (IsEmptyInline(curNode))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
|
|
if (nsHTMLEditUtils::IsList(curNode))
|
|
{
|
|
nsAutoString existingListStr;
|
|
res = mHTMLEditor->GetTagString(curNode, existingListStr);
|
|
ToLowerCase(existingListStr);
|
|
// do we have a curList already?
|
|
if (curList && !nsEditorUtils::IsDescendantOf(curNode, curList))
|
|
{
|
|
// move all of our children into curList.
|
|
// cheezy way to do it: move whole list and then
|
|
// RemoveContainer() on the list.
|
|
// ConvertListType first: that routine
|
|
// handles converting the list item types, if needed
|
|
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = ConvertListType(curNode, address_of(newBlock), *aListType, itemType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->RemoveBlockContainer(newBlock);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// replace list with new list type
|
|
res = ConvertListType(curNode, address_of(newBlock), *aListType, itemType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curList = newBlock;
|
|
}
|
|
prevListItem = 0;
|
|
continue;
|
|
}
|
|
|
|
if (nsHTMLEditUtils::IsListItem(curNode))
|
|
{
|
|
nsAutoString existingListStr;
|
|
res = mHTMLEditor->GetTagString(curParent, existingListStr);
|
|
ToLowerCase(existingListStr);
|
|
if ( existingListStr != *aListType )
|
|
{
|
|
// list item is in wrong type of list.
|
|
// if we don't have a curList, split the old list
|
|
// and make a new list of correct type.
|
|
if (!curList || nsEditorUtils::IsDescendantOf(curNode, curList))
|
|
{
|
|
res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> p;
|
|
PRInt32 o;
|
|
res = nsEditor::GetNodeLocation(curParent, address_of(p), &o);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(*aListType, p, o, getter_AddRefs(curList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// move list item to new list
|
|
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// convert list item type if needed
|
|
if (!mHTMLEditor->NodeIsTypeString(curNode,itemType))
|
|
{
|
|
res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), itemType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// item is in right type of list. But we might still have to move it.
|
|
// and we might need to convert list item types.
|
|
if (!curList)
|
|
curList = curParent;
|
|
else
|
|
{
|
|
if (curParent != curList)
|
|
{
|
|
// move list item to new list
|
|
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
if (!mHTMLEditor->NodeIsTypeString(curNode,itemType))
|
|
{
|
|
res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), itemType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMElement> curElement = do_QueryInterface(curNode);
|
|
NS_NAMED_LITERAL_STRING(typestr, "type");
|
|
if (aBulletType && !aBulletType->IsEmpty()) {
|
|
res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType);
|
|
}
|
|
else {
|
|
res = mHTMLEditor->RemoveAttribute(curElement, typestr);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
|
|
// if we hit a div clear our prevListItem, insert divs contents
|
|
// into our node array, and remove the div
|
|
if (nsHTMLEditUtils::IsDiv(curNode))
|
|
{
|
|
prevListItem = nsnull;
|
|
PRInt32 j=i+1;
|
|
res = GetInnerContent(curNode, arrayOfNodes, &j);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->RemoveContainer(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
listCount = arrayOfNodes.Count();
|
|
continue;
|
|
}
|
|
|
|
// need to make a list to put things in if we haven't already,
|
|
if (!curList)
|
|
{
|
|
res = SplitAsNeeded(aListType, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(*aListType, curParent, offset, getter_AddRefs(curList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curList;
|
|
// curList is now the correct thing to put curNode in
|
|
prevListItem = 0;
|
|
}
|
|
|
|
// if curNode isn't a list item, we must wrap it in one
|
|
nsCOMPtr<nsIDOMNode> listItem;
|
|
if (!nsHTMLEditUtils::IsListItem(curNode))
|
|
{
|
|
if (IsInlineNode(curNode) && prevListItem)
|
|
{
|
|
// this is a continuation of some inline nodes that belong together in
|
|
// the same list item. use prevListItem
|
|
res = mHTMLEditor->MoveNode(curNode, prevListItem, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// don't wrap li around a paragraph. instead replace paragraph with li
|
|
if (nsHTMLEditUtils::IsParagraph(curNode))
|
|
{
|
|
res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem), itemType);
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->InsertContainerAbove(curNode, address_of(listItem), itemType);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (IsInlineNode(curNode))
|
|
prevListItem = listItem;
|
|
else
|
|
prevListItem = nsnull;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
listItem = curNode;
|
|
}
|
|
|
|
if (listItem) // if we made a new list item, deal with it
|
|
{
|
|
// tuck the listItem into the end of the active list
|
|
res = mHTMLEditor->MoveNode(listItem, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillRemoveList(nsISelection *aSelection,
|
|
PRBool aOrdered,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
|
|
nsresult res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
nsCOMArray<nsIDOMRange> arrayOfRanges;
|
|
res = GetPromotedRanges(aSelection, arrayOfRanges, kMakeList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetListActionNodes(arrayOfNodes, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Remove all non-editable nodes. Leave them be.
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsIDOMNode* testNode = arrayOfNodes[i];
|
|
if (!mHTMLEditor->IsEditable(testNode))
|
|
{
|
|
arrayOfNodes.RemoveObjectAt(i);
|
|
}
|
|
}
|
|
|
|
// reset list count
|
|
listCount = arrayOfNodes.Count();
|
|
|
|
// Only act on lists or list items in the array
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsIDOMNode* curNode = arrayOfNodes[i];
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (nsHTMLEditUtils::IsListItem(curNode)) // unlist this listitem
|
|
{
|
|
PRBool bOutOfList;
|
|
do
|
|
{
|
|
res = PopListItem(curNode, &bOutOfList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
} while (!bOutOfList); // keep popping it out until it's not in a list anymore
|
|
}
|
|
else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, move list items out
|
|
{
|
|
res = RemoveListStructure(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillMakeDefListItem(nsISelection *aSelection,
|
|
const nsAString *aItemType,
|
|
PRBool aEntireList,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled)
|
|
{
|
|
// for now we let WillMakeList handle this
|
|
NS_NAMED_LITERAL_STRING(listType, "dl");
|
|
return WillMakeList(aSelection, &listType, aEntireList, nsnull, aCancel, aHandled, aItemType);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillMakeBasicBlock(nsISelection *aSelection,
|
|
const nsAString *aBlockType,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_FALSE;
|
|
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
|
|
*aHandled = PR_TRUE;
|
|
nsString tString(*aBlockType);
|
|
|
|
// contruct a list of nodes to act on.
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetNodesFromSelection(aSelection, kMakeBasicBlock, arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Remove all non-editable nodes. Leave them be.
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
if (!mHTMLEditor->IsEditable(arrayOfNodes[i]))
|
|
{
|
|
arrayOfNodes.RemoveObjectAt(i);
|
|
}
|
|
}
|
|
|
|
// reset list count
|
|
listCount = arrayOfNodes.Count();
|
|
|
|
// if nothing visible in list, make an empty block
|
|
if (ListIsEmptyLine(arrayOfNodes))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, theBlock;
|
|
PRInt32 offset;
|
|
|
|
// get selection location
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (tString.EqualsLiteral("normal") ||
|
|
tString.IsEmpty() ) // we are removing blocks (going to "body text")
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curBlock = parent;
|
|
if (!IsBlockNode(curBlock))
|
|
curBlock = mHTMLEditor->GetBlockNodeParent(parent);
|
|
nsCOMPtr<nsIDOMNode> curBlockPar;
|
|
NS_ENSURE_TRUE(curBlock, NS_ERROR_NULL_POINTER);
|
|
curBlock->GetParentNode(getter_AddRefs(curBlockPar));
|
|
if (nsHTMLEditUtils::IsFormatNode(curBlock))
|
|
{
|
|
// if the first editable node after selection is a br, consume it. Otherwise
|
|
// it gets pushed into a following block after the split, which is visually bad.
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (brNode && nsTextEditUtils::IsBreak(brNode))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(brNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// do the splits!
|
|
res = mHTMLEditor->SplitNodeDeep(curBlock, parent, offset, &offset, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// put a br at the split point
|
|
res = mHTMLEditor->CreateBR(curBlockPar, offset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// put selection at the split point
|
|
res = aSelection->Collapse(curBlockPar, offset);
|
|
selectionResetter.Abort(); // to prevent selection reseter from overriding us.
|
|
*aHandled = PR_TRUE;
|
|
}
|
|
// else nothing to do!
|
|
}
|
|
else // we are making a block
|
|
{
|
|
// consume a br, if needed
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (brNode && nsTextEditUtils::IsBreak(brNode))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(brNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// we don't need to act on this node any more
|
|
arrayOfNodes.RemoveObject(brNode);
|
|
}
|
|
// make sure we can put a block here
|
|
res = SplitAsNeeded(aBlockType, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = theBlock;
|
|
// delete anything that was in the list of nodes
|
|
for (PRInt32 j = arrayOfNodes.Count() - 1; j >= 0; --j)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = arrayOfNodes.RemoveObjectAt(0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// put selection in new block
|
|
res = aSelection->Collapse(theBlock,0);
|
|
selectionResetter.Abort(); // to prevent selection reseter from overriding us.
|
|
*aHandled = PR_TRUE;
|
|
}
|
|
return res;
|
|
}
|
|
else
|
|
{
|
|
// Ok, now go through all the nodes and make the right kind of blocks,
|
|
// or whatever is approriate. Wohoo!
|
|
// Note: blockquote is handled a little differently
|
|
if (tString.EqualsLiteral("blockquote"))
|
|
res = MakeBlockquote(arrayOfNodes);
|
|
else if (tString.EqualsLiteral("normal") ||
|
|
tString.IsEmpty() )
|
|
res = RemoveBlockStyle(arrayOfNodes);
|
|
else
|
|
res = ApplyBlockStyle(arrayOfNodes, aBlockType);
|
|
return res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::DidMakeBasicBlock(nsISelection *aSelection,
|
|
nsRulesInfo *aInfo, nsresult aResult)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
|
|
// check for empty block. if so, put a moz br in it.
|
|
PRBool isCollapsed;
|
|
nsresult res = aSelection->GetIsCollapsed(&isCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!isCollapsed) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = InsertMozBRIfNeeded(parent);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillIndent(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
|
|
{
|
|
PRBool useCSS;
|
|
nsresult res;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
|
|
if (useCSS) {
|
|
res = WillCSSIndent(aSelection, aCancel, aHandled);
|
|
}
|
|
else {
|
|
res = WillHTMLIndent(aSelection, aCancel, aHandled);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillCSSIndent(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
|
|
res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
nsCOMArray<nsIDOMRange> arrayOfRanges;
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
|
|
// short circuit: detect case of collapsed selection inside an <li>.
|
|
// just sublist that <li>. This prevents bug 97797.
|
|
|
|
PRBool bCollapsed;
|
|
nsCOMPtr<nsIDOMNode> liNode;
|
|
res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bCollapsed)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node, block;
|
|
PRInt32 offset;
|
|
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (IsBlockNode(node))
|
|
block = node;
|
|
else
|
|
block = mHTMLEditor->GetBlockNodeParent(node);
|
|
if (block && nsHTMLEditUtils::IsListItem(block))
|
|
liNode = block;
|
|
}
|
|
|
|
if (liNode)
|
|
{
|
|
arrayOfNodes.AppendObject(liNode);
|
|
}
|
|
else
|
|
{
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
res = GetNodesFromSelection(aSelection, kIndent, arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
|
|
// if nothing visible in list, make an empty block
|
|
if (ListIsEmptyLine(arrayOfNodes))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, theBlock;
|
|
PRInt32 offset;
|
|
nsAutoString quoteType(NS_LITERAL_STRING("div"));
|
|
// get selection location
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// make sure we can put a block here
|
|
res = SplitAsNeeded("eType, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = theBlock;
|
|
RelativeChangeIndentationOfElementNode(theBlock, +1);
|
|
// delete anything that was in the list of nodes
|
|
for (PRInt32 j = arrayOfNodes.Count() - 1; j >= 0; --j)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = arrayOfNodes.RemoveObjectAt(0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// put selection in new block
|
|
res = aSelection->Collapse(theBlock,0);
|
|
selectionResetter.Abort(); // to prevent selection reseter from overriding us.
|
|
*aHandled = PR_TRUE;
|
|
return res;
|
|
}
|
|
|
|
// Ok, now go through all the nodes and put them in a blockquote,
|
|
// or whatever is appropriate. Wohoo!
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
nsCOMPtr<nsIDOMNode> curQuote;
|
|
nsCOMPtr<nsIDOMNode> curList;
|
|
nsCOMPtr<nsIDOMNode> sibling;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
|
|
// Ignore all non-editable nodes. Leave them be.
|
|
if (!mHTMLEditor->IsEditable(curNode)) continue;
|
|
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// some logic for putting list items into nested lists...
|
|
if (nsHTMLEditUtils::IsList(curParent))
|
|
{
|
|
sibling = nsnull;
|
|
|
|
// Check for whether we should join a list that follows curNode.
|
|
// We do this if the next element is a list, and the list is of the
|
|
// same type (li/ol) as curNode was a part it.
|
|
mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling));
|
|
if (sibling && nsHTMLEditUtils::IsList(sibling))
|
|
{
|
|
nsAutoString curListTag, siblingListTag;
|
|
nsEditor::GetTagString(curParent, curListTag);
|
|
nsEditor::GetTagString(sibling, siblingListTag);
|
|
if (curListTag == siblingListTag)
|
|
{
|
|
res = mHTMLEditor->MoveNode(curNode, sibling, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
}
|
|
// Check for whether we should join a list that preceeds curNode.
|
|
// We do this if the previous element is a list, and the list is of
|
|
// the same type (li/ol) as curNode was a part of.
|
|
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
|
|
if (sibling && nsHTMLEditUtils::IsList(sibling))
|
|
{
|
|
nsAutoString curListTag, siblingListTag;
|
|
nsEditor::GetTagString(curParent, curListTag);
|
|
nsEditor::GetTagString(sibling, siblingListTag);
|
|
if (curListTag == siblingListTag)
|
|
{
|
|
res = mHTMLEditor->MoveNode(curNode, sibling, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
}
|
|
sibling = nsnull;
|
|
|
|
// check to see if curList is still appropriate. Which it is if
|
|
// curNode is still right after it in the same list.
|
|
if (curList)
|
|
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
|
|
|
|
if (!curList || (sibling && sibling != curList))
|
|
{
|
|
nsAutoString listTag;
|
|
nsEditor::GetTagString(curParent,listTag);
|
|
ToLowerCase(listTag);
|
|
// create a new nested list of correct type
|
|
res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// curList is now the correct thing to put curNode in
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curList;
|
|
}
|
|
// tuck the node into the end of the active list
|
|
PRUint32 listLen;
|
|
res = mHTMLEditor->GetLengthOfDOMNode(curList, listLen);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->MoveNode(curNode, curList, listLen);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
else // not a list item
|
|
{
|
|
if (IsBlockNode(curNode)) {
|
|
RelativeChangeIndentationOfElementNode(curNode, +1);
|
|
curQuote = nsnull;
|
|
}
|
|
else {
|
|
if (!curQuote)
|
|
{
|
|
NS_NAMED_LITERAL_STRING(divquoteType, "div");
|
|
res = SplitAsNeeded(&divquoteType, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(divquoteType, curParent, offset, getter_AddRefs(curQuote));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
RelativeChangeIndentationOfElementNode(curQuote, +1);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curQuote;
|
|
// curQuote is now the correct thing to put curNode in
|
|
}
|
|
|
|
// tuck the node into the end of the active blockquote
|
|
PRUint32 quoteLen;
|
|
res = mHTMLEditor->GetLengthOfDOMNode(curQuote, quoteLen);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->MoveNode(curNode, curQuote, quoteLen);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillHTMLIndent(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
|
|
res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
|
|
nsCOMArray<nsIDOMRange> arrayOfRanges;
|
|
res = GetPromotedRanges(aSelection, arrayOfRanges, kIndent);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, kIndent);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
|
|
|
|
// if nothing visible in list, make an empty block
|
|
if (ListIsEmptyLine(arrayOfNodes))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, theBlock;
|
|
PRInt32 offset;
|
|
|
|
// get selection location
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// make sure we can put a block here
|
|
res = SplitAsNeeded("eType, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = theBlock;
|
|
// delete anything that was in the list of nodes
|
|
for (PRInt32 j = arrayOfNodes.Count() - 1; j >= 0; --j)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = arrayOfNodes.RemoveObjectAt(0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// put selection in new block
|
|
res = aSelection->Collapse(theBlock,0);
|
|
selectionResetter.Abort(); // to prevent selection reseter from overriding us.
|
|
*aHandled = PR_TRUE;
|
|
return res;
|
|
}
|
|
|
|
// Ok, now go through all the nodes and put them in a blockquote,
|
|
// or whatever is appropriate. Wohoo!
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMNode> curParent, curQuote, curList, indentedLI, sibling;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
|
|
// Ignore all non-editable nodes. Leave them be.
|
|
if (!mHTMLEditor->IsEditable(curNode)) continue;
|
|
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// some logic for putting list items into nested lists...
|
|
if (nsHTMLEditUtils::IsList(curParent))
|
|
{
|
|
sibling = nsnull;
|
|
|
|
// Check for whether we should join a list that follows curNode.
|
|
// We do this if the next element is a list, and the list is of the
|
|
// same type (li/ol) as curNode was a part it.
|
|
mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling));
|
|
if (sibling && nsHTMLEditUtils::IsList(sibling))
|
|
{
|
|
nsAutoString curListTag, siblingListTag;
|
|
nsEditor::GetTagString(curParent, curListTag);
|
|
nsEditor::GetTagString(sibling, siblingListTag);
|
|
if (curListTag == siblingListTag)
|
|
{
|
|
res = mHTMLEditor->MoveNode(curNode, sibling, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Check for whether we should join a list that preceeds curNode.
|
|
// We do this if the previous element is a list, and the list is of
|
|
// the same type (li/ol) as curNode was a part of.
|
|
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
|
|
if (sibling && nsHTMLEditUtils::IsList(sibling))
|
|
{
|
|
nsAutoString curListTag, siblingListTag;
|
|
nsEditor::GetTagString(curParent, curListTag);
|
|
nsEditor::GetTagString(sibling, siblingListTag);
|
|
if (curListTag == siblingListTag)
|
|
{
|
|
res = mHTMLEditor->MoveNode(curNode, sibling, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
sibling = nsnull;
|
|
|
|
// check to see if curList is still appropriate. Which it is if
|
|
// curNode is still right after it in the same list.
|
|
if (curList)
|
|
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
|
|
|
|
if (!curList || (sibling && sibling != curList) )
|
|
{
|
|
nsAutoString listTag;
|
|
nsEditor::GetTagString(curParent,listTag);
|
|
ToLowerCase(listTag);
|
|
// create a new nested list of correct type
|
|
res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// curList is now the correct thing to put curNode in
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curList;
|
|
}
|
|
// tuck the node into the end of the active list
|
|
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// forget curQuote, if any
|
|
curQuote = nsnull;
|
|
}
|
|
|
|
else // not a list item, use blockquote?
|
|
{
|
|
// if we are inside a list item, we don't want to blockquote, we want
|
|
// to sublist the list item. We may have several nodes listed in the
|
|
// array of nodes to act on, that are in the same list item. Since
|
|
// we only want to indent that li once, we must keep track of the most
|
|
// recent indented list item, and not indent it if we find another node
|
|
// to act on that is still inside the same li.
|
|
nsCOMPtr<nsIDOMNode> listitem=IsInListItem(curNode);
|
|
if (listitem)
|
|
{
|
|
if (indentedLI == listitem) continue; // already indented this list item
|
|
res = nsEditor::GetNodeLocation(listitem, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// check to see if curList is still appropriate. Which it is if
|
|
// curNode is still right after it in the same list.
|
|
if (curList)
|
|
{
|
|
sibling = nsnull;
|
|
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
|
|
}
|
|
|
|
if (!curList || (sibling && sibling != curList) )
|
|
{
|
|
nsAutoString listTag;
|
|
nsEditor::GetTagString(curParent,listTag);
|
|
ToLowerCase(listTag);
|
|
// create a new nested list of correct type
|
|
res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
res = mHTMLEditor->MoveNode(listitem, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember we indented this li
|
|
indentedLI = listitem;
|
|
}
|
|
|
|
else
|
|
{
|
|
// need to make a blockquote to put things in if we haven't already,
|
|
// or if this node doesn't go in blockquote we used earlier.
|
|
// One reason it might not go in prio blockquote is if we are now
|
|
// in a different table cell.
|
|
if (curQuote)
|
|
{
|
|
PRBool bInDifTblElems;
|
|
res = InDifferentTableElements(curQuote, curNode, &bInDifTblElems);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bInDifTblElems)
|
|
curQuote = nsnull;
|
|
}
|
|
|
|
if (!curQuote)
|
|
{
|
|
res = SplitAsNeeded("eType, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curQuote;
|
|
// curQuote is now the correct thing to put curNode in
|
|
}
|
|
|
|
// tuck the node into the end of the active blockquote
|
|
res = mHTMLEditor->MoveNode(curNode, curQuote, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// forget curList, if any
|
|
curList = nsnull;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillOutdent(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> rememberedLeftBQ, rememberedRightBQ;
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
|
|
res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// some scoping for selection resetting - we may need to tweak it
|
|
{
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetNodesFromSelection(aSelection, kOutdent, arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Ok, now go through all the nodes and remove a level of blockquoting,
|
|
// or whatever is appropriate. Wohoo!
|
|
|
|
nsCOMPtr<nsIDOMNode> curBlockQuote, firstBQChild, lastBQChild;
|
|
PRBool curBlockQuoteIsIndentedWithCSS = PR_FALSE;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// is it a blockquote?
|
|
if (nsHTMLEditUtils::IsBlockquote(curNode))
|
|
{
|
|
// if it is a blockquote, remove it.
|
|
// So we need to finish up dealng with any curBlockQuote first.
|
|
if (curBlockQuote)
|
|
{
|
|
res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
|
|
curBlockQuoteIsIndentedWithCSS,
|
|
address_of(rememberedLeftBQ),
|
|
address_of(rememberedRightBQ));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0;
|
|
curBlockQuoteIsIndentedWithCSS = PR_FALSE;
|
|
}
|
|
res = mHTMLEditor->RemoveBlockContainer(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
// is it a list item?
|
|
if (nsHTMLEditUtils::IsListItem(curNode))
|
|
{
|
|
// if it is a list item, that means we are not outdenting whole list.
|
|
// So we need to finish up dealng with any curBlockQuote, and then
|
|
// pop this list item.
|
|
if (curBlockQuote)
|
|
{
|
|
res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
|
|
curBlockQuoteIsIndentedWithCSS,
|
|
address_of(rememberedLeftBQ),
|
|
address_of(rememberedRightBQ));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0;
|
|
curBlockQuoteIsIndentedWithCSS = PR_FALSE;
|
|
}
|
|
PRBool bOutOfList;
|
|
res = PopListItem(curNode, &bOutOfList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
continue;
|
|
}
|
|
// do we have a blockquote that we are already committed to removing?
|
|
if (curBlockQuote)
|
|
{
|
|
// if so, is this node a descendant?
|
|
if (nsEditorUtils::IsDescendantOf(curNode, curBlockQuote))
|
|
{
|
|
lastBQChild = curNode;
|
|
continue; // then we don't need to do anything different for this node
|
|
}
|
|
else
|
|
{
|
|
// otherwise, we have progressed beyond end of curBlockQuote,
|
|
// so lets handle it now. We need to remove the portion of
|
|
// curBlockQuote that contains [firstBQChild - lastBQChild].
|
|
res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
|
|
curBlockQuoteIsIndentedWithCSS,
|
|
address_of(rememberedLeftBQ),
|
|
address_of(rememberedRightBQ));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0;
|
|
curBlockQuoteIsIndentedWithCSS = PR_FALSE;
|
|
// fall out and handle curNode
|
|
}
|
|
}
|
|
|
|
// are we inside a blockquote?
|
|
nsCOMPtr<nsIDOMNode> n = curNode;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
curBlockQuoteIsIndentedWithCSS = PR_FALSE;
|
|
// keep looking up the hierarchy as long as we don't hit the body or a table element
|
|
// (other than an entire table)
|
|
while (!nsTextEditUtils::IsBody(n) &&
|
|
(nsHTMLEditUtils::IsTable(n) || !nsHTMLEditUtils::IsTableElement(n)))
|
|
{
|
|
n->GetParentNode(getter_AddRefs(tmp));
|
|
if (!tmp) {
|
|
break;
|
|
}
|
|
n = tmp;
|
|
if (nsHTMLEditUtils::IsBlockquote(n))
|
|
{
|
|
// if so, remember it, and remember first node we are taking out of it.
|
|
curBlockQuote = n;
|
|
firstBQChild = curNode;
|
|
lastBQChild = curNode;
|
|
break;
|
|
}
|
|
else if (useCSS)
|
|
{
|
|
nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode);
|
|
nsAutoString value;
|
|
mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(n, marginProperty, value);
|
|
float f;
|
|
nsCOMPtr<nsIAtom> unit;
|
|
mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit));
|
|
if (f > 0)
|
|
{
|
|
curBlockQuote = n;
|
|
firstBQChild = curNode;
|
|
lastBQChild = curNode;
|
|
curBlockQuoteIsIndentedWithCSS = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!curBlockQuote)
|
|
{
|
|
// could not find an enclosing blockquote for this node. handle list cases.
|
|
if (nsHTMLEditUtils::IsList(curParent)) // move node out of list
|
|
{
|
|
if (nsHTMLEditUtils::IsList(curNode)) // just unwrap this sublist
|
|
{
|
|
res = mHTMLEditor->RemoveBlockContainer(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// handled list item case above
|
|
}
|
|
else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, but parent is non-list: move list items out
|
|
{
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
curNode->GetLastChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
if (nsHTMLEditUtils::IsListItem(child))
|
|
{
|
|
PRBool bOutOfList;
|
|
res = PopListItem(child, &bOutOfList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (nsHTMLEditUtils::IsList(child))
|
|
{
|
|
// We have an embedded list, so move it out from under the
|
|
// parent list. Be sure to put it after the parent list
|
|
// because this loop iterates backwards through the parent's
|
|
// list of children.
|
|
|
|
res = mHTMLEditor->MoveNode(child, curParent, offset + 1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// delete any non- list items for now
|
|
res = mHTMLEditor->DeleteNode(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
curNode->GetLastChild(getter_AddRefs(child));
|
|
}
|
|
// delete the now-empty list
|
|
res = mHTMLEditor->RemoveBlockContainer(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (useCSS) {
|
|
nsCOMPtr<nsIDOMElement> element;
|
|
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(curNode);
|
|
if (textNode) {
|
|
// We want to outdent the parent of text nodes
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
textNode->GetParentNode(getter_AddRefs(parent));
|
|
element = do_QueryInterface(parent);
|
|
} else {
|
|
element = do_QueryInterface(curNode);
|
|
}
|
|
if (element) {
|
|
RelativeChangeIndentationOfElementNode(element, -1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (curBlockQuote)
|
|
{
|
|
// we have a blockquote we haven't finished handling
|
|
res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
|
|
curBlockQuoteIsIndentedWithCSS,
|
|
address_of(rememberedLeftBQ),
|
|
address_of(rememberedRightBQ));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
// make sure selection didn't stick to last piece of content in old bq
|
|
// (only a problem for collapsed selections)
|
|
if (rememberedLeftBQ || rememberedRightBQ)
|
|
{
|
|
PRBool bCollapsed;
|
|
res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
if (bCollapsed)
|
|
{
|
|
// push selection past end of rememberedLeftBQ
|
|
nsCOMPtr<nsIDOMNode> sNode;
|
|
PRInt32 sOffset;
|
|
mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset);
|
|
if (rememberedLeftBQ &&
|
|
((sNode == rememberedLeftBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedLeftBQ)))
|
|
{
|
|
// selection is inside rememberedLeftBQ - push it past it.
|
|
nsEditor::GetNodeLocation(rememberedLeftBQ, address_of(sNode), &sOffset);
|
|
sOffset++;
|
|
aSelection->Collapse(sNode, sOffset);
|
|
}
|
|
// and pull selection before beginning of rememberedRightBQ
|
|
mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset);
|
|
if (rememberedRightBQ &&
|
|
((sNode == rememberedRightBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedRightBQ)))
|
|
{
|
|
// selection is inside rememberedRightBQ - push it before it.
|
|
nsEditor::GetNodeLocation(rememberedRightBQ, address_of(sNode), &sOffset);
|
|
aSelection->Collapse(sNode, sOffset);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// RemovePartOfBlock: split aBlock and move aStartChild to aEndChild out
|
|
// of aBlock. return left side of block (if any) in
|
|
// aLeftNode. return right side of block (if any) in
|
|
// aRightNode.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::RemovePartOfBlock(nsIDOMNode *aBlock,
|
|
nsIDOMNode *aStartChild,
|
|
nsIDOMNode *aEndChild,
|
|
nsCOMPtr<nsIDOMNode> *aLeftNode,
|
|
nsCOMPtr<nsIDOMNode> *aRightNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> middleNode;
|
|
nsresult res = SplitBlock(aBlock, aStartChild, aEndChild,
|
|
aLeftNode, aRightNode,
|
|
address_of(middleNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// get rid of part of blockquote we are outdenting
|
|
|
|
return mHTMLEditor->RemoveBlockContainer(aBlock);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::SplitBlock(nsIDOMNode *aBlock,
|
|
nsIDOMNode *aStartChild,
|
|
nsIDOMNode *aEndChild,
|
|
nsCOMPtr<nsIDOMNode> *aLeftNode,
|
|
nsCOMPtr<nsIDOMNode> *aRightNode,
|
|
nsCOMPtr<nsIDOMNode> *aMiddleNode)
|
|
{
|
|
NS_ENSURE_TRUE(aBlock && aStartChild && aEndChild, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNode> startParent, endParent, leftNode, rightNode;
|
|
PRInt32 startOffset, endOffset, offset;
|
|
nsresult res;
|
|
|
|
// get split point location
|
|
res = nsEditor::GetNodeLocation(aStartChild, address_of(startParent), &startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// do the splits!
|
|
res = mHTMLEditor->SplitNodeDeep(aBlock, startParent, startOffset, &offset,
|
|
PR_TRUE, address_of(leftNode), address_of(rightNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (rightNode) aBlock = rightNode;
|
|
|
|
// remember left portion of block if caller requested
|
|
if (aLeftNode)
|
|
*aLeftNode = leftNode;
|
|
|
|
// get split point location
|
|
res = nsEditor::GetNodeLocation(aEndChild, address_of(endParent), &endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
endOffset++; // want to be after lastBQChild
|
|
|
|
// do the splits!
|
|
res = mHTMLEditor->SplitNodeDeep(aBlock, endParent, endOffset, &offset,
|
|
PR_TRUE, address_of(leftNode), address_of(rightNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (leftNode) aBlock = leftNode;
|
|
|
|
// remember right portion of block if caller requested
|
|
if (aRightNode)
|
|
*aRightNode = rightNode;
|
|
|
|
if (aMiddleNode)
|
|
*aMiddleNode = aBlock;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::OutdentPartOfBlock(nsIDOMNode *aBlock,
|
|
nsIDOMNode *aStartChild,
|
|
nsIDOMNode *aEndChild,
|
|
PRBool aIsBlockIndentedWithCSS,
|
|
nsCOMPtr<nsIDOMNode> *aLeftNode,
|
|
nsCOMPtr<nsIDOMNode> *aRightNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> middleNode;
|
|
nsresult res = SplitBlock(aBlock, aStartChild, aEndChild,
|
|
aLeftNode,
|
|
aRightNode,
|
|
address_of(middleNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (aIsBlockIndentedWithCSS)
|
|
res = RelativeChangeIndentationOfElementNode(middleNode, -1);
|
|
else
|
|
res = mHTMLEditor->RemoveBlockContainer(middleNode);
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ConvertListType: convert list type and list item type.
|
|
//
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::ConvertListType(nsIDOMNode *aList,
|
|
nsCOMPtr<nsIDOMNode> *outList,
|
|
const nsAString& aListType,
|
|
const nsAString& aItemType)
|
|
{
|
|
NS_ENSURE_TRUE(aList && outList, NS_ERROR_NULL_POINTER);
|
|
*outList = aList; // we might not need to change the list
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> child, temp;
|
|
aList->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
if (nsHTMLEditUtils::IsListItem(child) && !nsEditor::NodeIsTypeString(child, aItemType))
|
|
{
|
|
res = mHTMLEditor->ReplaceContainer(child, address_of(temp), aItemType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
child = temp;
|
|
}
|
|
else if (nsHTMLEditUtils::IsList(child) && !nsEditor::NodeIsTypeString(child, aListType))
|
|
{
|
|
res = ConvertListType(child, address_of(temp), aListType, aItemType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
child = temp;
|
|
}
|
|
child->GetNextSibling(getter_AddRefs(temp));
|
|
child = temp;
|
|
}
|
|
if (!nsEditor::NodeIsTypeString(aList, aListType))
|
|
{
|
|
res = mHTMLEditor->ReplaceContainer(aList, outList, aListType);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CreateStyleForInsertText: take care of clearing and setting appropriate
|
|
// style nodes for text insertion.
|
|
//
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, nsIDOMDocument *aDoc)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection && aDoc, NS_ERROR_NULL_POINTER);
|
|
NS_ENSURE_TRUE(mHTMLEditor->mTypeInState, NS_ERROR_NULL_POINTER);
|
|
|
|
PRBool weDidSometing = PR_FALSE;
|
|
nsCOMPtr<nsIDOMNode> node, tmp;
|
|
PRInt32 offset;
|
|
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoPtr<PropItem> item;
|
|
|
|
// if we deleted selection then also for cached styles
|
|
if (mDidDeleteSelection &&
|
|
((mTheAction == nsEditor::kOpInsertText ) ||
|
|
(mTheAction == nsEditor::kOpInsertIMEText) ||
|
|
(mTheAction == nsEditor::kOpInsertBreak) ||
|
|
(mTheAction == nsEditor::kOpDeleteSelection)))
|
|
{
|
|
res = ReapplyCachedStyles();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// either way we clear the cached styles array
|
|
res = ClearCachedStyles();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// next examine our present style and make sure default styles are either present or
|
|
// explicitly overridden. If neither, add the default style to the TypeInState
|
|
PRInt32 j, defcon = mHTMLEditor->mDefaultStyles.Length();
|
|
for (j=0; j<defcon; j++)
|
|
{
|
|
PropItem *propItem = mHTMLEditor->mDefaultStyles[j];
|
|
NS_ENSURE_TRUE(propItem, NS_ERROR_NULL_POINTER);
|
|
PRBool bFirst, bAny, bAll;
|
|
|
|
// GetInlineProperty also examine TypeInState. The only gotcha here is that a cleared
|
|
// property looks like an unset property. For now I'm assuming that's not a problem:
|
|
// that default styles will always be multivalue styles (like font face or size) where
|
|
// clearing the style means we want to go back to the default. If we ever wanted a
|
|
// "toggle" style like bold for a default, though, I'll have to add code to detect the
|
|
// difference between unset and explicitly cleared, else user would never be able to
|
|
// unbold, for instance.
|
|
nsAutoString curValue;
|
|
res = mHTMLEditor->GetInlinePropertyBase(propItem->tag, &(propItem->attr), nsnull,
|
|
&bFirst, &bAny, &bAll, &curValue, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (!bAny) // no style set for this prop/attr
|
|
{
|
|
mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr, propItem->value);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMElement> rootElement;
|
|
res = aDoc->GetDocumentElement(getter_AddRefs(rootElement));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// process clearing any styles first
|
|
mHTMLEditor->mTypeInState->TakeClearProperty(getter_Transfers(item));
|
|
while (item && node != rootElement)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> leftNode, rightNode, secondSplitParent, newSelParent, savedBR;
|
|
res = mHTMLEditor->SplitStyleAbovePoint(address_of(node), &offset, item->tag, &item->attr, address_of(leftNode), address_of(rightNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRBool bIsEmptyNode;
|
|
if (leftNode)
|
|
{
|
|
mHTMLEditor->IsEmptyNode(leftNode, &bIsEmptyNode, PR_FALSE, PR_TRUE);
|
|
if (bIsEmptyNode)
|
|
{
|
|
// delete leftNode if it became empty
|
|
res = mEditor->DeleteNode(leftNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
if (rightNode)
|
|
{
|
|
secondSplitParent = mHTMLEditor->GetLeftmostChild(rightNode);
|
|
// don't try to split non-containers (br's, images, hr's, etc)
|
|
if (!secondSplitParent) secondSplitParent = rightNode;
|
|
if (!mHTMLEditor->IsContainer(secondSplitParent))
|
|
{
|
|
if (nsTextEditUtils::IsBreak(secondSplitParent))
|
|
savedBR = secondSplitParent;
|
|
|
|
secondSplitParent->GetParentNode(getter_AddRefs(tmp));
|
|
secondSplitParent = tmp;
|
|
}
|
|
offset = 0;
|
|
res = mHTMLEditor->SplitStyleAbovePoint(address_of(secondSplitParent), &offset, item->tag, &(item->attr), address_of(leftNode), address_of(rightNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// should be impossible to not get a new leftnode here
|
|
NS_ENSURE_TRUE(leftNode, NS_ERROR_FAILURE);
|
|
newSelParent = mHTMLEditor->GetLeftmostChild(leftNode);
|
|
if (!newSelParent) newSelParent = leftNode;
|
|
// if rightNode starts with a br, suck it out of right node and into leftNode.
|
|
// This is so we you don't revert back to the previous style if you happen to click at the end of a line.
|
|
if (savedBR)
|
|
{
|
|
res = mEditor->MoveNode(savedBR, newSelParent, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
mHTMLEditor->IsEmptyNode(rightNode, &bIsEmptyNode, PR_FALSE, PR_TRUE);
|
|
if (bIsEmptyNode)
|
|
{
|
|
// delete rightNode if it became empty
|
|
res = mEditor->DeleteNode(rightNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// remove the style on this new hierarchy
|
|
PRInt32 newSelOffset = 0;
|
|
{
|
|
// track the point at the new hierarchy.
|
|
// This is so we can know where to put the selection after we call
|
|
// RemoveStyleInside(). RemoveStyleInside() could remove any and all of those nodes,
|
|
// so I have to use the range tracking system to find the right spot to put selection.
|
|
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(newSelParent), &newSelOffset);
|
|
res = mHTMLEditor->RemoveStyleInside(leftNode, item->tag, &(item->attr));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// reset our node offset values to the resulting new sel point
|
|
node = newSelParent;
|
|
offset = newSelOffset;
|
|
}
|
|
mHTMLEditor->mTypeInState->TakeClearProperty(getter_Transfers(item));
|
|
weDidSometing = PR_TRUE;
|
|
}
|
|
|
|
// then process setting any styles
|
|
PRInt32 relFontSize;
|
|
|
|
res = mHTMLEditor->mTypeInState->TakeRelativeFontSize(&relFontSize);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->mTypeInState->TakeSetProperty(getter_Transfers(item));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (item || relFontSize) // we have at least one style to add; make a
|
|
{ // new text node to insert style nodes above.
|
|
if (mHTMLEditor->IsTextNode(node))
|
|
{
|
|
// if we are in a text node, split it
|
|
res = mHTMLEditor->SplitNodeDeep(node, node, offset, &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
node->GetParentNode(getter_AddRefs(tmp));
|
|
node = tmp;
|
|
}
|
|
if (!mHTMLEditor->IsContainer(node))
|
|
{
|
|
return NS_OK;
|
|
}
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
nsCOMPtr<nsIDOMText> nodeAsText;
|
|
res = aDoc->CreateTextNode(EmptyString(), getter_AddRefs(nodeAsText));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(nodeAsText, NS_ERROR_NULL_POINTER);
|
|
newNode = do_QueryInterface(nodeAsText);
|
|
res = mHTMLEditor->InsertNode(newNode, node, offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
node = newNode;
|
|
offset = 0;
|
|
weDidSometing = PR_TRUE;
|
|
|
|
if (relFontSize)
|
|
{
|
|
PRInt32 j, dir;
|
|
// dir indicated bigger versus smaller. 1 = bigger, -1 = smaller
|
|
if (relFontSize > 0) dir=1;
|
|
else dir = -1;
|
|
for (j=0; j<abs(relFontSize); j++)
|
|
{
|
|
res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText, 0, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
while (item)
|
|
{
|
|
res = mHTMLEditor->SetInlinePropertyOnNode(node, item->tag, &item->attr, &item->value);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
mHTMLEditor->mTypeInState->TakeSetProperty(getter_Transfers(item));
|
|
}
|
|
}
|
|
if (weDidSometing)
|
|
return aSelection->Collapse(node, offset);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsEmptyBlock: figure out if aNode is (or is inside) an empty block.
|
|
// A block can have children and still be considered empty,
|
|
// if the children are empty or non-editable.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode,
|
|
PRBool *outIsEmptyBlock,
|
|
PRBool aMozBRDoesntCount,
|
|
PRBool aListItemsNotEmpty)
|
|
{
|
|
NS_ENSURE_TRUE(aNode && outIsEmptyBlock, NS_ERROR_NULL_POINTER);
|
|
*outIsEmptyBlock = PR_TRUE;
|
|
|
|
// nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> nodeToTest;
|
|
if (IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode);
|
|
// else nsCOMPtr<nsIDOMElement> block;
|
|
// looks like I forgot to finish this. Wonder what I was going to do?
|
|
|
|
NS_ENSURE_TRUE(nodeToTest, NS_ERROR_NULL_POINTER);
|
|
return mHTMLEditor->IsEmptyNode(nodeToTest, outIsEmptyBlock,
|
|
aMozBRDoesntCount, aListItemsNotEmpty);
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillAlign(nsISelection *aSelection,
|
|
const nsAString *alignType,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_FALSE;
|
|
|
|
res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
*aHandled = PR_TRUE;
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetNodesFromSelection(aSelection, kAlign, arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if we don't have any nodes, or we have only a single br, then we are
|
|
// creating an empty alignment div. We have to do some different things for these.
|
|
PRBool emptyDiv = PR_FALSE;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
if (!listCount) emptyDiv = PR_TRUE;
|
|
if (listCount == 1)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> theNode = arrayOfNodes[0];
|
|
|
|
if (nsHTMLEditUtils::SupportsAlignAttr(theNode))
|
|
{
|
|
// the node is a table element, an horiz rule, a paragraph, a div
|
|
// or a section header; in HTML 4, it can directly carry the ALIGN
|
|
// attribute and we don't need to make a div! If we are in CSS mode,
|
|
// all the work is done in AlignBlock
|
|
nsCOMPtr<nsIDOMElement> theElem = do_QueryInterface(theNode);
|
|
res = AlignBlock(theElem, alignType, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (nsTextEditUtils::IsBreak(theNode))
|
|
{
|
|
// The special case emptyDiv code (below) that consumes BRs can
|
|
// cause tables to split if the start node of the selection is
|
|
// not in a table cell or caption, for example parent is a <tr>.
|
|
// Avoid this unnecessary splitting if possible by leaving emptyDiv
|
|
// FALSE so that we fall through to the normal case alignment code.
|
|
//
|
|
// XXX: It seems a little error prone for the emptyDiv special
|
|
// case code to assume that the start node of the selection
|
|
// is the parent of the single node in the arrayOfNodes, as
|
|
// the paragraph above points out. Do we rely on the selection
|
|
// start node because of the fact that arrayOfNodes can be empty?
|
|
// We should probably revisit this issue. - kin
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
|
|
if (!nsHTMLEditUtils::IsTableElement(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent))
|
|
emptyDiv = PR_TRUE;
|
|
}
|
|
}
|
|
if (emptyDiv)
|
|
{
|
|
PRInt32 offset;
|
|
nsCOMPtr<nsIDOMNode> brNode, parent, theDiv, sib;
|
|
NS_NAMED_LITERAL_STRING(divType, "div");
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = SplitAsNeeded(&divType, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// consume a trailing br, if any. This is to keep an alignment from
|
|
// creating extra lines, if possible.
|
|
res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (brNode && nsTextEditUtils::IsBreak(brNode))
|
|
{
|
|
// making use of html structure... if next node after where
|
|
// we are putting our div is not a block, then the br we
|
|
// found is in same block we are, so its safe to consume it.
|
|
res = mHTMLEditor->GetNextHTMLSibling(parent, offset, address_of(sib));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!IsBlockNode(sib))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(brNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = theDiv;
|
|
// set up the alignment on the div, using HTML or CSS
|
|
nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(theDiv);
|
|
res = AlignBlock(divElem, alignType, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
*aHandled = PR_TRUE;
|
|
// put in a moz-br so that it won't get deleted
|
|
res = CreateMozBR(theDiv, 0, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = aSelection->Collapse(theDiv, 0);
|
|
selectionResetter.Abort(); // don't reset our selection in this case.
|
|
return res;
|
|
}
|
|
|
|
// Next we detect all the transitions in the array, where a transition
|
|
// means that adjacent nodes in the array don't have the same parent.
|
|
|
|
nsTArray<PRPackedBool> transitionList;
|
|
res = MakeTransitionList(arrayOfNodes, transitionList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Ok, now go through all the nodes and give them an align attrib or put them in a div,
|
|
// or whatever is appropriate. Wohoo!
|
|
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
nsCOMPtr<nsIDOMNode> curDiv;
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// the node is a table element, an horiz rule, a paragraph, a div
|
|
// or a section header; in HTML 4, it can directly carry the ALIGN
|
|
// attribute and we don't need to nest it, just set the alignment.
|
|
// In CSS, assign the corresponding CSS styles in AlignBlock
|
|
if (nsHTMLEditUtils::SupportsAlignAttr(curNode))
|
|
{
|
|
nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode);
|
|
res = AlignBlock(curElem, alignType, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// clear out curDiv so that we don't put nodes after this one into it
|
|
curDiv = 0;
|
|
continue;
|
|
}
|
|
|
|
// Skip insignificant formatting text nodes to prevent
|
|
// unnecessary structure splitting!
|
|
if (nsEditor::IsTextNode(curNode) &&
|
|
((nsHTMLEditUtils::IsTableElement(curParent) && !nsHTMLEditUtils::IsTableCellOrCaption(curParent)) ||
|
|
nsHTMLEditUtils::IsList(curParent)))
|
|
continue;
|
|
|
|
// if it's a list item, or a list
|
|
// inside a list, forget any "current" div, and instead put divs inside
|
|
// the appropriate block (td, li, etc)
|
|
if ( nsHTMLEditUtils::IsListItem(curNode)
|
|
|| nsHTMLEditUtils::IsList(curNode))
|
|
{
|
|
res = RemoveAlignment(curNode, *alignType, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (useCSS) {
|
|
nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode);
|
|
NS_NAMED_LITERAL_STRING(attrName, "align");
|
|
PRInt32 count;
|
|
mHTMLEditor->mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(curNode, nsnull,
|
|
&attrName, alignType,
|
|
&count, PR_FALSE);
|
|
curDiv = 0;
|
|
continue;
|
|
}
|
|
else if (nsHTMLEditUtils::IsList(curParent)) {
|
|
// if we don't use CSS, add a contraint to list element : they have
|
|
// to be inside another list, ie >= second level of nesting
|
|
res = AlignInnerBlocks(curNode, alignType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curDiv = 0;
|
|
continue;
|
|
}
|
|
// clear out curDiv so that we don't put nodes after this one into it
|
|
}
|
|
|
|
// need to make a div to put things in if we haven't already,
|
|
// or if this node doesn't go in div we used earlier.
|
|
if (!curDiv || transitionList[i])
|
|
{
|
|
NS_NAMED_LITERAL_STRING(divType, "div");
|
|
res = SplitAsNeeded(&divType, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curDiv;
|
|
// set up the alignment on the div
|
|
nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curDiv);
|
|
res = AlignBlock(divElem, alignType, PR_TRUE);
|
|
// nsAutoString attr(NS_LITERAL_STRING("align"));
|
|
// res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
|
|
// NS_ENSURE_SUCCESS(res, res);
|
|
// curDiv is now the correct thing to put curNode in
|
|
}
|
|
|
|
// tuck the node into the end of the active div
|
|
res = mHTMLEditor->MoveNode(curNode, curDiv, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// AlignInnerBlocks: align inside table cells or list items
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsAString *alignType)
|
|
{
|
|
NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER);
|
|
nsresult res;
|
|
|
|
// gather list of table cells or list items
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
nsTableCellAndListItemFunctor functor;
|
|
nsDOMIterator iter;
|
|
res = iter.Init(aNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = iter.AppendList(functor, arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// now that we have the list, align their contents as requested
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
PRInt32 j;
|
|
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
nsIDOMNode* node = arrayOfNodes[0];
|
|
res = AlignBlockContents(node, alignType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
arrayOfNodes.RemoveObjectAt(0);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// AlignBlockContents: align contents of a block element
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsAString *alignType)
|
|
{
|
|
NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER);
|
|
nsresult res;
|
|
nsCOMPtr <nsIDOMNode> firstChild, lastChild, divNode;
|
|
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
|
|
res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(firstChild));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_NAMED_LITERAL_STRING(attr, "align");
|
|
if (!firstChild)
|
|
{
|
|
// this cell has no content, nothing to align
|
|
}
|
|
else if ((firstChild==lastChild) && nsHTMLEditUtils::IsDiv(firstChild))
|
|
{
|
|
// the cell already has a div containing all of it's content: just
|
|
// act on this div.
|
|
nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(firstChild);
|
|
if (useCSS) {
|
|
res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, PR_FALSE);
|
|
}
|
|
else {
|
|
res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// else we need to put in a div, set the alignment, and toss in all the children
|
|
res = mHTMLEditor->CreateNode(NS_LITERAL_STRING("div"), aNode, 0, getter_AddRefs(divNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// set up the alignment on the div
|
|
nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(divNode);
|
|
if (useCSS) {
|
|
res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, PR_FALSE);
|
|
}
|
|
else {
|
|
res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// tuck the children into the end of the active div
|
|
while (lastChild && (lastChild != divNode))
|
|
{
|
|
res = mHTMLEditor->MoveNode(lastChild, divNode, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CheckForEmptyBlock: Called by WillDeleteSelection to detect and handle
|
|
// case of deleting from inside an empty block.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::CheckForEmptyBlock(nsIDOMNode *aStartNode,
|
|
nsIDOMNode *aBodyNode,
|
|
nsISelection *aSelection,
|
|
PRBool *aHandled)
|
|
{
|
|
// If the editing host is an inline element, bail out early.
|
|
if (IsInlineNode(aBodyNode)) {
|
|
return NS_OK;
|
|
}
|
|
// if we are inside an empty block, delete it.
|
|
// Note: do NOT delete table elements this way.
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> block, emptyBlock;
|
|
if (IsBlockNode(aStartNode))
|
|
block = aStartNode;
|
|
else
|
|
block = mHTMLEditor->GetBlockNodeParent(aStartNode);
|
|
PRBool bIsEmptyNode;
|
|
if (block != aBodyNode) // efficiency hack. avoiding IsEmptyNode() call when in body
|
|
{
|
|
res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
while (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(block) && (block != aBodyNode))
|
|
{
|
|
emptyBlock = block;
|
|
block = mHTMLEditor->GetBlockNodeParent(emptyBlock);
|
|
res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIContent> emptyContent = do_QueryInterface(emptyBlock);
|
|
if (emptyBlock && emptyContent->IsEditable())
|
|
{
|
|
nsCOMPtr<nsIDOMNode> blockParent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(emptyBlock, address_of(blockParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(blockParent && offset >= 0, NS_ERROR_FAILURE);
|
|
|
|
if (nsHTMLEditUtils::IsListItem(emptyBlock))
|
|
{
|
|
// are we the first list item in the list?
|
|
PRBool bIsFirst;
|
|
res = mHTMLEditor->IsFirstEditableChild(emptyBlock, &bIsFirst);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bIsFirst)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> listParent;
|
|
PRInt32 listOffset;
|
|
res = nsEditor::GetNodeLocation(blockParent, address_of(listParent), &listOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(listParent && listOffset >= 0, NS_ERROR_FAILURE);
|
|
// if we are a sublist, skip the br creation
|
|
if (!nsHTMLEditUtils::IsList(listParent))
|
|
{
|
|
// create a br before list
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->CreateBR(listParent, listOffset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// adjust selection to be right before it
|
|
res = aSelection->Collapse(listParent, listOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// else just let selection perculate up. We'll adjust it in AfterEdit()
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// adjust selection to be right after it
|
|
res = aSelection->Collapse(blockParent, offset+1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
res = mHTMLEditor->DeleteNode(emptyBlock);
|
|
*aHandled = PR_TRUE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::CheckForInvisibleBR(nsIDOMNode *aBlock,
|
|
BRLocation aWhere,
|
|
nsCOMPtr<nsIDOMNode> *outBRNode,
|
|
PRInt32 aOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aBlock && outBRNode, NS_ERROR_NULL_POINTER);
|
|
*outBRNode = nsnull;
|
|
|
|
nsCOMPtr<nsIDOMNode> testNode;
|
|
PRInt32 testOffset = 0;
|
|
PRBool runTest = PR_FALSE;
|
|
|
|
if (aWhere == kBlockEnd)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> rightmostNode;
|
|
rightmostNode = mHTMLEditor->GetRightmostChild(aBlock, PR_TRUE); // no block crossing
|
|
|
|
if (rightmostNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> nodeParent;
|
|
PRInt32 nodeOffset;
|
|
|
|
if (NS_SUCCEEDED(nsEditor::GetNodeLocation(rightmostNode,
|
|
address_of(nodeParent),
|
|
&nodeOffset)))
|
|
{
|
|
runTest = PR_TRUE;
|
|
testNode = nodeParent;
|
|
// use offset + 1, because we want the last node included in our evaluation
|
|
testOffset = nodeOffset + 1;
|
|
}
|
|
}
|
|
}
|
|
else if (aOffset)
|
|
{
|
|
runTest = PR_TRUE;
|
|
testNode = aBlock;
|
|
// we'll check everything to the left of the input position
|
|
testOffset = aOffset;
|
|
}
|
|
|
|
if (runTest)
|
|
{
|
|
nsWSRunObject wsTester(mHTMLEditor, testNode, testOffset);
|
|
if (nsWSRunObject::eBreak == wsTester.mStartReason)
|
|
{
|
|
*outBRNode = wsTester.mStartReasonNode;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetInnerContent: aList and aTbl allow the caller to specify what kind
|
|
// of content to "look inside". If aTbl is true, look inside
|
|
// any table content, and insert the inner content into the
|
|
// supplied issupportsarray at offset aIndex.
|
|
// Similarly with aList and list content.
|
|
// aIndex is updated to point past inserted elements.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetInnerContent(nsIDOMNode *aNode, nsCOMArray<nsIDOMNode> &outArrayOfNodes,
|
|
PRInt32 *aIndex, PRBool aList, PRBool aTbl)
|
|
{
|
|
NS_ENSURE_TRUE(aNode && aIndex, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
|
|
nsresult res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(node));
|
|
while (NS_SUCCEEDED(res) && node)
|
|
{
|
|
if ( ( aList && (nsHTMLEditUtils::IsList(node) ||
|
|
nsHTMLEditUtils::IsListItem(node) ) )
|
|
|| ( aTbl && nsHTMLEditUtils::IsTableElement(node) ) )
|
|
{
|
|
res = GetInnerContent(node, outArrayOfNodes, aIndex, aList, aTbl);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
outArrayOfNodes.InsertObjectAt(node, *aIndex);
|
|
(*aIndex)++;
|
|
}
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
res = node->GetNextSibling(getter_AddRefs(tmp));
|
|
node = tmp;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ExpandSelectionForDeletion: this promotes our selection to include blocks
|
|
// that have all their children selected.
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::ExpandSelectionForDeletion(nsISelection *aSelection)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
|
|
|
|
// don't need to touch collapsed selections
|
|
PRBool bCollapsed;
|
|
nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bCollapsed) return res;
|
|
|
|
PRInt32 rangeCount;
|
|
res = aSelection->GetRangeCount(&rangeCount);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// we don't need to mess with cell selections, and we assume multirange selections are those.
|
|
if (rangeCount != 1) return NS_OK;
|
|
|
|
// find current sel start and end
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = aSelection->GetRangeAt(0, getter_AddRefs(range));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsIDOMNode> selStartNode, selEndNode, selCommon;
|
|
PRInt32 selStartOffset, selEndOffset;
|
|
|
|
res = range->GetStartContainer(getter_AddRefs(selStartNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->GetStartOffset(&selStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->GetEndContainer(getter_AddRefs(selEndNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->GetEndOffset(&selEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// find current selection common block parent
|
|
res = range->GetCommonAncestorContainer(getter_AddRefs(selCommon));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!IsBlockNode(selCommon))
|
|
selCommon = nsHTMLEditor::GetBlockNodeParent(selCommon);
|
|
|
|
// set up for loops and cache our root element
|
|
PRBool stillLooking = PR_TRUE;
|
|
nsCOMPtr<nsIDOMNode> visNode, firstBRParent;
|
|
PRInt32 visOffset=0, firstBROffset=0;
|
|
PRInt16 wsType;
|
|
nsCOMPtr<nsIContent> rootContent = mHTMLEditor->GetActiveEditingHost();
|
|
nsCOMPtr<nsIDOMNode> rootElement = do_QueryInterface(rootContent);
|
|
NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
|
|
|
|
// find previous visible thingy before start of selection
|
|
if ((selStartNode!=selCommon) && (selStartNode!=rootElement))
|
|
{
|
|
while (stillLooking)
|
|
{
|
|
nsWSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset);
|
|
res = wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (wsType == nsWSRunObject::eThisBlock)
|
|
{
|
|
// we want to keep looking up. But stop if we are crossing table element
|
|
// boundaries, or if we hit the root.
|
|
if ( nsHTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) ||
|
|
(selCommon == wsObj.mStartReasonNode) ||
|
|
(rootElement == wsObj.mStartReasonNode) )
|
|
{
|
|
stillLooking = PR_FALSE;
|
|
}
|
|
else
|
|
{
|
|
nsEditor::GetNodeLocation(wsObj.mStartReasonNode, address_of(selStartNode), &selStartOffset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stillLooking = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
stillLooking = PR_TRUE;
|
|
// find next visible thingy after end of selection
|
|
if ((selEndNode!=selCommon) && (selEndNode!=rootElement))
|
|
{
|
|
while (stillLooking)
|
|
{
|
|
nsWSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset);
|
|
res = wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (wsType == nsWSRunObject::eBreak)
|
|
{
|
|
if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode))
|
|
{
|
|
stillLooking = PR_FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (!firstBRParent)
|
|
{
|
|
firstBRParent = selEndNode;
|
|
firstBROffset = selEndOffset;
|
|
}
|
|
nsEditor::GetNodeLocation(wsObj.mEndReasonNode, address_of(selEndNode), &selEndOffset);
|
|
++selEndOffset;
|
|
}
|
|
}
|
|
else if (wsType == nsWSRunObject::eThisBlock)
|
|
{
|
|
// we want to keep looking up. But stop if we are crossing table element
|
|
// boundaries, or if we hit the root.
|
|
if ( nsHTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) ||
|
|
(selCommon == wsObj.mEndReasonNode) ||
|
|
(rootElement == wsObj.mEndReasonNode) )
|
|
{
|
|
stillLooking = PR_FALSE;
|
|
}
|
|
else
|
|
{
|
|
nsEditor::GetNodeLocation(wsObj.mEndReasonNode, address_of(selEndNode), &selEndOffset);
|
|
++selEndOffset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stillLooking = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
// now set the selection to the new range
|
|
aSelection->Collapse(selStartNode, selStartOffset);
|
|
|
|
// expand selection endpoint only if we didnt pass a br,
|
|
// or if we really needed to pass that br (ie, it's block is now
|
|
// totally selected)
|
|
PRBool doEndExpansion = PR_TRUE;
|
|
if (firstBRParent)
|
|
{
|
|
// find block node containing br
|
|
nsCOMPtr<nsIDOMNode> brBlock = firstBRParent;
|
|
if (!IsBlockNode(brBlock))
|
|
brBlock = nsHTMLEditor::GetBlockNodeParent(brBlock);
|
|
PRBool nodeBefore=PR_FALSE, nodeAfter=PR_FALSE;
|
|
|
|
// create a range that represents expanded selection
|
|
nsCOMPtr<nsIDOMRange> range = do_CreateInstance("@mozilla.org/content/range;1");
|
|
NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
|
|
res = range->SetStart(selStartNode, selStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->SetEnd(selEndNode, selEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// check if block is entirely inside range
|
|
nsCOMPtr<nsIContent> brContentBlock = do_QueryInterface(brBlock);
|
|
res = mHTMLEditor->sRangeHelper->CompareNodeToRange(brContentBlock, range, &nodeBefore, &nodeAfter);
|
|
|
|
// if block isn't contained, forgo grabbing the br in the expanded selection
|
|
if (nodeBefore || nodeAfter)
|
|
doEndExpansion = PR_FALSE;
|
|
}
|
|
if (doEndExpansion)
|
|
{
|
|
res = aSelection->Extend(selEndNode, selEndOffset);
|
|
}
|
|
else
|
|
{
|
|
// only expand to just before br
|
|
res = aSelection->Extend(firstBRParent, firstBROffset);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#ifdef XXX_DEAD_CODE
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// AtStartOfBlock: is node/offset at the start of the editable material in this block?
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::AtStartOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
|
|
if (nodeAsText && aOffset) return PR_FALSE; // there are chars in front of us
|
|
|
|
nsCOMPtr<nsIDOMNode> priorNode;
|
|
nsresult res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(priorNode));
|
|
NS_ENSURE_SUCCESS(res, PR_TRUE);
|
|
NS_ENSURE_TRUE(priorNode, PR_TRUE);
|
|
nsCOMPtr<nsIDOMNode> blockParent = mHTMLEditor->GetBlockNodeParent(priorNode);
|
|
if (blockParent && (blockParent == aBlock)) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// AtEndOfBlock: is node/offset at the end of the editable material in this block?
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::AtEndOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
|
|
if (nodeAsText)
|
|
{
|
|
PRUint32 strLength;
|
|
nodeAsText->GetLength(&strLength);
|
|
if ((PRInt32)strLength > aOffset) return PR_FALSE; // there are chars in after us
|
|
}
|
|
nsCOMPtr<nsIDOMNode> nextNode;
|
|
nsresult res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nextNode));
|
|
NS_ENSURE_SUCCESS(res, PR_TRUE);
|
|
NS_ENSURE_TRUE(nextNode, PR_TRUE);
|
|
nsCOMPtr<nsIDOMNode> blockParent = mHTMLEditor->GetBlockNodeParent(nextNode);
|
|
if (blockParent && (blockParent == aBlock)) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// CreateMozDiv: makes a div with type = _moz
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::CreateMozDiv(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outDiv)
|
|
{
|
|
NS_ENSURE_TRUE(inParent && outDiv, NS_ERROR_NULL_POINTER);
|
|
nsAutoString divType= "div";
|
|
*outDiv = nsnull;
|
|
nsresult res = mHTMLEditor->CreateNode(divType, inParent, inOffset, getter_AddRefs(*outDiv));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// give it special moz attr
|
|
nsCOMPtr<nsIDOMElement> mozDivElem = do_QueryInterface(*outDiv);
|
|
res = mHTMLEditor->SetAttribute(mozDivElem, "type", "_moz");
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = AddTrailerBR(*outDiv);
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NormalizeSelection: tweak non-collapsed selections to be more "natural".
|
|
// Idea here is to adjust selection endpoint so that they do not cross
|
|
// breaks or block boundaries unless something editable beyond that boundary
|
|
// is also selected. This adjustment makes it much easier for the various
|
|
// block operations to determine what nodes to act on.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::NormalizeSelection(nsISelection *inSelection)
|
|
{
|
|
NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER);
|
|
|
|
// don't need to touch collapsed selections
|
|
PRBool bCollapsed;
|
|
nsresult res = inSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bCollapsed) return res;
|
|
|
|
PRInt32 rangeCount;
|
|
res = inSelection->GetRangeCount(&rangeCount);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// we don't need to mess with cell selections, and we assume multirange selections are those.
|
|
if (rangeCount != 1) return NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = inSelection->GetRangeAt(0, getter_AddRefs(range));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
PRInt32 startOffset, endOffset;
|
|
nsCOMPtr<nsIDOMNode> newStartNode, newEndNode;
|
|
PRInt32 newStartOffset, newEndOffset;
|
|
|
|
res = range->GetStartContainer(getter_AddRefs(startNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->GetStartOffset(&startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->GetEndContainer(getter_AddRefs(endNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->GetEndOffset(&endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// adjusted values default to original values
|
|
newStartNode = startNode;
|
|
newStartOffset = startOffset;
|
|
newEndNode = endNode;
|
|
newEndOffset = endOffset;
|
|
|
|
// some locals we need for whitespace code
|
|
nsCOMPtr<nsIDOMNode> someNode;
|
|
PRInt32 offset;
|
|
PRInt16 wsType;
|
|
|
|
// let the whitespace code do the heavy lifting
|
|
nsWSRunObject wsEndObj(mHTMLEditor, endNode, endOffset);
|
|
// is there any intervening visible whitespace? if so we can't push selection past that,
|
|
// it would visibly change maening of users selection
|
|
res = wsEndObj.PriorVisibleNode(endNode, endOffset, address_of(someNode), &offset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if ((wsType != nsWSRunObject::eText) && (wsType != nsWSRunObject::eNormalWS))
|
|
{
|
|
// eThisBlock and eOtherBlock conveniently distinquish cases
|
|
// of going "down" into a block and "up" out of a block.
|
|
if (wsEndObj.mStartReason == nsWSRunObject::eOtherBlock)
|
|
{
|
|
// endpoint is just after the close of a block.
|
|
nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode, PR_TRUE);
|
|
if (child)
|
|
{
|
|
res = nsEditor::GetNodeLocation(child, address_of(newEndNode), &newEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
++newEndOffset; // offset *after* child
|
|
}
|
|
// else block is empty - we can leave selection alone here, i think.
|
|
}
|
|
else if (wsEndObj.mStartReason == nsWSRunObject::eThisBlock)
|
|
{
|
|
// endpoint is just after start of this block
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
res = mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child));
|
|
if (child)
|
|
{
|
|
res = nsEditor::GetNodeLocation(child, address_of(newEndNode), &newEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
++newEndOffset; // offset *after* child
|
|
}
|
|
// else block is empty - we can leave selection alone here, i think.
|
|
}
|
|
else if (wsEndObj.mStartReason == nsWSRunObject::eBreak)
|
|
{
|
|
// endpoint is just after break. lets adjust it to before it.
|
|
res = nsEditor::GetNodeLocation(wsEndObj.mStartReasonNode, address_of(newEndNode), &newEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
|
|
// similar dealio for start of range
|
|
nsWSRunObject wsStartObj(mHTMLEditor, startNode, startOffset);
|
|
// is there any intervening visible whitespace? if so we can't push selection past that,
|
|
// it would visibly change maening of users selection
|
|
res = wsStartObj.NextVisibleNode(startNode, startOffset, address_of(someNode), &offset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if ((wsType != nsWSRunObject::eText) && (wsType != nsWSRunObject::eNormalWS))
|
|
{
|
|
// eThisBlock and eOtherBlock conveniently distinquish cases
|
|
// of going "down" into a block and "up" out of a block.
|
|
if (wsStartObj.mEndReason == nsWSRunObject::eOtherBlock)
|
|
{
|
|
// startpoint is just before the start of a block.
|
|
nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode, PR_TRUE);
|
|
if (child)
|
|
{
|
|
res = nsEditor::GetNodeLocation(child, address_of(newStartNode), &newStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// else block is empty - we can leave selection alone here, i think.
|
|
}
|
|
else if (wsStartObj.mEndReason == nsWSRunObject::eThisBlock)
|
|
{
|
|
// startpoint is just before end of this block
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
res = mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child));
|
|
if (child)
|
|
{
|
|
res = nsEditor::GetNodeLocation(child, address_of(newStartNode), &newStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// else block is empty - we can leave selection alone here, i think.
|
|
}
|
|
else if (wsStartObj.mEndReason == nsWSRunObject::eBreak)
|
|
{
|
|
// startpoint is just before a break. lets adjust it to after it.
|
|
res = nsEditor::GetNodeLocation(wsStartObj.mEndReasonNode, address_of(newStartNode), &newStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
++newStartOffset; // offset *after* break
|
|
}
|
|
}
|
|
|
|
// there is a demented possiblity we have to check for. We might have a very strange selection
|
|
// that is not collapsed and yet does not contain any editable content, and satisfies some of the
|
|
// above conditions that cause tweaking. In this case we don't want to tweak the selection into
|
|
// a block it was never in, etc. There are a variety of strategies one might use to try to
|
|
// detect these cases, but I think the most straightforward is to see if the adjusted locations
|
|
// "cross" the old values: ie, new end before old start, or new start after old end. If so
|
|
// then just leave things alone.
|
|
|
|
PRInt16 comp;
|
|
comp = mHTMLEditor->sRangeHelper->ComparePoints(startNode, startOffset, newEndNode, newEndOffset);
|
|
if (comp == 1) return NS_OK; // new end before old start
|
|
comp = mHTMLEditor->sRangeHelper->ComparePoints(newStartNode, newStartOffset, endNode, endOffset);
|
|
if (comp == 1) return NS_OK; // new start after old end
|
|
|
|
// otherwise set selection to new values.
|
|
inSelection->Collapse(newStartNode, newStartOffset);
|
|
inSelection->Extend(newEndNode, newEndOffset);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPromotedPoint: figure out where a start or end point for a block
|
|
// operation really is
|
|
nsresult
|
|
nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt32 aOffset,
|
|
PRInt32 actionID, nsCOMPtr<nsIDOMNode> *outNode, PRInt32 *outOffset)
|
|
{
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> nearNode, node = aNode;
|
|
nsCOMPtr<nsIDOMNode> parent = aNode;
|
|
PRInt32 pOffset, offset = aOffset;
|
|
|
|
// default values
|
|
*outNode = node;
|
|
*outOffset = offset;
|
|
|
|
// we do one thing for text actions, something else entirely for other actions
|
|
if (actionID == nsEditor::kOpInsertText ||
|
|
actionID == nsEditor::kOpInsertIMEText ||
|
|
actionID == nsEditor::kOpInsertBreak ||
|
|
actionID == nsEditor::kOpDeleteText)
|
|
{
|
|
PRBool isSpace, isNBSP;
|
|
nsCOMPtr<nsIDOMNode> temp;
|
|
// for text actions, we want to look backwards (or forwards, as appropriate)
|
|
// for additional whitespace or nbsp's. We may have to act on these later even though
|
|
// they are outside of the initial selection. Even if they are in another node!
|
|
if (aWhere == kStart)
|
|
{
|
|
do
|
|
{
|
|
PRInt32 prevOffset;
|
|
res = mHTMLEditor->IsPrevCharWhitespace(node, offset, &isSpace, &isNBSP, address_of(temp), &prevOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (isSpace || isNBSP) {
|
|
node = temp;
|
|
offset = prevOffset;
|
|
} else {
|
|
break;
|
|
}
|
|
} while (node);
|
|
|
|
*outNode = node;
|
|
*outOffset = offset;
|
|
}
|
|
else if (aWhere == kEnd)
|
|
{
|
|
do
|
|
{
|
|
PRInt32 nextOffset;
|
|
res = mHTMLEditor->IsNextCharWhitespace(node, offset, &isSpace, &isNBSP, address_of(temp), &nextOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (isSpace || isNBSP) {
|
|
node = temp;
|
|
offset = nextOffset;
|
|
} else {
|
|
break;
|
|
}
|
|
} while (node);
|
|
|
|
*outNode = node;
|
|
*outOffset = offset;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// else not a text section. In this case we want to see if we should
|
|
// grab any adjacent inline nodes and/or parents and other ancestors
|
|
if (aWhere == kStart)
|
|
{
|
|
// some special casing for text nodes
|
|
if (nsEditor::IsTextNode(aNode))
|
|
{
|
|
res = nsEditor::GetNodeLocation(aNode, address_of(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// look back through any further inline nodes that
|
|
// aren't across a <br> from us, and that are enclosed in the same block.
|
|
nsCOMPtr<nsIDOMNode> priorNode;
|
|
res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(priorNode), PR_TRUE);
|
|
|
|
while (priorNode && NS_SUCCEEDED(res))
|
|
{
|
|
if (mHTMLEditor->IsVisBreak(priorNode))
|
|
break;
|
|
if (IsBlockNode(priorNode))
|
|
break;
|
|
res = nsEditor::GetNodeLocation(priorNode, address_of(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(priorNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
|
|
// finding the real start for this point. look up the tree for as long as we are the
|
|
// first node in the container, and as long as we haven't hit the body node.
|
|
res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
while (!nearNode && !nsTextEditUtils::IsBody(node))
|
|
{
|
|
// some cutoffs are here: we don't need to also include them in the aWhere == kEnd case.
|
|
// as long as they are in one or the other it will work.
|
|
// special case for outdent: don't keep looking up
|
|
// if we have found a blockquote element to act on
|
|
if ((actionID == nsHTMLEditor::kOpOutdent) && nsHTMLEditUtils::IsBlockquote(node))
|
|
break;
|
|
|
|
res = nsEditor::GetNodeLocation(node, address_of(parent), &pOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Don't walk past the editable section. Note that we need to check
|
|
// before walking up to a parent because we need to return the parent
|
|
// object, so the parent itself might not be in the editable area, but
|
|
// it's OK.
|
|
if (!mHTMLEditor->IsNodeInActiveEditor(node) &&
|
|
!mHTMLEditor->IsNodeInActiveEditor(parent)) {
|
|
break;
|
|
}
|
|
|
|
node = parent;
|
|
offset = pOffset;
|
|
res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
*outNode = node;
|
|
*outOffset = offset;
|
|
return res;
|
|
}
|
|
|
|
if (aWhere == kEnd)
|
|
{
|
|
// some special casing for text nodes
|
|
if (nsEditor::IsTextNode(aNode))
|
|
{
|
|
res = nsEditor::GetNodeLocation(aNode, address_of(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
offset++; // want to be after the text node
|
|
}
|
|
|
|
// look ahead through any further inline nodes that
|
|
// aren't across a <br> from us, and that are enclosed in the same block.
|
|
nsCOMPtr<nsIDOMNode> nextNode;
|
|
res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nextNode), PR_TRUE);
|
|
|
|
while (nextNode && NS_SUCCEEDED(res))
|
|
{
|
|
if (IsBlockNode(nextNode))
|
|
break;
|
|
res = nsEditor::GetNodeLocation(nextNode, address_of(node), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
offset++;
|
|
if (mHTMLEditor->IsVisBreak(nextNode))
|
|
break;
|
|
res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nextNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// finding the real end for this point. look up the tree for as long as we are the
|
|
// last node in the container, and as long as we haven't hit the body node.
|
|
res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
while (!nearNode && !nsTextEditUtils::IsBody(node))
|
|
{
|
|
res = nsEditor::GetNodeLocation(node, address_of(parent), &pOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Don't walk past the editable section. Note that we need to check
|
|
// before walking up to a parent because we need to return the parent
|
|
// object, so the parent itself might not be in the editable area, but
|
|
// it's OK.
|
|
if (!mHTMLEditor->IsNodeInActiveEditor(node) &&
|
|
!mHTMLEditor->IsNodeInActiveEditor(parent)) {
|
|
break;
|
|
}
|
|
|
|
node = parent;
|
|
offset = pOffset+1; // we want to be AFTER nearNode
|
|
res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
*outNode = node;
|
|
*outOffset = offset;
|
|
return res;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPromotedRanges: run all the selection range endpoint through
|
|
// GetPromotedPoint()
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetPromotedRanges(nsISelection *inSelection,
|
|
nsCOMArray<nsIDOMRange> &outArrayOfRanges,
|
|
PRInt32 inOperationType)
|
|
{
|
|
NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER);
|
|
|
|
PRInt32 rangeCount;
|
|
nsresult res = inSelection->GetRangeCount(&rangeCount);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMRange> selectionRange;
|
|
nsCOMPtr<nsIDOMRange> opRange;
|
|
|
|
for (i = 0; i < rangeCount; i++)
|
|
{
|
|
res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// clone range so we don't muck with actual selection ranges
|
|
res = selectionRange->CloneRange(getter_AddRefs(opRange));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// make a new adjusted range to represent the appropriate block content.
|
|
// The basic idea is to push out the range endpoints
|
|
// to truly enclose the blocks that we will affect.
|
|
// This call alters opRange.
|
|
res = PromoteRange(opRange, inOperationType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// stuff new opRange into array
|
|
outArrayOfRanges.AppendObject(opRange);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// PromoteRange: expand a range to include any parents for which all
|
|
// editable children are already in range.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange,
|
|
PRInt32 inOperationType)
|
|
{
|
|
NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
PRInt32 startOffset, endOffset;
|
|
|
|
res = inRange->GetStartContainer(getter_AddRefs(startNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = inRange->GetStartOffset(&startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = inRange->GetEndContainer(getter_AddRefs(endNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = inRange->GetEndOffset(&endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// MOOSE major hack:
|
|
// GetPromotedPoint doesn't really do the right thing for collapsed ranges
|
|
// inside block elements that contain nothing but a solo <br>. It's easier
|
|
// to put a workaround here than to revamp GetPromotedPoint. :-(
|
|
if ( (startNode == endNode) && (startOffset == endOffset))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> block;
|
|
if (IsBlockNode(startNode))
|
|
block = startNode;
|
|
else
|
|
block = mHTMLEditor->GetBlockNodeParent(startNode);
|
|
if (block)
|
|
{
|
|
PRBool bIsEmptyNode = PR_FALSE;
|
|
// check for the editing host
|
|
nsIContent *rootContent = mHTMLEditor->GetActiveEditingHost();
|
|
nsCOMPtr<nsINode> rootNode = do_QueryInterface(rootContent);
|
|
nsCOMPtr<nsINode> blockNode = do_QueryInterface(block);
|
|
NS_ENSURE_TRUE(rootNode && blockNode, NS_ERROR_UNEXPECTED);
|
|
// Make sure we don't go higher than our root element in the content tree
|
|
if (!nsContentUtils::ContentIsDescendantOf(rootNode, blockNode))
|
|
{
|
|
res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
|
|
}
|
|
if (bIsEmptyNode)
|
|
{
|
|
PRUint32 numChildren;
|
|
nsEditor::GetLengthOfDOMNode(block, numChildren);
|
|
startNode = block;
|
|
endNode = block;
|
|
startOffset = 0;
|
|
endOffset = numChildren;
|
|
}
|
|
}
|
|
}
|
|
|
|
// make a new adjusted range to represent the appropriate block content.
|
|
// this is tricky. the basic idea is to push out the range endpoints
|
|
// to truly enclose the blocks that we will affect
|
|
|
|
nsCOMPtr<nsIDOMNode> opStartNode;
|
|
nsCOMPtr<nsIDOMNode> opEndNode;
|
|
PRInt32 opStartOffset, opEndOffset;
|
|
nsCOMPtr<nsIDOMRange> opRange;
|
|
|
|
res = GetPromotedPoint( kStart, startNode, startOffset, inOperationType, address_of(opStartNode), &opStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = GetPromotedPoint( kEnd, endNode, endOffset, inOperationType, address_of(opEndNode), &opEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Make sure that the new range ends up to be in the editable section.
|
|
if (!mHTMLEditor->IsNodeInActiveEditor(nsEditor::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) ||
|
|
!mHTMLEditor->IsNodeInActiveEditor(nsEditor::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
res = inRange->SetStart(opStartNode, opStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = inRange->SetEnd(opEndNode, opEndOffset);
|
|
return res;
|
|
}
|
|
|
|
class nsUniqueFunctor : public nsBoolDomIterFunctor
|
|
{
|
|
public:
|
|
nsUniqueFunctor(nsCOMArray<nsIDOMNode> &aArray) : mArray(aArray)
|
|
{
|
|
}
|
|
virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all nodes iterator covers
|
|
{
|
|
return mArray.IndexOf(aNode) < 0;
|
|
}
|
|
|
|
private:
|
|
nsCOMArray<nsIDOMNode> &mArray;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNodesForOperation: run through the ranges in the array and construct
|
|
// a new array of nodes to be acted on.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetNodesForOperation(nsCOMArray<nsIDOMRange>& inArrayOfRanges,
|
|
nsCOMArray<nsIDOMNode>& outArrayOfNodes,
|
|
PRInt32 inOperationType,
|
|
PRBool aDontTouchContent)
|
|
{
|
|
PRInt32 rangeCount = inArrayOfRanges.Count();
|
|
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMRange> opRange;
|
|
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
// bust up any inlines that cross our range endpoints,
|
|
// but only if we are allowed to touch content.
|
|
|
|
if (!aDontTouchContent)
|
|
{
|
|
nsAutoTArray<nsRangeStore, 16> rangeItemArray;
|
|
if (!rangeItemArray.AppendElements(rangeCount)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
NS_ASSERTION(static_cast<PRUint32>(rangeCount) == rangeItemArray.Length(),
|
|
"How did that happen?");
|
|
|
|
// first register ranges for special editor gravity
|
|
for (i = 0; i < rangeCount; i++)
|
|
{
|
|
opRange = inArrayOfRanges[0];
|
|
nsRangeStore *item = rangeItemArray.Elements() + i;
|
|
item->StoreRange(opRange);
|
|
mHTMLEditor->mRangeUpdater.RegisterRangeItem(item);
|
|
inArrayOfRanges.RemoveObjectAt(0);
|
|
}
|
|
// now bust up inlines. Safe to start at rangeCount-1, since we
|
|
// asserted we have enough items above.
|
|
for (i = rangeCount-1; i >= 0 && NS_SUCCEEDED(res); i--)
|
|
{
|
|
res = BustUpInlinesAtRangeEndpoints(rangeItemArray[i]);
|
|
}
|
|
// then unregister the ranges
|
|
for (i = 0; i < rangeCount; i++)
|
|
{
|
|
nsRangeStore *item = rangeItemArray.Elements() + i;
|
|
mHTMLEditor->mRangeUpdater.DropRangeItem(item);
|
|
nsresult res2 = item->GetRange(address_of(opRange));
|
|
if (NS_FAILED(res2) && NS_SUCCEEDED(res)) {
|
|
// Remember the failure, but keep going so we make sure to unregister
|
|
// all our range items.
|
|
res = res2;
|
|
}
|
|
inArrayOfRanges.AppendObject(opRange);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// gather up a list of all the nodes
|
|
for (i = 0; i < rangeCount; i++)
|
|
{
|
|
opRange = inArrayOfRanges[i];
|
|
|
|
nsDOMSubtreeIterator iter;
|
|
res = iter.Init(opRange);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (outArrayOfNodes.Count() == 0) {
|
|
nsTrivialFunctor functor;
|
|
res = iter.AppendList(functor, outArrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else {
|
|
// We don't want duplicates in outArrayOfNodes, so we use an
|
|
// iterator/functor that only return nodes that are not already in
|
|
// outArrayOfNodes.
|
|
nsCOMArray<nsIDOMNode> nodes;
|
|
nsUniqueFunctor functor(outArrayOfNodes);
|
|
res = iter.AppendList(functor, nodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!outArrayOfNodes.AppendObjects(nodes))
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
// certain operations should not act on li's and td's, but rather inside
|
|
// them. alter the list as needed
|
|
if (inOperationType == kMakeBasicBlock)
|
|
{
|
|
PRInt32 listCount = outArrayOfNodes.Count();
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
|
|
if (nsHTMLEditUtils::IsListItem(node))
|
|
{
|
|
PRInt32 j=i;
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
res = GetInnerContent(node, outArrayOfNodes, &j);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
// indent/outdent already do something special for list items, but
|
|
// we still need to make sure we don't act on table elements
|
|
else if ( (inOperationType == kOutdent) ||
|
|
(inOperationType == kIndent) ||
|
|
(inOperationType == kSetAbsolutePosition))
|
|
{
|
|
PRInt32 listCount = outArrayOfNodes.Count();
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
|
|
if (nsHTMLEditUtils::IsTableElementButNotTable(node))
|
|
{
|
|
PRInt32 j=i;
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
res = GetInnerContent(node, outArrayOfNodes, &j);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
// outdent should look inside of divs.
|
|
if (inOperationType == kOutdent && !useCSS)
|
|
{
|
|
PRInt32 listCount = outArrayOfNodes.Count();
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
|
|
if (nsHTMLEditUtils::IsDiv(node))
|
|
{
|
|
PRInt32 j=i;
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
res = GetInnerContent(node, outArrayOfNodes, &j, PR_FALSE, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// post process the list to break up inline containers that contain br's.
|
|
// but only for operations that might care, like making lists or para's...
|
|
if ( (inOperationType == kMakeBasicBlock) ||
|
|
(inOperationType == kMakeList) ||
|
|
(inOperationType == kAlign) ||
|
|
(inOperationType == kSetAbsolutePosition) ||
|
|
(inOperationType == kIndent) ||
|
|
(inOperationType == kOutdent) )
|
|
{
|
|
PRInt32 listCount = outArrayOfNodes.Count();
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
|
|
if (!aDontTouchContent && IsInlineNode(node)
|
|
&& mHTMLEditor->IsContainer(node) && !mHTMLEditor->IsTextNode(node))
|
|
{
|
|
nsCOMArray<nsIDOMNode> arrayOfInlines;
|
|
res = BustUpInlinesAtBRs(node, arrayOfInlines);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// put these nodes in outArrayOfNodes, replacing the current node
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
outArrayOfNodes.InsertObjectsAt(arrayOfInlines, i);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetChildNodesForOperation:
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode,
|
|
nsCOMArray<nsIDOMNode>& outArrayOfNodes)
|
|
{
|
|
NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
nsresult res = inNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(childNodes, NS_ERROR_NULL_POINTER);
|
|
PRUint32 childCount;
|
|
res = childNodes->GetLength(&childCount);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRUint32 i;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
for (i = 0; i < childCount; i++)
|
|
{
|
|
res = childNodes->Item( i, getter_AddRefs(node));
|
|
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
|
|
if (!outArrayOfNodes.AppendObject(node))
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetListActionNodes:
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetListActionNodes(nsCOMArray<nsIDOMNode> &outArrayOfNodes,
|
|
PRBool aEntireList,
|
|
PRBool aDontTouchContent)
|
|
{
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE);
|
|
// added this in so that ui code can ask to change an entire list, even if selection
|
|
// is only in part of it. used by list item dialog.
|
|
if (aEntireList)
|
|
{
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(enumerator, NS_ERROR_UNEXPECTED);
|
|
|
|
for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next())
|
|
{
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(currentItem, NS_ERROR_UNEXPECTED);
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
nsCOMPtr<nsIDOMNode> commonParent, parent, tmp;
|
|
range->GetCommonAncestorContainer(getter_AddRefs(commonParent));
|
|
if (commonParent)
|
|
{
|
|
parent = commonParent;
|
|
while (parent)
|
|
{
|
|
if (nsHTMLEditUtils::IsList(parent))
|
|
{
|
|
outArrayOfNodes.AppendObject(parent);
|
|
break;
|
|
}
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
parent = tmp;
|
|
}
|
|
}
|
|
}
|
|
// if we didn't find any nodes this way, then try the normal way. perhaps the
|
|
// selection spans multiple lists but with no common list parent.
|
|
if (outArrayOfNodes.Count()) return NS_OK;
|
|
}
|
|
|
|
{
|
|
// We don't like other people messing with our selection!
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
|
|
|
|
// contruct a list of nodes to act on.
|
|
res = GetNodesFromSelection(selection, kMakeList, outArrayOfNodes, aDontTouchContent);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// pre process our list of nodes...
|
|
PRInt32 listCount = outArrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i];
|
|
|
|
// Remove all non-editable nodes. Leave them be.
|
|
if (!mHTMLEditor->IsEditable(testNode))
|
|
{
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
}
|
|
|
|
// scan for table elements and divs. If we find table elements other than table,
|
|
// replace it with a list of any editable non-table content.
|
|
if (nsHTMLEditUtils::IsTableElementButNotTable(testNode))
|
|
{
|
|
PRInt32 j=i;
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
res = GetInnerContent(testNode, outArrayOfNodes, &j, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
// if there is only one node in the array, and it is a list, div, or blockquote,
|
|
// then look inside of it until we find inner list or content.
|
|
res = LookInsideDivBQandList(outArrayOfNodes);
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// LookInsideDivBQandList:
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::LookInsideDivBQandList(nsCOMArray<nsIDOMNode>& aNodeArray)
|
|
{
|
|
// if there is only one node in the array, and it is a list, div, or blockquote,
|
|
// then look inside of it until we find inner list or content.
|
|
nsresult res = NS_OK;
|
|
PRInt32 listCount = aNodeArray.Count();
|
|
if (listCount == 1)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = aNodeArray[0];
|
|
|
|
while (nsHTMLEditUtils::IsDiv(curNode)
|
|
|| nsHTMLEditUtils::IsList(curNode)
|
|
|| nsHTMLEditUtils::IsBlockquote(curNode))
|
|
{
|
|
// dive as long as there is only one child, and it is a list, div, blockquote
|
|
PRUint32 numChildren;
|
|
res = mHTMLEditor->CountEditableChildren(curNode, numChildren);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (numChildren == 1)
|
|
{
|
|
// keep diving
|
|
nsCOMPtr <nsIDOMNode> tmpNode = nsEditor::GetChildAt(curNode, 0);
|
|
if (nsHTMLEditUtils::IsDiv(tmpNode)
|
|
|| nsHTMLEditUtils::IsList(tmpNode)
|
|
|| nsHTMLEditUtils::IsBlockquote(tmpNode))
|
|
{
|
|
// check editablility XXX floppy moose
|
|
curNode = tmpNode;
|
|
}
|
|
else break;
|
|
}
|
|
else break;
|
|
}
|
|
// we've found innermost list/blockquote/div:
|
|
// replace the one node in the array with these nodes
|
|
aNodeArray.RemoveObjectAt(0);
|
|
if ((nsHTMLEditUtils::IsDiv(curNode) || nsHTMLEditUtils::IsBlockquote(curNode)))
|
|
{
|
|
PRInt32 j=0;
|
|
res = GetInnerContent(curNode, aNodeArray, &j, PR_FALSE, PR_FALSE);
|
|
}
|
|
else
|
|
{
|
|
aNodeArray.AppendObject(curNode);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetDefinitionListItemTypes:
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetDefinitionListItemTypes(nsIDOMNode *aNode, PRBool &aDT, PRBool &aDD)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
aDT = aDD = PR_FALSE;
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> child, temp;
|
|
res = aNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child && NS_SUCCEEDED(res))
|
|
{
|
|
if (nsEditor::NodeIsType(child, nsEditProperty::dt)) aDT = PR_TRUE;
|
|
else if (nsEditor::NodeIsType(child, nsEditProperty::dd)) aDD = PR_TRUE;
|
|
res = child->GetNextSibling(getter_AddRefs(temp));
|
|
child = temp;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetParagraphFormatNodes:
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetParagraphFormatNodes(nsCOMArray<nsIDOMNode>& outArrayOfNodes,
|
|
PRBool aDontTouchContent)
|
|
{
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// contruct a list of nodes to act on.
|
|
res = GetNodesFromSelection(selection, kMakeBasicBlock, outArrayOfNodes, aDontTouchContent);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// pre process our list of nodes...
|
|
PRInt32 listCount = outArrayOfNodes.Count();
|
|
PRInt32 i;
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i];
|
|
|
|
// Remove all non-editable nodes. Leave them be.
|
|
if (!mHTMLEditor->IsEditable(testNode))
|
|
{
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
}
|
|
|
|
// scan for table elements. If we find table elements other than table,
|
|
// replace it with a list of any editable non-table content. Ditto for list elements.
|
|
if (nsHTMLEditUtils::IsTableElement(testNode) ||
|
|
nsHTMLEditUtils::IsList(testNode) ||
|
|
nsHTMLEditUtils::IsListItem(testNode) )
|
|
{
|
|
PRInt32 j=i;
|
|
outArrayOfNodes.RemoveObjectAt(i);
|
|
res = GetInnerContent(testNode, outArrayOfNodes, &j);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// BustUpInlinesAtRangeEndpoints:
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item)
|
|
{
|
|
nsresult res = NS_OK;
|
|
PRBool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset));
|
|
|
|
nsCOMPtr<nsIDOMNode> endInline = GetHighestInlineParent(item.endNode);
|
|
|
|
// if we have inline parents above range endpoints, split them
|
|
if (endInline && !isCollapsed)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> resultEndNode;
|
|
PRInt32 resultEndOffset;
|
|
endInline->GetParentNode(getter_AddRefs(resultEndNode));
|
|
res = mHTMLEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset,
|
|
&resultEndOffset, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// reset range
|
|
item.endNode = resultEndNode; item.endOffset = resultEndOffset;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> startInline = GetHighestInlineParent(item.startNode);
|
|
|
|
if (startInline)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> resultStartNode;
|
|
PRInt32 resultStartOffset;
|
|
startInline->GetParentNode(getter_AddRefs(resultStartNode));
|
|
res = mHTMLEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset,
|
|
&resultStartOffset, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// reset range
|
|
item.startNode = resultStartNode; item.startOffset = resultStartOffset;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// BustUpInlinesAtBRs:
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode,
|
|
nsCOMArray<nsIDOMNode>& outArrayOfNodes)
|
|
{
|
|
NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER);
|
|
|
|
// first step is to build up a list of all the break nodes inside
|
|
// the inline container.
|
|
nsCOMArray<nsIDOMNode> arrayOfBreaks;
|
|
nsBRNodeFunctor functor;
|
|
nsDOMIterator iter;
|
|
nsresult res = iter.Init(inNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = iter.AppendList(functor, arrayOfBreaks);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if there aren't any breaks, just put inNode itself in the array
|
|
PRInt32 listCount = arrayOfBreaks.Count();
|
|
if (!listCount)
|
|
{
|
|
if (!outArrayOfNodes.AppendObject(inNode))
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
else
|
|
{
|
|
// else we need to bust up inNode along all the breaks
|
|
nsCOMPtr<nsIDOMNode> breakNode;
|
|
nsCOMPtr<nsIDOMNode> inlineParentNode;
|
|
nsCOMPtr<nsIDOMNode> leftNode;
|
|
nsCOMPtr<nsIDOMNode> rightNode;
|
|
nsCOMPtr<nsIDOMNode> splitDeepNode = inNode;
|
|
nsCOMPtr<nsIDOMNode> splitParentNode;
|
|
PRInt32 splitOffset, resultOffset, i;
|
|
inNode->GetParentNode(getter_AddRefs(inlineParentNode));
|
|
|
|
for (i=0; i< listCount; i++)
|
|
{
|
|
breakNode = arrayOfBreaks[i];
|
|
NS_ENSURE_TRUE(breakNode, NS_ERROR_NULL_POINTER);
|
|
NS_ENSURE_TRUE(splitDeepNode, NS_ERROR_NULL_POINTER);
|
|
res = nsEditor::GetNodeLocation(breakNode, address_of(splitParentNode), &splitOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset,
|
|
&resultOffset, PR_FALSE, address_of(leftNode), address_of(rightNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// put left node in node list
|
|
if (leftNode)
|
|
{
|
|
// might not be a left node. a break might have been at the very
|
|
// beginning of inline container, in which case splitnodedeep
|
|
// would not actually split anything
|
|
if (!outArrayOfNodes.AppendObject(leftNode))
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
// move break outside of container and also put in node list
|
|
res = mHTMLEditor->MoveNode(breakNode, inlineParentNode, resultOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!outArrayOfNodes.AppendObject(breakNode))
|
|
return NS_ERROR_FAILURE;
|
|
// now rightNode becomes the new node to split
|
|
splitDeepNode = rightNode;
|
|
}
|
|
// now tack on remaining rightNode, if any, to the list
|
|
if (rightNode)
|
|
{
|
|
if (!outArrayOfNodes.AppendObject(rightNode))
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsCOMPtr<nsIDOMNode>
|
|
nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, nsnull);
|
|
if (IsBlockNode(aNode)) return nsnull;
|
|
nsCOMPtr<nsIDOMNode> inlineNode, node=aNode;
|
|
|
|
while (node && IsInlineNode(node))
|
|
{
|
|
inlineNode = node;
|
|
inlineNode->GetParentNode(getter_AddRefs(node));
|
|
}
|
|
return inlineNode;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNodesFromPoint: given a particular operation, construct a list
|
|
// of nodes from a point that will be operated on.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetNodesFromPoint(DOMPoint point,
|
|
PRInt32 operation,
|
|
nsCOMArray<nsIDOMNode> &arrayOfNodes,
|
|
PRBool dontTouchContent)
|
|
{
|
|
nsresult res;
|
|
|
|
// get our point
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
point.GetPoint(node, offset);
|
|
|
|
// use it to make a range
|
|
nsCOMPtr<nsIDOMRange> range = do_CreateInstance("@mozilla.org/content/range;1");
|
|
res = range->SetStart(node, offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
/* SetStart() will also set the end for this new range
|
|
res = range->SetEnd(node, offset);
|
|
NS_ENSURE_SUCCESS(res, res); */
|
|
|
|
// expand the range to include adjacent inlines
|
|
res = PromoteRange(range, operation);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// make array of ranges
|
|
nsCOMArray<nsIDOMRange> arrayOfRanges;
|
|
|
|
// stuff new opRange into array
|
|
arrayOfRanges.AppendObject(range);
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent);
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNodesFromSelection: given a particular operation, construct a list
|
|
// of nodes from the selection that will be operated on.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetNodesFromSelection(nsISelection *selection,
|
|
PRInt32 operation,
|
|
nsCOMArray<nsIDOMNode>& arrayOfNodes,
|
|
PRBool dontTouchContent)
|
|
{
|
|
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
|
|
nsresult res;
|
|
|
|
// promote selection ranges
|
|
nsCOMArray<nsIDOMRange> arrayOfRanges;
|
|
res = GetPromotedRanges(selection, arrayOfRanges, operation);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent);
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// MakeTransitionList: detect all the transitions in the array, where a
|
|
// transition means that adjacent nodes in the array
|
|
// don't have the same parent.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::MakeTransitionList(nsCOMArray<nsIDOMNode>& inArrayOfNodes,
|
|
nsTArray<PRPackedBool> &inTransitionArray)
|
|
{
|
|
PRUint32 listCount = inArrayOfNodes.Count();
|
|
inTransitionArray.EnsureLengthAtLeast(listCount);
|
|
PRUint32 i;
|
|
nsCOMPtr<nsIDOMNode> prevElementParent;
|
|
nsCOMPtr<nsIDOMNode> curElementParent;
|
|
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
nsIDOMNode* transNode = inArrayOfNodes[i];
|
|
transNode->GetParentNode(getter_AddRefs(curElementParent));
|
|
if (curElementParent != prevElementParent)
|
|
{
|
|
// different parents, or separated by <br>: transition point
|
|
inTransitionArray[i] = PR_TRUE;
|
|
}
|
|
else
|
|
{
|
|
// same parents: these nodes grew up together
|
|
inTransitionArray[i] = PR_FALSE;
|
|
}
|
|
prevElementParent = curElementParent;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
/********************************************************
|
|
* main implementation methods
|
|
********************************************************/
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsInListItem: if aNode is the descendant of a listitem, return that li.
|
|
// But table element boundaries are stoppers on the search.
|
|
// Also test if aNode is an li itself.
|
|
//
|
|
nsCOMPtr<nsIDOMNode>
|
|
nsHTMLEditRules::IsInListItem(nsIDOMNode *aNode)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, nsnull);
|
|
if (nsHTMLEditUtils::IsListItem(aNode)) return aNode;
|
|
|
|
nsCOMPtr<nsIDOMNode> parent, tmp;
|
|
aNode->GetParentNode(getter_AddRefs(parent));
|
|
|
|
while (parent)
|
|
{
|
|
if (nsHTMLEditUtils::IsTableElement(parent)) return nsnull;
|
|
if (nsHTMLEditUtils::IsListItem(parent)) return parent;
|
|
tmp=parent; tmp->GetParentNode(getter_AddRefs(parent));
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ReturnInHeader: do the right thing for returns pressed in headers
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection,
|
|
nsIDOMNode *aHeader,
|
|
nsIDOMNode *aNode,
|
|
PRInt32 aOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection && aHeader && aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
// remeber where the header is
|
|
nsCOMPtr<nsIDOMNode> headerParent;
|
|
PRInt32 offset;
|
|
nsresult res = nsEditor::GetNodeLocation(aHeader, address_of(headerParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// get ws code to adjust any ws
|
|
nsCOMPtr<nsIDOMNode> selNode = aNode;
|
|
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// split the header
|
|
PRInt32 newOffset;
|
|
res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if the leftand heading is empty, put a mozbr in it
|
|
nsCOMPtr<nsIDOMNode> prevItem;
|
|
mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem));
|
|
if (prevItem && nsHTMLEditUtils::IsHeader(prevItem))
|
|
{
|
|
PRBool bIsEmptyNode;
|
|
res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bIsEmptyNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = CreateMozBR(prevItem, 0, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
// if the new (righthand) header node is empty, delete it
|
|
PRBool isEmpty;
|
|
res = IsEmptyBlock(aHeader, &isEmpty, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (isEmpty)
|
|
{
|
|
res = mHTMLEditor->DeleteNode(aHeader);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// layout tells the caret to blink in a weird place
|
|
// if we don't place a break after the header.
|
|
nsCOMPtr<nsIDOMNode> sibling;
|
|
res = mHTMLEditor->GetNextHTMLSibling(headerParent, offset+1, address_of(sibling));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!sibling || !nsTextEditUtils::IsBreak(sibling))
|
|
{
|
|
// create a paragraph
|
|
NS_NAMED_LITERAL_STRING(pType, "p");
|
|
nsCOMPtr<nsIDOMNode> pNode;
|
|
res = mHTMLEditor->CreateNode(pType, headerParent, offset+1, getter_AddRefs(pNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// append a <br> to it
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// set selection to before the break
|
|
res = aSelection->Collapse(pNode, 0);
|
|
}
|
|
else
|
|
{
|
|
res = nsEditor::GetNodeLocation(sibling, address_of(headerParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// put selection after break
|
|
res = aSelection->Collapse(headerParent,offset+1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// put selection at front of righthand heading
|
|
res = aSelection->Collapse(aHeader,0);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ReturnInParagraph: do the right thing for returns pressed in paragraphs
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection,
|
|
nsIDOMNode *aPara,
|
|
nsIDOMNode *aNode,
|
|
PRInt32 aOffset,
|
|
PRBool *aCancel,
|
|
PRBool *aHandled)
|
|
{
|
|
if (!aSelection || !aPara || !aNode || !aCancel || !aHandled)
|
|
{ return NS_ERROR_NULL_POINTER; }
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_FALSE;
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
nsresult res = nsEditor::GetNodeLocation(aNode, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRBool doesCRCreateNewP;
|
|
res = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph(&doesCRCreateNewP);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRBool newBRneeded = PR_FALSE;
|
|
nsCOMPtr<nsIDOMNode> sibling;
|
|
|
|
if (aNode == aPara && doesCRCreateNewP) {
|
|
// we are at the edges of the block, newBRneeded not needed!
|
|
sibling = aNode;
|
|
}
|
|
else if (mHTMLEditor->IsTextNode(aNode))
|
|
{
|
|
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aNode);
|
|
PRUint32 strLength;
|
|
res = textNode->GetLength(&strLength);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// at beginning of text node?
|
|
if (!aOffset)
|
|
{
|
|
// is there a BR prior to it?
|
|
mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling));
|
|
if (!sibling ||
|
|
!mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(sibling))
|
|
{
|
|
newBRneeded = PR_TRUE;
|
|
}
|
|
}
|
|
else if (aOffset == (PRInt32)strLength)
|
|
{
|
|
// we're at the end of text node...
|
|
// is there a BR after to it?
|
|
res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling));
|
|
if (!sibling ||
|
|
!mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(sibling))
|
|
{
|
|
newBRneeded = PR_TRUE;
|
|
offset++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (doesCRCreateNewP)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
res = mEditor->SplitNode(aNode, aOffset, getter_AddRefs(tmp));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
aNode = tmp;
|
|
}
|
|
|
|
newBRneeded = PR_TRUE;
|
|
offset++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not in a text node.
|
|
// is there a BR prior to it?
|
|
nsCOMPtr<nsIDOMNode> nearNode, selNode = aNode;
|
|
res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(nearNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || nsTextEditUtils::HasMozAttr(nearNode))
|
|
{
|
|
// is there a BR after it?
|
|
res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nearNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || nsTextEditUtils::HasMozAttr(nearNode))
|
|
{
|
|
newBRneeded = PR_TRUE;
|
|
}
|
|
}
|
|
if (!newBRneeded)
|
|
sibling = nearNode;
|
|
}
|
|
if (newBRneeded)
|
|
{
|
|
// if CR does not create a new P, default to BR creation
|
|
NS_ENSURE_TRUE(doesCRCreateNewP, NS_OK);
|
|
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
|
|
sibling = brNode;
|
|
}
|
|
nsCOMPtr<nsIDOMNode> selNode = aNode;
|
|
*aHandled = PR_TRUE;
|
|
return SplitParagraph(aPara, sibling, aSelection, address_of(selNode), &aOffset);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// SplitParagraph: split a paragraph at selection point, possibly deleting a br
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::SplitParagraph(nsIDOMNode *aPara,
|
|
nsIDOMNode *aBRNode,
|
|
nsISelection *aSelection,
|
|
nsCOMPtr<nsIDOMNode> *aSelNode,
|
|
PRInt32 *aOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aPara && aBRNode && aSelNode && *aSelNode && aOffset && aSelection, NS_ERROR_NULL_POINTER);
|
|
nsresult res = NS_OK;
|
|
|
|
// split para
|
|
PRInt32 newOffset;
|
|
// get ws code to adjust any ws
|
|
nsCOMPtr<nsIDOMNode> leftPara, rightPara;
|
|
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, aSelNode, aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// split the paragraph
|
|
res = mHTMLEditor->SplitNodeDeep(aPara, *aSelNode, *aOffset, &newOffset, PR_FALSE,
|
|
address_of(leftPara), address_of(rightPara));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p)
|
|
if (mHTMLEditor->IsVisBreak(aBRNode))
|
|
{
|
|
res = mHTMLEditor->DeleteNode(aBRNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// remove ID attribute on the paragraph we just created
|
|
nsCOMPtr<nsIDOMElement> rightElt = do_QueryInterface(rightPara);
|
|
res = mHTMLEditor->RemoveAttribute(rightElt, NS_LITERAL_STRING("id"));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// check both halves of para to see if we need mozBR
|
|
res = InsertMozBRIfNeeded(leftPara);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = InsertMozBRIfNeeded(rightPara);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// selection to beginning of right hand para;
|
|
// look inside any containers that are up front.
|
|
nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(rightPara, PR_TRUE);
|
|
if (mHTMLEditor->IsTextNode(child) || mHTMLEditor->IsContainer(child))
|
|
{
|
|
aSelection->Collapse(child,0);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(child, address_of(parent), &offset);
|
|
aSelection->Collapse(parent,offset);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ReturnInListItem: do the right thing for returns pressed in list items
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::ReturnInListItem(nsISelection *aSelection,
|
|
nsIDOMNode *aListItem,
|
|
nsIDOMNode *aNode,
|
|
PRInt32 aOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection && aListItem && aNode, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsISelection> selection(aSelection);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> listitem;
|
|
|
|
// sanity check
|
|
NS_PRECONDITION(PR_TRUE == nsHTMLEditUtils::IsListItem(aListItem),
|
|
"expected a list item and didn't get one");
|
|
|
|
// if we are in an empty listitem, then we want to pop up out of the list
|
|
PRBool isEmpty;
|
|
res = IsEmptyBlock(aListItem, &isEmpty, PR_TRUE, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (isEmpty && mReturnInEmptyLIKillsList) // but only if prefs says it's ok
|
|
{
|
|
nsCOMPtr<nsIDOMNode> list, listparent;
|
|
PRInt32 offset, itemOffset;
|
|
res = nsEditor::GetNodeLocation(aListItem, address_of(list), &itemOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = nsEditor::GetNodeLocation(list, address_of(listparent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// are we the last list item in the list?
|
|
PRBool bIsLast;
|
|
res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLast);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bIsLast)
|
|
{
|
|
// we need to split the list!
|
|
nsCOMPtr<nsIDOMNode> tempNode;
|
|
res = mHTMLEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// are we in a sublist?
|
|
if (nsHTMLEditUtils::IsList(listparent)) //in a sublist
|
|
{
|
|
// if so, move this list item out of this list and into the grandparent list
|
|
res = mHTMLEditor->MoveNode(aListItem,listparent,offset+1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = aSelection->Collapse(aListItem,0);
|
|
}
|
|
else
|
|
{
|
|
// otherwise kill this listitem
|
|
res = mHTMLEditor->DeleteNode(aListItem);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// time to insert a paragraph
|
|
NS_NAMED_LITERAL_STRING(pType, "p");
|
|
nsCOMPtr<nsIDOMNode> pNode;
|
|
res = mHTMLEditor->CreateNode(pType, listparent, offset+1, getter_AddRefs(pNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// append a <br> to it
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// set selection to before the break
|
|
res = aSelection->Collapse(pNode, 0);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// else we want a new list item at the same list level.
|
|
// get ws code to adjust any ws
|
|
nsCOMPtr<nsIDOMNode> selNode = aNode;
|
|
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// now split list item
|
|
PRInt32 newOffset;
|
|
res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// hack: until I can change the damaged doc range code back to being
|
|
// extra inclusive, I have to manually detect certain list items that
|
|
// may be left empty.
|
|
nsCOMPtr<nsIDOMNode> prevItem;
|
|
mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem));
|
|
|
|
if (prevItem && nsHTMLEditUtils::IsListItem(prevItem))
|
|
{
|
|
PRBool bIsEmptyNode;
|
|
res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bIsEmptyNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = CreateMozBR(prevItem, 0, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->IsEmptyNode(aListItem, &bIsEmptyNode, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bIsEmptyNode)
|
|
{
|
|
nsCOMPtr<nsIAtom> nodeAtom = nsEditor::GetTag(aListItem);
|
|
if (nodeAtom == nsEditProperty::dd || nodeAtom == nsEditProperty::dt)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> list;
|
|
PRInt32 itemOffset;
|
|
res = nsEditor::GetNodeLocation(aListItem, address_of(list), &itemOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
nsAutoString listTag((nodeAtom == nsEditProperty::dt) ? NS_LITERAL_STRING("dd") : NS_LITERAL_STRING("dt"));
|
|
nsCOMPtr<nsIDOMNode> newListItem;
|
|
res = mHTMLEditor->CreateNode(listTag, list, itemOffset+1, getter_AddRefs(newListItem));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mEditor->DeleteNode(aListItem);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
return aSelection->Collapse(newListItem, 0);
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = mHTMLEditor->CopyLastEditableChildStyles(prevItem, aListItem, getter_AddRefs(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (brNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> brParent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(brNode, address_of(brParent), &offset);
|
|
return aSelection->Collapse(brParent, offset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nsWSRunObject wsObj(mHTMLEditor, aListItem, 0);
|
|
nsCOMPtr<nsIDOMNode> visNode;
|
|
PRInt32 visOffset = 0;
|
|
PRInt16 wsType;
|
|
res = wsObj.NextVisibleNode(aListItem, 0, address_of(visNode), &visOffset, &wsType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if ( (wsType==nsWSRunObject::eSpecial) ||
|
|
(wsType==nsWSRunObject::eBreak) ||
|
|
nsHTMLEditUtils::IsHR(visNode) )
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(visNode, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
return aSelection->Collapse(parent, offset);
|
|
}
|
|
else
|
|
{
|
|
return aSelection->Collapse(visNode, visOffset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
res = aSelection->Collapse(aListItem,0);
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// MakeBlockquote: put the list of nodes into one or more blockquotes.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::MakeBlockquote(nsCOMArray<nsIDOMNode>& arrayOfNodes)
|
|
{
|
|
// the idea here is to put the nodes into a minimal number of
|
|
// blockquotes. When the user blockquotes something, they expect
|
|
// one blockquote. That may not be possible (for instance, if they
|
|
// have two table cells selected, you need two blockquotes inside the cells).
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock;
|
|
PRInt32 offset;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
|
|
nsCOMPtr<nsIDOMNode> prevParent;
|
|
|
|
PRInt32 i;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// get the node to act on, and it's location
|
|
curNode = arrayOfNodes[i];
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if the node is a table element or list item, dive inside
|
|
if (nsHTMLEditUtils::IsTableElementButNotTable(curNode) ||
|
|
nsHTMLEditUtils::IsListItem(curNode))
|
|
{
|
|
curBlock = 0; // forget any previous block
|
|
// recursion time
|
|
nsCOMArray<nsIDOMNode> childArray;
|
|
res = GetChildNodesForOperation(curNode, childArray);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = MakeBlockquote(childArray);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// if the node has different parent than previous node,
|
|
// further nodes in a new parent
|
|
if (prevParent)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> temp;
|
|
curNode->GetParentNode(getter_AddRefs(temp));
|
|
if (temp != prevParent)
|
|
{
|
|
curBlock = 0; // forget any previous blockquote node we were using
|
|
prevParent = temp;
|
|
}
|
|
}
|
|
else
|
|
|
|
{
|
|
curNode->GetParentNode(getter_AddRefs(prevParent));
|
|
}
|
|
|
|
// if no curBlock, make one
|
|
if (!curBlock)
|
|
{
|
|
NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
|
|
res = SplitAsNeeded("eType, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curBlock;
|
|
// note: doesn't matter if we set mNewBlock multiple times.
|
|
}
|
|
|
|
res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// RemoveBlockStyle: make the nodes have no special block type.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::RemoveBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes)
|
|
{
|
|
// intent of this routine is to be used for converting to/from
|
|
// headers, paragraphs, pre, and address. Those blocks
|
|
// that pretty much just contain inline things...
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, firstNode, lastNode;
|
|
PRInt32 offset;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
|
|
PRInt32 i;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// get the node to act on, and it's location
|
|
curNode = arrayOfNodes[i];
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoString curNodeTag, curBlockTag;
|
|
nsEditor::GetTagString(curNode, curNodeTag);
|
|
ToLowerCase(curNodeTag);
|
|
|
|
// if curNode is a address, p, header, address, or pre, remove it
|
|
if (nsHTMLEditUtils::IsFormatNode(curNode))
|
|
{
|
|
// process any partial progress saved
|
|
if (curBlock)
|
|
{
|
|
res = RemovePartOfBlock(curBlock, firstNode, lastNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlock = 0; firstNode = 0; lastNode = 0;
|
|
}
|
|
// remove curent block
|
|
res = mHTMLEditor->RemoveBlockContainer(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (nsHTMLEditUtils::IsTable(curNode) ||
|
|
nsHTMLEditUtils::IsTableRow(curNode) ||
|
|
(curNodeTag.EqualsLiteral("tbody")) ||
|
|
(curNodeTag.EqualsLiteral("td")) ||
|
|
nsHTMLEditUtils::IsList(curNode) ||
|
|
(curNodeTag.EqualsLiteral("li")) ||
|
|
nsHTMLEditUtils::IsBlockquote(curNode) ||
|
|
nsHTMLEditUtils::IsDiv(curNode))
|
|
{
|
|
// process any partial progress saved
|
|
if (curBlock)
|
|
{
|
|
res = RemovePartOfBlock(curBlock, firstNode, lastNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlock = 0; firstNode = 0; lastNode = 0;
|
|
}
|
|
// recursion time
|
|
nsCOMArray<nsIDOMNode> childArray;
|
|
res = GetChildNodesForOperation(curNode, childArray);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = RemoveBlockStyle(childArray);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (IsInlineNode(curNode))
|
|
{
|
|
if (curBlock)
|
|
{
|
|
// if so, is this node a descendant?
|
|
if (nsEditorUtils::IsDescendantOf(curNode, curBlock))
|
|
{
|
|
lastNode = curNode;
|
|
continue; // then we don't need to do anything different for this node
|
|
}
|
|
else
|
|
{
|
|
// otherwise, we have progressed beyond end of curBlock,
|
|
// so lets handle it now. We need to remove the portion of
|
|
// curBlock that contains [firstNode - lastNode].
|
|
res = RemovePartOfBlock(curBlock, firstNode, lastNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlock = 0; firstNode = 0; lastNode = 0;
|
|
// fall out and handle curNode
|
|
}
|
|
}
|
|
curBlock = mHTMLEditor->GetBlockNodeParent(curNode);
|
|
if (nsHTMLEditUtils::IsFormatNode(curBlock))
|
|
{
|
|
firstNode = curNode;
|
|
lastNode = curNode;
|
|
}
|
|
else
|
|
curBlock = 0; // not a block kind that we care about.
|
|
}
|
|
else
|
|
{ // some node that is already sans block style. skip over it and
|
|
// process any partial progress saved
|
|
if (curBlock)
|
|
{
|
|
res = RemovePartOfBlock(curBlock, firstNode, lastNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlock = 0; firstNode = 0; lastNode = 0;
|
|
}
|
|
}
|
|
}
|
|
// process any partial progress saved
|
|
if (curBlock)
|
|
{
|
|
res = RemovePartOfBlock(curBlock, firstNode, lastNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curBlock = 0; firstNode = 0; lastNode = 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ApplyBlockStyle: do whatever it takes to make the list of nodes into
|
|
// one or more blocks of type blockTag.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::ApplyBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes, const nsAString *aBlockTag)
|
|
{
|
|
// intent of this routine is to be used for converting to/from
|
|
// headers, paragraphs, pre, and address. Those blocks
|
|
// that pretty much just contain inline things...
|
|
|
|
NS_ENSURE_TRUE(aBlockTag, NS_ERROR_NULL_POINTER);
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock;
|
|
PRInt32 offset;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
nsString tString(*aBlockTag);////MJUDGE SCC NEED HELP
|
|
|
|
// Remove all non-editable nodes. Leave them be.
|
|
PRInt32 j;
|
|
for (j=listCount-1; j>=0; j--)
|
|
{
|
|
if (!mHTMLEditor->IsEditable(arrayOfNodes[j]))
|
|
{
|
|
arrayOfNodes.RemoveObjectAt(j);
|
|
}
|
|
}
|
|
|
|
// reset list count
|
|
listCount = arrayOfNodes.Count();
|
|
|
|
PRInt32 i;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// get the node to act on, and it's location
|
|
curNode = arrayOfNodes[i];
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoString curNodeTag;
|
|
nsEditor::GetTagString(curNode, curNodeTag);
|
|
ToLowerCase(curNodeTag);
|
|
|
|
// is it already the right kind of block?
|
|
if (curNodeTag == *aBlockTag)
|
|
{
|
|
curBlock = 0; // forget any previous block used for previous inline nodes
|
|
continue; // do nothing to this block
|
|
}
|
|
|
|
// if curNode is a address, p, header, address, or pre, replace
|
|
// it with a new block of correct type.
|
|
// xxx floppy moose: pre can't hold everything the others can
|
|
if (nsHTMLEditUtils::IsMozDiv(curNode) ||
|
|
nsHTMLEditUtils::IsFormatNode(curNode))
|
|
{
|
|
curBlock = 0; // forget any previous block used for previous inline nodes
|
|
res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), *aBlockTag,
|
|
nsnull, nsnull, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (nsHTMLEditUtils::IsTable(curNode) ||
|
|
(curNodeTag.EqualsLiteral("tbody")) ||
|
|
(curNodeTag.EqualsLiteral("tr")) ||
|
|
(curNodeTag.EqualsLiteral("td")) ||
|
|
nsHTMLEditUtils::IsList(curNode) ||
|
|
(curNodeTag.EqualsLiteral("li")) ||
|
|
nsHTMLEditUtils::IsBlockquote(curNode) ||
|
|
nsHTMLEditUtils::IsDiv(curNode))
|
|
{
|
|
curBlock = 0; // forget any previous block used for previous inline nodes
|
|
// recursion time
|
|
nsCOMArray<nsIDOMNode> childArray;
|
|
res = GetChildNodesForOperation(curNode, childArray);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRInt32 childCount = childArray.Count();
|
|
if (childCount)
|
|
{
|
|
res = ApplyBlockStyle(childArray, aBlockTag);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// make sure we can put a block here
|
|
res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> theBlock;
|
|
res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(theBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = theBlock;
|
|
}
|
|
}
|
|
|
|
// if the node is a break, we honor it by putting further nodes in a new parent
|
|
else if (curNodeTag.EqualsLiteral("br"))
|
|
{
|
|
if (curBlock)
|
|
{
|
|
curBlock = 0; // forget any previous block used for previous inline nodes
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// the break is the first (or even only) node we encountered. Create a
|
|
// block for it.
|
|
res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curBlock;
|
|
// note: doesn't matter if we set mNewBlock multiple times.
|
|
res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
|
|
// if curNode is inline, pull it into curBlock
|
|
// note: it's assumed that consecutive inline nodes in the
|
|
// arrayOfNodes are actually members of the same block parent.
|
|
// this happens to be true now as a side effect of how
|
|
// arrayOfNodes is contructed, but some additional logic should
|
|
// be added here if that should change
|
|
|
|
else if (IsInlineNode(curNode))
|
|
{
|
|
// if curNode is a non editable, drop it if we are going to <pre>
|
|
if (tString.LowerCaseEqualsLiteral("pre")
|
|
&& (!mHTMLEditor->IsEditable(curNode)))
|
|
continue; // do nothing to this block
|
|
|
|
// if no curBlock, make one
|
|
if (!curBlock)
|
|
{
|
|
res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curBlock;
|
|
// note: doesn't matter if we set mNewBlock multiple times.
|
|
}
|
|
|
|
// if curNode is a Break, replace it with a return if we are going to <pre>
|
|
// xxx floppy moose
|
|
|
|
// this is a continuation of some inline nodes that belong together in
|
|
// the same block item. use curBlock
|
|
res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// SplitAsNeeded: given a tag name, split inOutParent up to the point
|
|
// where we can insert the tag. Adjust inOutParent and
|
|
// inOutOffset to pint to new location for tag.
|
|
nsresult
|
|
nsHTMLEditRules::SplitAsNeeded(const nsAString *aTag,
|
|
nsCOMPtr<nsIDOMNode> *inOutParent,
|
|
PRInt32 *inOutOffset)
|
|
{
|
|
NS_ENSURE_TRUE(aTag && inOutParent && inOutOffset, NS_ERROR_NULL_POINTER);
|
|
NS_ENSURE_TRUE(*inOutParent, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsIDOMNode> tagParent, temp, splitNode, parent = *inOutParent;
|
|
nsresult res = NS_OK;
|
|
|
|
// check that we have a place that can legally contain the tag
|
|
while (!tagParent)
|
|
{
|
|
// sniffing up the parent tree until we find
|
|
// a legal place for the block
|
|
if (!parent) break;
|
|
if (mHTMLEditor->CanContainTag(parent, *aTag))
|
|
{
|
|
tagParent = parent;
|
|
break;
|
|
}
|
|
splitNode = parent;
|
|
parent->GetParentNode(getter_AddRefs(temp));
|
|
parent = temp;
|
|
}
|
|
if (!tagParent)
|
|
{
|
|
// could not find a place to build tag!
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (splitNode)
|
|
{
|
|
// we found a place for block, but above inOutParent. We need to split nodes.
|
|
res = mHTMLEditor->SplitNodeDeep(splitNode, *inOutParent, *inOutOffset, inOutOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
*inOutParent = tagParent;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// JoinNodesSmart: join two nodes, doing whatever makes sense for their
|
|
// children (which often means joining them, too).
|
|
// aNodeLeft & aNodeRight must be same type of node.
|
|
nsresult
|
|
nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft,
|
|
nsIDOMNode *aNodeRight,
|
|
nsCOMPtr<nsIDOMNode> *aOutMergeParent,
|
|
PRInt32 *aOutMergeOffset)
|
|
{
|
|
// check parms
|
|
NS_ENSURE_TRUE(aNodeLeft &&
|
|
aNodeRight &&
|
|
aOutMergeParent &&
|
|
aOutMergeOffset, NS_ERROR_NULL_POINTER);
|
|
|
|
nsresult res = NS_OK;
|
|
// caller responsible for:
|
|
// left & right node are same type
|
|
PRInt32 parOffset;
|
|
nsCOMPtr<nsIDOMNode> parent, rightParent;
|
|
res = nsEditor::GetNodeLocation(aNodeLeft, address_of(parent), &parOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
aNodeRight->GetParentNode(getter_AddRefs(rightParent));
|
|
|
|
// if they don't have the same parent, first move the 'right' node
|
|
// to after the 'left' one
|
|
if (parent != rightParent)
|
|
{
|
|
res = mHTMLEditor->MoveNode(aNodeRight, parent, parOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// defaults for outParams
|
|
*aOutMergeParent = aNodeRight;
|
|
res = mHTMLEditor->GetLengthOfDOMNode(aNodeLeft, *((PRUint32*)aOutMergeOffset));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// separate join rules for differing blocks
|
|
if (nsHTMLEditUtils::IsList(aNodeLeft) ||
|
|
mHTMLEditor->IsTextNode(aNodeLeft))
|
|
{
|
|
// for list's, merge shallow (wouldn't want to combine list items)
|
|
res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
return res;
|
|
}
|
|
else
|
|
{
|
|
// remember the last left child, and firt right child
|
|
nsCOMPtr<nsIDOMNode> lastLeft, firstRight;
|
|
res = mHTMLEditor->GetLastEditableChild(aNodeLeft, address_of(lastLeft));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->GetFirstEditableChild(aNodeRight, address_of(firstRight));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// for list items, divs, etc, merge smart
|
|
res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (lastLeft && firstRight &&
|
|
mHTMLEditor->NodesSameType(lastLeft, firstRight) &&
|
|
(nsEditor::IsTextNode(lastLeft) ||
|
|
mHTMLEditor->mHTMLCSSUtils->ElementsSameStyle(lastLeft, firstRight)))
|
|
return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode,
|
|
nsCOMPtr<nsIDOMNode> *aOutCiteNode,
|
|
PRBool aPlainText)
|
|
{
|
|
// check parms
|
|
NS_ENSURE_TRUE(aNode && aOutCiteNode, NS_ERROR_NULL_POINTER);
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> node, parentNode;
|
|
node = do_QueryInterface(aNode);
|
|
|
|
while (node)
|
|
{
|
|
if ( (aPlainText && nsHTMLEditUtils::IsPre(node)) ||
|
|
nsHTMLEditUtils::IsMailCite(node) )
|
|
*aOutCiteNode = node;
|
|
if (nsTextEditUtils::IsBody(node)) break;
|
|
|
|
res = node->GetParentNode(getter_AddRefs(parentNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
node = parentNode;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::CacheInlineStyles(nsIDOMNode *aNode)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
|
|
PRInt32 j;
|
|
for (j=0; j<SIZE_STYLE_TABLE; j++)
|
|
{
|
|
PRBool isSet = PR_FALSE;
|
|
nsAutoString outValue;
|
|
nsCOMPtr<nsIDOMNode> resultNode;
|
|
if (!useCSS)
|
|
{
|
|
mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag, &(mCachedStyles[j].attr), nsnull,
|
|
isSet, getter_AddRefs(resultNode), &outValue);
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode, mCachedStyles[j].tag, &(mCachedStyles[j].attr),
|
|
isSet, outValue, COMPUTED_STYLE_TYPE);
|
|
}
|
|
if (isSet)
|
|
{
|
|
mCachedStyles[j].mPresent = PR_TRUE;
|
|
mCachedStyles[j].value.Assign(outValue);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::ReapplyCachedStyles()
|
|
{
|
|
// The idea here is to examine our cached list of styles
|
|
// and see if any have been removed. If so, add typeinstate
|
|
// for them, so that they will be reinserted when new
|
|
// content is added.
|
|
|
|
// When we apply cached styles to TypeInState, we always want
|
|
// to blow away prior TypeInState:
|
|
mHTMLEditor->mTypeInState->Reset();
|
|
|
|
// remember if we are in css mode
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
|
|
// get selection point
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> selNode;
|
|
PRInt32 selOffset;
|
|
res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
res = NS_OK;
|
|
PRInt32 j;
|
|
for (j=0; j<SIZE_STYLE_TABLE; j++)
|
|
{
|
|
if (mCachedStyles[j].mPresent)
|
|
{
|
|
PRBool bFirst, bAny, bAll;
|
|
bFirst = bAny = bAll = PR_FALSE;
|
|
|
|
nsAutoString curValue;
|
|
if (useCSS) // check computed style first in css case
|
|
{
|
|
mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(selNode, mCachedStyles[j].tag, &(mCachedStyles[j].attr),
|
|
bAny, curValue, COMPUTED_STYLE_TYPE);
|
|
}
|
|
if (!bAny) // then check typeinstate and html style
|
|
{
|
|
res = mHTMLEditor->GetInlinePropertyBase(mCachedStyles[j].tag, &(mCachedStyles[j].attr), &(mCachedStyles[j].value),
|
|
&bFirst, &bAny, &bAll, &curValue, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// this style has disappeared through deletion. Add it onto our typeinstate:
|
|
if (!bAny)
|
|
{
|
|
mHTMLEditor->mTypeInState->SetProp(mCachedStyles[j].tag, mCachedStyles[j].attr, mCachedStyles[j].value);
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::ClearCachedStyles()
|
|
{
|
|
// clear the mPresent bits in mCachedStyles array
|
|
|
|
PRInt32 j;
|
|
for (j=0; j<SIZE_STYLE_TABLE; j++)
|
|
{
|
|
mCachedStyles[j].mPresent = PR_FALSE;
|
|
mCachedStyles[j].value.Truncate(0);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::AdjustSpecialBreaks(PRBool aSafeToAskFrames)
|
|
{
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
PRInt32 nodeCount,j;
|
|
|
|
// gather list of empty nodes
|
|
nsEmptyEditableFunctor functor(mHTMLEditor);
|
|
nsDOMIterator iter;
|
|
nsresult res = iter.Init(mDocChangeRange);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = iter.AppendList(functor, arrayOfNodes);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// put moz-br's into these empty li's and td's
|
|
nodeCount = arrayOfNodes.Count();
|
|
for (j = 0; j < nodeCount; j++)
|
|
{
|
|
// need to put br at END of node. It may have
|
|
// empty containers in it and still pass the "IsEmptynode" test,
|
|
// and we want the br's to be after them. Also, we want the br
|
|
// to be after the selection if the selection is in this node.
|
|
PRUint32 len;
|
|
nsCOMPtr<nsIDOMNode> brNode, theNode = arrayOfNodes[0];
|
|
arrayOfNodes.RemoveObjectAt(0);
|
|
res = nsEditor::GetLengthOfDOMNode(theNode, len);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = CreateMozBR(theNode, (PRInt32)len, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::AdjustWhitespace(nsISelection *aSelection)
|
|
{
|
|
// get selection point
|
|
nsCOMPtr<nsIDOMNode> selNode;
|
|
PRInt32 selOffset;
|
|
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// ask whitespace object to tweak nbsp's
|
|
return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::PinSelectionToNewBlock(nsISelection *aSelection)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
|
|
PRBool bCollapsed;
|
|
nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bCollapsed) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// get the (collapsed) selection location
|
|
nsCOMPtr<nsIDOMNode> selNode, temp;
|
|
PRInt32 selOffset;
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
temp = selNode;
|
|
|
|
// use ranges and sRangeHelper to compare sel point to new block
|
|
nsCOMPtr<nsIDOMRange> range = do_CreateInstance("@mozilla.org/content/range;1");
|
|
res = range->SetStart(selNode, selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->SetEnd(selNode, selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIContent> block (do_QueryInterface(mNewBlock));
|
|
NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE);
|
|
PRBool nodeBefore, nodeAfter;
|
|
res = mHTMLEditor->sRangeHelper->CompareNodeToRange(block, range, &nodeBefore, &nodeAfter);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (nodeBefore && nodeAfter)
|
|
return NS_OK; // selection is inside block
|
|
else if (nodeBefore)
|
|
{
|
|
// selection is after block. put at end of block.
|
|
nsCOMPtr<nsIDOMNode> tmp = mNewBlock;
|
|
mHTMLEditor->GetLastEditableChild(mNewBlock, address_of(tmp));
|
|
PRUint32 endPoint;
|
|
if (mHTMLEditor->IsTextNode(tmp) || mHTMLEditor->IsContainer(tmp))
|
|
{
|
|
res = nsEditor::GetLengthOfDOMNode(tmp, endPoint);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp2;
|
|
res = nsEditor::GetNodeLocation(tmp, address_of(tmp2), (PRInt32*)&endPoint);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
tmp = tmp2;
|
|
endPoint++; // want to be after this node
|
|
}
|
|
return aSelection->Collapse(tmp, (PRInt32)endPoint);
|
|
}
|
|
else
|
|
{
|
|
// selection is before block. put at start of block.
|
|
nsCOMPtr<nsIDOMNode> tmp = mNewBlock;
|
|
mHTMLEditor->GetFirstEditableChild(mNewBlock, address_of(tmp));
|
|
PRInt32 offset;
|
|
if (!(mHTMLEditor->IsTextNode(tmp) || mHTMLEditor->IsContainer(tmp)))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp2;
|
|
res = nsEditor::GetNodeLocation(tmp, address_of(tmp2), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
tmp = tmp2;
|
|
}
|
|
return aSelection->Collapse(tmp, 0);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::CheckInterlinePosition(nsISelection *aSelection)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsISelection> selection(aSelection);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
// if the selection isn't collapsed, do nothing.
|
|
PRBool bCollapsed;
|
|
nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bCollapsed) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// get the (collapsed) selection location
|
|
nsCOMPtr<nsIDOMNode> selNode, node;
|
|
PRInt32 selOffset;
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// are we after a block? If so try set caret to following content
|
|
mHTMLEditor->GetPriorHTMLSibling(selNode, selOffset, address_of(node));
|
|
if (node && IsBlockNode(node))
|
|
{
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
return NS_OK;
|
|
}
|
|
|
|
// are we before a block? If so try set caret to prior content
|
|
mHTMLEditor->GetNextHTMLSibling(selNode, selOffset, address_of(node));
|
|
if (node && IsBlockNode(node))
|
|
{
|
|
selPriv->SetInterlinePosition(PR_FALSE);
|
|
return NS_OK;
|
|
}
|
|
|
|
// are we after a <br>? If so we want to stick to whatever is after <br>.
|
|
mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(node), PR_TRUE);
|
|
if (node && nsTextEditUtils::IsBreak(node))
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection aAction)
|
|
{
|
|
NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
|
|
nsCOMPtr<nsISelection> selection(aSelection);
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
// if the selection isn't collapsed, do nothing.
|
|
// moose: one thing to do instead is check for the case of
|
|
// only a single break selected, and collapse it. Good thing? Beats me.
|
|
PRBool bCollapsed;
|
|
nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bCollapsed) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// get the (collapsed) selection location
|
|
nsCOMPtr<nsIDOMNode> selNode, temp;
|
|
PRInt32 selOffset;
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
temp = selNode;
|
|
|
|
// are we in an editable node?
|
|
while (!mHTMLEditor->IsEditable(selNode))
|
|
{
|
|
// scan up the tree until we find an editable place to be
|
|
res = nsEditor::GetNodeLocation(temp, address_of(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(selNode, NS_ERROR_FAILURE);
|
|
temp = selNode;
|
|
}
|
|
|
|
// make sure we aren't in an empty block - user will see no cursor. If this
|
|
// is happening, put a <br> in the block if allowed.
|
|
nsCOMPtr<nsIDOMNode> theblock;
|
|
if (IsBlockNode(selNode)) theblock = selNode;
|
|
else theblock = mHTMLEditor->GetBlockNodeParent(selNode);
|
|
if (theblock && mHTMLEditor->IsEditable(theblock)) {
|
|
PRBool bIsEmptyNode;
|
|
res = mHTMLEditor->IsEmptyNode(theblock, &bIsEmptyNode, PR_FALSE, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// check if br can go into the destination node
|
|
if (bIsEmptyNode && mHTMLEditor->CanContainTag(selNode, NS_LITERAL_STRING("br")))
|
|
{
|
|
nsIDOMElement *rootElement = mHTMLEditor->GetRoot();
|
|
NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
|
|
nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootElement));
|
|
if (selNode == rootNode)
|
|
{
|
|
// Our root node is completely empty. Don't add a <br> here.
|
|
// AfterEditInner() will add one for us when it calls
|
|
// CreateBogusNodeIfNeeded()!
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
// we know we can skip the rest of this routine given the cirumstance
|
|
return CreateMozBR(selNode, selOffset, address_of(brNode));
|
|
}
|
|
}
|
|
|
|
// are we in a text node?
|
|
nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(selNode);
|
|
if (textNode)
|
|
return NS_OK; // we LIKE it when we are in a text node. that RULZ
|
|
|
|
// do we need to insert a special mozBR? We do if we are:
|
|
// 1) prior node is in same block where selection is AND
|
|
// 2) prior node is a br AND
|
|
// 3) that br is not visible
|
|
|
|
nsCOMPtr<nsIDOMNode> nearNode;
|
|
res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (nearNode)
|
|
{
|
|
// is nearNode also a descendant of same block?
|
|
nsCOMPtr<nsIDOMNode> block, nearBlock;
|
|
if (IsBlockNode(selNode)) block = selNode;
|
|
else block = mHTMLEditor->GetBlockNodeParent(selNode);
|
|
nearBlock = mHTMLEditor->GetBlockNodeParent(nearNode);
|
|
if (block == nearBlock)
|
|
{
|
|
if (nearNode && nsTextEditUtils::IsBreak(nearNode) )
|
|
{
|
|
if (!mHTMLEditor->IsVisBreak(nearNode))
|
|
{
|
|
// need to insert special moz BR. Why? Because if we don't
|
|
// the user will see no new line for the break. Also, things
|
|
// like table cells won't grow in height.
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = CreateMozBR(selNode, selOffset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = nsEditor::GetNodeLocation(brNode, address_of(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// selection stays *before* moz-br, sticking to it
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
res = aSelection->Collapse(selNode,selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> nextNode;
|
|
mHTMLEditor->GetNextHTMLNode(nearNode, address_of(nextNode), PR_TRUE);
|
|
if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
|
|
{
|
|
// selection between br and mozbr. make it stick to mozbr
|
|
// so that it will be on blank line.
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// we aren't in a textnode: are we adjacent to text or a break or an image?
|
|
res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (nearNode && (nsTextEditUtils::IsBreak(nearNode)
|
|
|| nsEditor::IsTextNode(nearNode)
|
|
|| nsHTMLEditUtils::IsImage(nearNode)
|
|
|| nsHTMLEditUtils::IsHR(nearNode)))
|
|
return NS_OK; // this is a good place for the caret to be
|
|
res = mHTMLEditor->GetNextHTMLNode(selNode, selOffset, address_of(nearNode), PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (nearNode && (nsTextEditUtils::IsBreak(nearNode)
|
|
|| nsEditor::IsTextNode(nearNode)
|
|
|| nsHTMLEditUtils::IsImage(nearNode)
|
|
|| nsHTMLEditUtils::IsHR(nearNode)))
|
|
return NS_OK; // this is a good place for the caret to be
|
|
|
|
// look for a nearby text node.
|
|
// prefer the correct direction.
|
|
res = FindNearSelectableNode(selNode, selOffset, aAction, address_of(nearNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (nearNode)
|
|
{
|
|
// is the nearnode a text node?
|
|
textNode = do_QueryInterface(nearNode);
|
|
if (textNode)
|
|
{
|
|
PRInt32 offset = 0;
|
|
// put selection in right place:
|
|
if (aAction == nsIEditor::ePrevious)
|
|
textNode->GetLength((PRUint32*)&offset);
|
|
res = aSelection->Collapse(nearNode,offset);
|
|
}
|
|
else // must be break or image
|
|
{
|
|
res = nsEditor::GetNodeLocation(nearNode, address_of(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (aAction == nsIEditor::ePrevious) selOffset++; // want to be beyond it if we backed up to it
|
|
res = aSelection->Collapse(selNode, selOffset);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::FindNearSelectableNode(nsIDOMNode *aSelNode,
|
|
PRInt32 aSelOffset,
|
|
nsIEditor::EDirection &aDirection,
|
|
nsCOMPtr<nsIDOMNode> *outSelectableNode)
|
|
{
|
|
NS_ENSURE_TRUE(aSelNode && outSelectableNode, NS_ERROR_NULL_POINTER);
|
|
*outSelectableNode = nsnull;
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> nearNode, curNode;
|
|
if (aDirection == nsIEditor::ePrevious)
|
|
res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
|
|
else
|
|
res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (!nearNode) // try the other direction then
|
|
{
|
|
if (aDirection == nsIEditor::ePrevious)
|
|
aDirection = nsIEditor::eNext;
|
|
else
|
|
aDirection = nsIEditor::ePrevious;
|
|
|
|
if (aDirection == nsIEditor::ePrevious)
|
|
res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
|
|
else
|
|
res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// scan in the right direction until we find an eligible text node,
|
|
// but don't cross any breaks, images, or table elements.
|
|
while (nearNode && !(mHTMLEditor->IsTextNode(nearNode)
|
|
|| nsTextEditUtils::IsBreak(nearNode)
|
|
|| nsHTMLEditUtils::IsImage(nearNode)))
|
|
{
|
|
curNode = nearNode;
|
|
if (aDirection == nsIEditor::ePrevious)
|
|
res = mHTMLEditor->GetPriorHTMLNode(curNode, address_of(nearNode));
|
|
else
|
|
res = mHTMLEditor->GetNextHTMLNode(curNode, address_of(nearNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
if (nearNode)
|
|
{
|
|
// don't cross any table elements
|
|
PRBool bInDifTblElems;
|
|
res = InDifferentTableElements(nearNode, aSelNode, &bInDifTblElems);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bInDifTblElems) return NS_OK;
|
|
|
|
// otherwise, ok, we have found a good spot to put the selection
|
|
*outSelectableNode = do_QueryInterface(nearNode);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::InDifferentTableElements(nsIDOMNode *aNode1, nsIDOMNode *aNode2, PRBool *aResult)
|
|
{
|
|
NS_ASSERTION(aNode1 && aNode2 && aResult, "null args");
|
|
NS_ENSURE_TRUE(aNode1 && aNode2 && aResult, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNode> tn1, tn2, node = aNode1, temp;
|
|
*aResult = PR_FALSE;
|
|
|
|
while (node && !nsHTMLEditUtils::IsTableElement(node))
|
|
{
|
|
node->GetParentNode(getter_AddRefs(temp));
|
|
node = temp;
|
|
}
|
|
tn1 = node;
|
|
|
|
node = aNode2;
|
|
while (node && !nsHTMLEditUtils::IsTableElement(node))
|
|
{
|
|
node->GetParentNode(getter_AddRefs(temp));
|
|
node = temp;
|
|
}
|
|
tn2 = node;
|
|
|
|
*aResult = (tn1 != tn2);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::RemoveEmptyNodes()
|
|
{
|
|
nsCOMArray<nsIDOMNode> arrayOfEmptyNodes, arrayOfEmptyCites;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
PRInt32 nodeCount,j;
|
|
|
|
// some general notes on the algorithm used here: the goal is to examine all the
|
|
// nodes in mDocChangeRange, and remove the empty ones. We do this by using a
|
|
// content iterator to traverse all the nodes in the range, and placing the empty
|
|
// nodes into an array. After finishing the iteration, we delete the empty nodes
|
|
// in the array. (they cannot be deleted as we find them becasue that would
|
|
// invalidate the iterator.)
|
|
// Since checking to see if a node is empty can be costly for nodes with many
|
|
// descendants, there are some optimizations made. I rely on the fact that the
|
|
// iterator is post-order: it will visit children of a node before visiting the
|
|
// parent node. So if I find that a child node is not empty, I know that it's
|
|
// parent is not empty without even checking. So I put the parent on a "skipList"
|
|
// which is just a voidArray of nodes I can skip the empty check on. If I
|
|
// encounter a node on the skiplist, i skip the processing for that node and replace
|
|
// it's slot in the skiplist with that node's parent.
|
|
// An interseting idea is to go ahead and regard parent nodes that are NOT on the
|
|
// skiplist as being empty (without even doing the IsEmptyNode check) on the theory
|
|
// that if they weren't empty, we would have encountered a non-empty child earlier
|
|
// and thus put this parent node on the skiplist.
|
|
// Unfortunately I can't use that strategy here, because the range may include
|
|
// some children of a node while excluding others. Thus I could find all the
|
|
// _examined_ children empty, but still not have an empty parent.
|
|
|
|
// need an iterator
|
|
nsCOMPtr<nsIContentIterator> iter =
|
|
do_CreateInstance("@mozilla.org/content/post-content-iterator;1");
|
|
NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER);
|
|
|
|
nsresult res = iter->Init(mDocChangeRange);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
nsTArray<nsIDOMNode*> skipList;
|
|
|
|
// check for empty nodes
|
|
while (!iter->IsDone())
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node, parent;
|
|
|
|
node = do_QueryInterface(iter->GetCurrentNode());
|
|
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
|
|
|
|
node->GetParentNode(getter_AddRefs(parent));
|
|
|
|
PRUint32 idx = skipList.IndexOf(node);
|
|
if (idx != skipList.NoIndex)
|
|
{
|
|
// this node is on our skip list. Skip processing for this node,
|
|
// and replace it's value in the skip list with the value of it's parent
|
|
skipList[idx] = parent;
|
|
}
|
|
else
|
|
{
|
|
PRBool bIsCandidate = PR_FALSE;
|
|
PRBool bIsEmptyNode = PR_FALSE;
|
|
PRBool bIsMailCite = PR_FALSE;
|
|
|
|
// don't delete the body
|
|
if (!nsTextEditUtils::IsBody(node))
|
|
{
|
|
// only consider certain nodes to be empty for purposes of removal
|
|
if ( (bIsMailCite = nsHTMLEditUtils::IsMailCite(node)) ||
|
|
nsEditor::NodeIsType(node, nsEditProperty::a) ||
|
|
nsHTMLEditUtils::IsInlineStyle(node) ||
|
|
nsHTMLEditUtils::IsList(node) ||
|
|
nsHTMLEditUtils::IsDiv(node) )
|
|
{
|
|
bIsCandidate = PR_TRUE;
|
|
}
|
|
// these node types are candidates if selection is not in them
|
|
else if (nsHTMLEditUtils::IsFormatNode(node) ||
|
|
nsHTMLEditUtils::IsListItem(node) ||
|
|
nsHTMLEditUtils::IsBlockquote(node) )
|
|
{
|
|
// if it is one of these, don't delete if selection inside.
|
|
// this is so we can create empty headings, etc, for the
|
|
// user to type into.
|
|
PRBool bIsSelInNode;
|
|
res = SelectionEndpointInNode(node, &bIsSelInNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bIsSelInNode)
|
|
{
|
|
bIsCandidate = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bIsCandidate)
|
|
{
|
|
if (bIsMailCite) // we delete mailcites even if they have a solo br in them
|
|
res = mHTMLEditor->IsEmptyNode(node, &bIsEmptyNode, PR_TRUE, PR_TRUE);
|
|
else // other nodes we require to be empty
|
|
res = mHTMLEditor->IsEmptyNode(node, &bIsEmptyNode, PR_FALSE, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (bIsEmptyNode)
|
|
{
|
|
if (bIsMailCite) // mailcites go on a separate list from other empty nodes
|
|
{
|
|
arrayOfEmptyCites.AppendObject(node);
|
|
}
|
|
else
|
|
{
|
|
arrayOfEmptyNodes.AppendObject(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bIsEmptyNode)
|
|
{
|
|
// put parent on skip list
|
|
skipList.AppendElement(parent);
|
|
}
|
|
}
|
|
|
|
iter->Next();
|
|
}
|
|
|
|
// now delete the empty nodes
|
|
nodeCount = arrayOfEmptyNodes.Count();
|
|
for (j = 0; j < nodeCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> delNode = arrayOfEmptyNodes[0];
|
|
arrayOfEmptyNodes.RemoveObjectAt(0);
|
|
if (mHTMLEditor->IsModifiableNode(delNode)) {
|
|
res = mHTMLEditor->DeleteNode(delNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
|
|
// now delete the empty mailcites
|
|
// this is a separate step because we want to pull out any br's and preserve them.
|
|
nodeCount = arrayOfEmptyCites.Count();
|
|
for (j = 0; j < nodeCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> delNode = arrayOfEmptyCites[0];
|
|
arrayOfEmptyCites.RemoveObjectAt(0);
|
|
PRBool bIsEmptyNode;
|
|
res = mHTMLEditor->IsEmptyNode(delNode, &bIsEmptyNode, PR_FALSE, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!bIsEmptyNode)
|
|
{
|
|
// we are deleting a cite that has just a br. We want to delete cite,
|
|
// but preserve br.
|
|
nsCOMPtr<nsIDOMNode> parent, brNode;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(delNode, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
res = mHTMLEditor->DeleteNode(delNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::SelectionEndpointInNode(nsIDOMNode *aNode, PRBool *aResult)
|
|
{
|
|
NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER);
|
|
|
|
*aResult = PR_FALSE;
|
|
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsISelectionPrivate>selPriv(do_QueryInterface(selection));
|
|
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(enumerator, NS_ERROR_UNEXPECTED);
|
|
|
|
for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next())
|
|
{
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(currentItem, NS_ERROR_UNEXPECTED);
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
nsCOMPtr<nsIDOMNode> startParent, endParent;
|
|
range->GetStartContainer(getter_AddRefs(startParent));
|
|
if (startParent)
|
|
{
|
|
if (aNode == startParent)
|
|
{
|
|
*aResult = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
if (nsEditorUtils::IsDescendantOf(startParent, aNode))
|
|
{
|
|
*aResult = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
range->GetEndContainer(getter_AddRefs(endParent));
|
|
if (startParent == endParent) continue;
|
|
if (endParent)
|
|
{
|
|
if (aNode == endParent)
|
|
{
|
|
*aResult = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
if (nsEditorUtils::IsDescendantOf(endParent, aNode))
|
|
{
|
|
*aResult = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsEmptyInline: return true if aNode is an empty inline container
|
|
//
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsEmptyInline(nsIDOMNode *aNode)
|
|
{
|
|
if (aNode && IsInlineNode(aNode) && mHTMLEditor->IsContainer(aNode))
|
|
{
|
|
PRBool bEmpty;
|
|
mHTMLEditor->IsEmptyNode(aNode, &bEmpty);
|
|
return bEmpty;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
PRBool
|
|
nsHTMLEditRules::ListIsEmptyLine(nsCOMArray<nsIDOMNode> &arrayOfNodes)
|
|
{
|
|
// we have a list of nodes which we are candidates for being moved
|
|
// into a new block. Determine if it's anything more than a blank line.
|
|
// Look for editable content above and beyond one single BR.
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
NS_ENSURE_TRUE(listCount, PR_TRUE);
|
|
nsCOMPtr<nsIDOMNode> somenode;
|
|
PRInt32 j, brCount=0;
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
somenode = arrayOfNodes[j];
|
|
if (somenode && mHTMLEditor->IsEditable(somenode))
|
|
{
|
|
if (nsTextEditUtils::IsBreak(somenode))
|
|
{
|
|
// first break doesn't count
|
|
if (brCount) return PR_FALSE;
|
|
brCount++;
|
|
}
|
|
else if (IsEmptyInline(somenode))
|
|
{
|
|
// empty inline, keep looking
|
|
}
|
|
else return PR_FALSE;
|
|
}
|
|
}
|
|
return PR_TRUE;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::PopListItem(nsIDOMNode *aListItem, PRBool *aOutOfList)
|
|
{
|
|
// check parms
|
|
NS_ENSURE_TRUE(aListItem && aOutOfList, NS_ERROR_NULL_POINTER);
|
|
|
|
// init out params
|
|
*aOutOfList = PR_FALSE;
|
|
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(aListItem));
|
|
PRInt32 offset;
|
|
nsresult res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (!nsHTMLEditUtils::IsListItem(curNode))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// if it's first or last list item, don't need to split the list
|
|
// otherwise we do.
|
|
nsCOMPtr<nsIDOMNode> curParPar;
|
|
PRInt32 parOffset;
|
|
res = nsEditor::GetNodeLocation(curParent, address_of(curParPar), &parOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRBool bIsFirstListItem;
|
|
res = mHTMLEditor->IsFirstEditableChild(curNode, &bIsFirstListItem);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
PRBool bIsLastListItem;
|
|
res = mHTMLEditor->IsLastEditableChild(curNode, &bIsLastListItem);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (!bIsFirstListItem && !bIsLastListItem)
|
|
{
|
|
// split the list
|
|
nsCOMPtr<nsIDOMNode> newBlock;
|
|
res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
if (!bIsFirstListItem) parOffset++;
|
|
|
|
res = mHTMLEditor->MoveNode(curNode, curParPar, parOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// unwrap list item contents if they are no longer in a list
|
|
if (!nsHTMLEditUtils::IsList(curParPar)
|
|
&& nsHTMLEditUtils::IsListItem(curNode))
|
|
{
|
|
res = mHTMLEditor->RemoveBlockContainer(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
*aOutOfList = PR_TRUE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::RemoveListStructure(nsIDOMNode *aList)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aList);
|
|
|
|
nsresult res;
|
|
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
aList->GetFirstChild(getter_AddRefs(child));
|
|
|
|
while (child)
|
|
{
|
|
if (nsHTMLEditUtils::IsListItem(child))
|
|
{
|
|
PRBool bOutOfList;
|
|
do
|
|
{
|
|
res = PopListItem(child, &bOutOfList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
} while (!bOutOfList); // keep popping it out until it's not in a list anymore
|
|
}
|
|
else if (nsHTMLEditUtils::IsList(child))
|
|
{
|
|
res = RemoveListStructure(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// delete any non- list items for now
|
|
res = mHTMLEditor->DeleteNode(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
aList->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
// delete the now-empty list
|
|
res = mHTMLEditor->RemoveBlockContainer(aList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::ConfirmSelectionInBody()
|
|
{
|
|
nsresult res = NS_OK;
|
|
|
|
// get the body
|
|
nsIDOMElement *rootElement = mHTMLEditor->GetRoot();
|
|
NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);
|
|
|
|
// get the selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// get the selection start location
|
|
nsCOMPtr<nsIDOMNode> selNode, temp, parent;
|
|
PRInt32 selOffset;
|
|
res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
temp = selNode;
|
|
|
|
// check that selNode is inside body
|
|
while (temp && !nsTextEditUtils::IsBody(temp))
|
|
{
|
|
res = temp->GetParentNode(getter_AddRefs(parent));
|
|
temp = parent;
|
|
}
|
|
|
|
// if we aren't in the body, force the issue
|
|
if (!temp)
|
|
{
|
|
// uncomment this to see when we get bad selections
|
|
// NS_NOTREACHED("selection not in body");
|
|
selection->Collapse(rootElement, 0);
|
|
}
|
|
|
|
// get the selection end location
|
|
res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
temp = selNode;
|
|
|
|
// check that selNode is inside body
|
|
while (temp && !nsTextEditUtils::IsBody(temp))
|
|
{
|
|
res = temp->GetParentNode(getter_AddRefs(parent));
|
|
temp = parent;
|
|
}
|
|
|
|
// if we aren't in the body, force the issue
|
|
if (!temp)
|
|
{
|
|
// uncomment this to see when we get bad selections
|
|
// NS_NOTREACHED("selection not in body");
|
|
selection->Collapse(rootElement, 0);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::UpdateDocChangeRange(nsIDOMRange *aRange)
|
|
{
|
|
nsresult res = NS_OK;
|
|
|
|
// first make sure aRange is in the document. It might not be if
|
|
// portions of our editting action involved manipulating nodes
|
|
// prior to placing them in the document (e.g., populating a list item
|
|
// before placing it in it's list)
|
|
nsCOMPtr<nsIDOMNode> startNode;
|
|
res = aRange->GetStartContainer(getter_AddRefs(startNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!mHTMLEditor->IsDescendantOfBody(startNode))
|
|
{
|
|
// just return - we don't need to adjust mDocChangeRange in this case
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mDocChangeRange)
|
|
{
|
|
// clone aRange.
|
|
res = aRange->CloneRange(getter_AddRefs(mDocChangeRange));
|
|
return res;
|
|
}
|
|
else
|
|
{
|
|
PRInt16 result;
|
|
|
|
// compare starts of ranges
|
|
res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, aRange, &result);
|
|
if (res == NS_ERROR_NOT_INITIALIZED) {
|
|
// This will happen is mDocChangeRange is non-null, but the range is
|
|
// uninitialized. In this case we'll set the start to aRange start.
|
|
// The same test won't be needed further down since after we've set
|
|
// the start the range will be collapsed to that point.
|
|
result = 1;
|
|
res = NS_OK;
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (result > 0) // positive result means mDocChangeRange start is after aRange start
|
|
{
|
|
PRInt32 startOffset;
|
|
res = aRange->GetStartOffset(&startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mDocChangeRange->SetStart(startNode, startOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// compare ends of ranges
|
|
res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, aRange, &result);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (result < 0) // negative result means mDocChangeRange end is before aRange end
|
|
{
|
|
nsCOMPtr<nsIDOMNode> endNode;
|
|
PRInt32 endOffset;
|
|
res = aRange->GetEndContainer(getter_AddRefs(endNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = aRange->GetEndOffset(&endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mDocChangeRange->SetEnd(endNode, endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::InsertMozBRIfNeeded(nsIDOMNode *aNode)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
if (!IsBlockNode(aNode)) return NS_OK;
|
|
|
|
PRBool isEmpty;
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
nsresult res = mHTMLEditor->IsEmptyNode(aNode, &isEmpty);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (isEmpty)
|
|
{
|
|
res = CreateMozBR(aNode, 0, address_of(brNode));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, PRInt32 aPosition)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidCreateNode(const nsAString& aTag,
|
|
nsIDOMNode *aNode,
|
|
nsIDOMNode *aParent,
|
|
PRInt32 aPosition,
|
|
nsresult aResult)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
// assumption that Join keeps the righthand node
|
|
nsresult res = mUtilRange->SelectNode(aNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aPosition)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidInsertNode(nsIDOMNode *aNode,
|
|
nsIDOMNode *aParent,
|
|
PRInt32 aPosition,
|
|
nsresult aResult)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
nsresult res = mUtilRange->SelectNode(aNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillDeleteNode(nsIDOMNode *aChild)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
nsresult res = mUtilRange->SelectNode(aChild);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillSplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidSplitNode(nsIDOMNode *aExistingRightNode,
|
|
PRInt32 aOffset,
|
|
nsIDOMNode *aNewLeftNode,
|
|
nsresult aResult)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
nsresult res = mUtilRange->SetStart(aNewLeftNode, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mUtilRange->SetEnd(aExistingRightNode, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
// remember split point
|
|
nsresult res = nsEditor::GetLengthOfDOMNode(aLeftNode, mJoinOffset);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidJoinNodes(nsIDOMNode *aLeftNode,
|
|
nsIDOMNode *aRightNode,
|
|
nsIDOMNode *aParent,
|
|
nsresult aResult)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
// assumption that Join keeps the righthand node
|
|
nsresult res = mUtilRange->SetStart(aRightNode, mJoinOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mUtilRange->SetEnd(aRightNode, mJoinOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsAString &aString)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidInsertText(nsIDOMCharacterData *aTextNode,
|
|
PRInt32 aOffset,
|
|
const nsAString &aString,
|
|
nsresult aResult)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
PRInt32 length = aString.Length();
|
|
nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
|
|
nsresult res = mUtilRange->SetStart(theNode, aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mUtilRange->SetEnd(theNode, aOffset+length);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidDeleteText(nsIDOMCharacterData *aTextNode,
|
|
PRInt32 aOffset,
|
|
PRInt32 aLength,
|
|
nsresult aResult)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
|
|
nsresult res = mUtilRange->SetStart(theNode, aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mUtilRange->SetEnd(theNode, aOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillDeleteRange(nsIDOMRange *aRange)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
// get the (collapsed) selection location
|
|
return UpdateDocChangeRange(aRange);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidDeleteRange(nsIDOMRange *aRange)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection)
|
|
{
|
|
if (!mListenerEnabled) {
|
|
return NS_OK;
|
|
}
|
|
// get the (collapsed) selection location
|
|
nsCOMPtr<nsIDOMNode> selNode;
|
|
PRInt32 selOffset;
|
|
|
|
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mUtilRange->SetStart(selNode, selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mUtilRange->SetEnd(selNode, selOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = UpdateDocChangeRange(mUtilRange);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
// Let's remove all alignment hints in the children of aNode; it can
|
|
// be an ALIGN attribute (in case we just remove it) or a CENTER
|
|
// element (here we have to remove the container and keep its
|
|
// children). We break on tables and don't look at their children.
|
|
nsresult
|
|
nsHTMLEditRules::RemoveAlignment(nsIDOMNode * aNode, const nsAString & aAlignType, PRBool aChildrenOnly)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
if (mHTMLEditor->IsTextNode(aNode) || nsHTMLEditUtils::IsTable(aNode)) return NS_OK;
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> child = aNode,tmp;
|
|
if (aChildrenOnly)
|
|
{
|
|
aNode->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
|
|
while (child)
|
|
{
|
|
if (aChildrenOnly) {
|
|
// get the next sibling right now because we could have to remove child
|
|
child->GetNextSibling(getter_AddRefs(tmp));
|
|
}
|
|
else
|
|
{
|
|
tmp = nsnull;
|
|
}
|
|
PRBool isBlock;
|
|
res = mHTMLEditor->NodeIsBlockStatic(child, &isBlock);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if ((isBlock && !nsHTMLEditUtils::IsDiv(child)) || nsHTMLEditUtils::IsHR(child))
|
|
{
|
|
// the current node is a block element
|
|
nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(child);
|
|
if (nsHTMLEditUtils::SupportsAlignAttr(child))
|
|
{
|
|
// remove the ALIGN attribute if this element can have it
|
|
res = mHTMLEditor->RemoveAttribute(curElem, NS_LITERAL_STRING("align"));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
if (useCSS)
|
|
{
|
|
if (nsHTMLEditUtils::IsTable(child) || nsHTMLEditUtils::IsHR(child))
|
|
{
|
|
res = mHTMLEditor->SetAttributeOrEquivalent(curElem, NS_LITERAL_STRING("align"), aAlignType, PR_FALSE);
|
|
}
|
|
else
|
|
{
|
|
nsAutoString dummyCssValue;
|
|
res = mHTMLEditor->mHTMLCSSUtils->RemoveCSSInlineStyle(child, nsEditProperty::cssTextAlign, dummyCssValue);
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
if (!nsHTMLEditUtils::IsTable(child))
|
|
{
|
|
// unless this is a table, look at children
|
|
res = RemoveAlignment(child, aAlignType, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
else if (nsEditor::NodeIsType(child, nsEditProperty::center)
|
|
|| nsHTMLEditUtils::IsDiv(child))
|
|
{
|
|
// this is a CENTER or a DIV element and we have to remove it
|
|
// first remove children's alignment
|
|
res = RemoveAlignment(child, aAlignType, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (useCSS && nsHTMLEditUtils::IsDiv(child))
|
|
{
|
|
// if we are in CSS mode and if the element is a DIV, let's remove it
|
|
// if it does not carry any style hint (style attr, class or ID)
|
|
nsAutoString dummyCssValue;
|
|
res = mHTMLEditor->mHTMLCSSUtils->RemoveCSSInlineStyle(child, nsEditProperty::cssTextAlign, dummyCssValue);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMElement> childElt = do_QueryInterface(child);
|
|
PRBool hasStyleOrIdOrClass;
|
|
res = mHTMLEditor->HasStyleOrIdOrClass(childElt, &hasStyleOrIdOrClass);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!hasStyleOrIdOrClass)
|
|
{
|
|
// we may have to insert BRs in first and last position of DIV's children
|
|
// if the nodes before/after are not blocks and not BRs
|
|
res = MakeSureElemStartsOrEndsOnCR(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->RemoveContainer(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we may have to insert BRs in first and last position of element's children
|
|
// if the nodes before/after are not blocks and not BRs
|
|
res = MakeSureElemStartsOrEndsOnCR(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// in HTML mode, let's remove the element
|
|
res = mHTMLEditor->RemoveContainer(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
child = tmp;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// Let's insert a BR as first (resp. last) child of aNode if its
|
|
// first (resp. last) child is not a block nor a BR, and if the
|
|
// previous (resp. next) sibling is not a block nor a BR
|
|
nsresult
|
|
nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode, PRBool aStarts)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
nsresult res;
|
|
if (aStarts)
|
|
{
|
|
res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(child));
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->GetLastEditableChild(aNode, address_of(child));
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(child, NS_OK);
|
|
PRBool isChildBlock;
|
|
res = mHTMLEditor->NodeIsBlockStatic(child, &isChildBlock);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRBool foundCR = PR_FALSE;
|
|
if (isChildBlock || nsTextEditUtils::IsBreak(child))
|
|
{
|
|
foundCR = PR_TRUE;
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> sibling;
|
|
if (aStarts)
|
|
{
|
|
res = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling));
|
|
}
|
|
else
|
|
{
|
|
res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling));
|
|
}
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (sibling)
|
|
{
|
|
PRBool isBlock;
|
|
res = mHTMLEditor->NodeIsBlockStatic(sibling, &isBlock);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (isBlock || nsTextEditUtils::IsBreak(sibling))
|
|
{
|
|
foundCR = PR_TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foundCR = PR_TRUE;
|
|
}
|
|
}
|
|
if (!foundCR)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
PRInt32 offset = 0;
|
|
if (!aStarts)
|
|
{
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = aNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(childNodes, NS_ERROR_NULL_POINTER);
|
|
PRUint32 childCount;
|
|
res = childNodes->GetLength(&childCount);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
offset = childCount;
|
|
}
|
|
res = mHTMLEditor->CreateBR(aNode, offset, address_of(brNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode)
|
|
{
|
|
nsresult res = MakeSureElemStartsOrEndsOnCR(aNode, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = MakeSureElemStartsOrEndsOnCR(aNode, PR_TRUE);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::AlignBlock(nsIDOMElement * aElement, const nsAString * aAlignType, PRBool aContentsOnly)
|
|
{
|
|
NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
|
|
PRBool isBlock = IsBlockNode(node);
|
|
if (!isBlock && !nsHTMLEditUtils::IsHR(node)) {
|
|
// we deal only with blocks; early way out
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult res = RemoveAlignment(node, *aAlignType, aContentsOnly);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_NAMED_LITERAL_STRING(attr, "align");
|
|
PRBool useCSS;
|
|
mHTMLEditor->GetIsCSSEnabled(&useCSS);
|
|
if (useCSS) {
|
|
// let's use CSS alignment; we use margin-left and margin-right for tables
|
|
// and text-align for other block-level elements
|
|
res = mHTMLEditor->SetAttributeOrEquivalent(aElement, attr, *aAlignType, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else {
|
|
// HTML case; this code is supposed to be called ONLY if the element
|
|
// supports the align attribute but we'll never know...
|
|
if (nsHTMLEditUtils::SupportsAlignAttr(node)) {
|
|
res = mHTMLEditor->SetAttribute(aElement, attr, *aAlignType);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::RelativeChangeIndentationOfElementNode(nsIDOMNode *aNode, PRInt8 aRelativeChange)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aNode);
|
|
|
|
if ( !( (aRelativeChange==1) || (aRelativeChange==-1) ) )
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
|
|
NS_ASSERTION(element, "not an element node");
|
|
|
|
if (element) {
|
|
nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, element);
|
|
nsAutoString value;
|
|
nsresult res;
|
|
mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(aNode, marginProperty, value);
|
|
float f;
|
|
nsIAtom * unit;
|
|
mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, &unit);
|
|
if (0 == f) {
|
|
NS_IF_RELEASE(unit);
|
|
nsAutoString defaultLengthUnit;
|
|
mHTMLEditor->mHTMLCSSUtils->GetDefaultLengthUnit(defaultLengthUnit);
|
|
unit = NS_NewAtom(defaultLengthUnit);
|
|
}
|
|
nsAutoString unitString;
|
|
unit->ToString(unitString);
|
|
if (nsEditProperty::cssInUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_IN * aRelativeChange;
|
|
else if (nsEditProperty::cssCmUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_CM * aRelativeChange;
|
|
else if (nsEditProperty::cssMmUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_MM * aRelativeChange;
|
|
else if (nsEditProperty::cssPtUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_PT * aRelativeChange;
|
|
else if (nsEditProperty::cssPcUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_PC * aRelativeChange;
|
|
else if (nsEditProperty::cssEmUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_EM * aRelativeChange;
|
|
else if (nsEditProperty::cssExUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_EX * aRelativeChange;
|
|
else if (nsEditProperty::cssPxUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_PX * aRelativeChange;
|
|
else if (nsEditProperty::cssPercentUnit == unit)
|
|
f += NS_EDITOR_INDENT_INCREMENT_PERCENT * aRelativeChange;
|
|
|
|
NS_IF_RELEASE(unit);
|
|
|
|
if (0 < f) {
|
|
nsAutoString newValue;
|
|
newValue.AppendFloat(f);
|
|
newValue.Append(unitString);
|
|
mHTMLEditor->mHTMLCSSUtils->SetCSSProperty(element, marginProperty, newValue, PR_FALSE);
|
|
}
|
|
else {
|
|
mHTMLEditor->mHTMLCSSUtils->RemoveCSSProperty(element, marginProperty, value, PR_FALSE);
|
|
if (nsHTMLEditUtils::IsDiv(aNode)) {
|
|
// we deal with a DIV ; let's see if it is useless and if we can remove it
|
|
nsCOMPtr<nsIDOMNamedNodeMap> attributeList;
|
|
res = element->GetAttributes(getter_AddRefs(attributeList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRUint32 count;
|
|
attributeList->GetLength(&count);
|
|
if (!count) {
|
|
// the DIV has no attribute at all, let's remove it
|
|
res = mHTMLEditor->RemoveContainer(element);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (1 == count) {
|
|
nsCOMPtr<nsIDOMNode> styleAttributeNode;
|
|
res = attributeList->GetNamedItem(NS_LITERAL_STRING("style"),
|
|
getter_AddRefs(styleAttributeNode));
|
|
if (!styleAttributeNode) {
|
|
res = mHTMLEditor->RemoveContainer(element);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
//
|
|
// Support for Absolute Positioning
|
|
//
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillAbsolutePosition(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore result of WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
|
|
nsCOMPtr<nsIDOMElement> focusElement;
|
|
res = mHTMLEditor->GetSelectionContainer(getter_AddRefs(focusElement));
|
|
if (focusElement) {
|
|
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(focusElement);
|
|
if (nsHTMLEditUtils::IsImage(node)) {
|
|
mNewBlock = node;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
res = NormalizeSelection(aSelection);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
|
|
nsCOMArray<nsIDOMRange> arrayOfRanges;
|
|
res = GetPromotedRanges(aSelection, arrayOfRanges, kSetAbsolutePosition);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
|
res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, kSetAbsolutePosition);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
NS_NAMED_LITERAL_STRING(divType, "div");
|
|
|
|
|
|
// if nothing visible in list, make an empty block
|
|
if (ListIsEmptyLine(arrayOfNodes))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, thePositionedDiv;
|
|
PRInt32 offset;
|
|
|
|
// get selection location
|
|
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// make sure we can put a block here
|
|
res = SplitAsNeeded(&divType, address_of(parent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(thePositionedDiv));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = thePositionedDiv;
|
|
// delete anything that was in the list of nodes
|
|
for (PRInt32 j = arrayOfNodes.Count() - 1; j >= 0; --j)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
|
|
res = mHTMLEditor->DeleteNode(curNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = arrayOfNodes.RemoveObjectAt(0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// put selection in new block
|
|
res = aSelection->Collapse(thePositionedDiv,0);
|
|
selectionResetter.Abort(); // to prevent selection reseter from overriding us.
|
|
*aHandled = PR_TRUE;
|
|
return res;
|
|
}
|
|
|
|
// Ok, now go through all the nodes and put them in a blockquote,
|
|
// or whatever is appropriate. Wohoo!
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMNode> curParent, curPositionedDiv, curList, indentedLI, sibling;
|
|
PRInt32 listCount = arrayOfNodes.Count();
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
|
|
|
// Ignore all non-editable nodes. Leave them be.
|
|
if (!mHTMLEditor->IsEditable(curNode)) continue;
|
|
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// some logic for putting list items into nested lists...
|
|
if (nsHTMLEditUtils::IsList(curParent))
|
|
{
|
|
// check to see if curList is still appropriate. Which it is if
|
|
// curNode is still right after it in the same list.
|
|
if (curList)
|
|
{
|
|
sibling = nsnull;
|
|
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
|
|
}
|
|
|
|
if (!curList || (sibling && sibling != curList) )
|
|
{
|
|
nsAutoString listTag;
|
|
nsEditor::GetTagString(curParent,listTag);
|
|
ToLowerCase(listTag);
|
|
// create a new nested list of correct type
|
|
res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!curPositionedDiv) {
|
|
PRInt32 parentOffset;
|
|
nsCOMPtr<nsIDOMNode> curParentParent;
|
|
res = nsEditor::GetNodeLocation(curParent, address_of(curParentParent), &parentOffset);
|
|
res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv));
|
|
mNewBlock = curPositionedDiv;
|
|
}
|
|
res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// curList is now the correct thing to put curNode in
|
|
// remember our new block for postprocessing
|
|
// mNewBlock = curList;
|
|
}
|
|
// tuck the node into the end of the active list
|
|
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// forget curPositionedDiv, if any
|
|
// curPositionedDiv = nsnull;
|
|
}
|
|
|
|
else // not a list item, use blockquote?
|
|
{
|
|
// if we are inside a list item, we don't want to blockquote, we want
|
|
// to sublist the list item. We may have several nodes listed in the
|
|
// array of nodes to act on, that are in the same list item. Since
|
|
// we only want to indent that li once, we must keep track of the most
|
|
// recent indented list item, and not indent it if we find another node
|
|
// to act on that is still inside the same li.
|
|
nsCOMPtr<nsIDOMNode> listitem=IsInListItem(curNode);
|
|
if (listitem)
|
|
{
|
|
if (indentedLI == listitem) continue; // already indented this list item
|
|
res = nsEditor::GetNodeLocation(listitem, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// check to see if curList is still appropriate. Which it is if
|
|
// curNode is still right after it in the same list.
|
|
if (curList)
|
|
{
|
|
sibling = nsnull;
|
|
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
|
|
}
|
|
|
|
if (!curList || (sibling && sibling != curList) )
|
|
{
|
|
nsAutoString listTag;
|
|
nsEditor::GetTagString(curParent,listTag);
|
|
ToLowerCase(listTag);
|
|
// create a new nested list of correct type
|
|
res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!curPositionedDiv) {
|
|
PRInt32 parentOffset;
|
|
nsCOMPtr<nsIDOMNode> curParentParent;
|
|
res = nsEditor::GetNodeLocation(curParent, address_of(curParentParent), &parentOffset);
|
|
res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv));
|
|
mNewBlock = curPositionedDiv;
|
|
}
|
|
res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
res = mHTMLEditor->MoveNode(listitem, curList, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember we indented this li
|
|
indentedLI = listitem;
|
|
}
|
|
|
|
else
|
|
{
|
|
// need to make a div to put things in if we haven't already
|
|
|
|
if (!curPositionedDiv)
|
|
{
|
|
if (nsHTMLEditUtils::IsDiv(curNode))
|
|
{
|
|
curPositionedDiv = curNode;
|
|
mNewBlock = curPositionedDiv;
|
|
curList = nsnull;
|
|
continue;
|
|
}
|
|
res = SplitAsNeeded(&divType, address_of(curParent), &offset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curPositionedDiv));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// remember our new block for postprocessing
|
|
mNewBlock = curPositionedDiv;
|
|
// curPositionedDiv is now the correct thing to put curNode in
|
|
}
|
|
|
|
// tuck the node into the end of the active blockquote
|
|
res = mHTMLEditor->MoveNode(curNode, curPositionedDiv, -1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// forget curList, if any
|
|
curList = nsnull;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::DidAbsolutePosition()
|
|
{
|
|
nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
|
|
nsCOMPtr<nsIDOMElement> elt = do_QueryInterface(mNewBlock);
|
|
return absPosHTMLEditor->AbsolutelyPositionElement(elt, PR_TRUE);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillRemoveAbsolutePosition(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore aCancel from WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
|
|
nsCOMPtr<nsIDOMElement> elt;
|
|
res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
|
|
return absPosHTMLEditor->AbsolutelyPositionElement(elt, PR_FALSE);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillRelativeChangeZIndex(nsISelection *aSelection,
|
|
PRInt32 aChange,
|
|
PRBool *aCancel,
|
|
PRBool * aHandled)
|
|
{
|
|
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res = WillInsert(aSelection, aCancel);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// initialize out param
|
|
// we want to ignore aCancel from WillInsert()
|
|
*aCancel = PR_FALSE;
|
|
*aHandled = PR_TRUE;
|
|
|
|
nsCOMPtr<nsIDOMElement> elt;
|
|
res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
|
|
|
nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
|
|
PRInt32 zIndex;
|
|
return absPosHTMLEditor->RelativeChangeElementZIndex(elt, aChange, &zIndex);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DocumentModified()
|
|
{
|
|
nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, &nsHTMLEditRules::DocumentModifiedWorker));
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsHTMLEditRules::DocumentModifiedWorker()
|
|
{
|
|
if (!mHTMLEditor) {
|
|
return;
|
|
}
|
|
|
|
// DeleteNode below may cause a flush, which could destroy the editor
|
|
nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
|
|
|
|
nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(mHTMLEditor);
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
|
|
NS_ENSURE_SUCCESS(res, );
|
|
|
|
// Delete our bogus node, if we have one, since the document might not be
|
|
// empty any more.
|
|
if (mBogusNode) {
|
|
mEditor->DeleteNode(mBogusNode);
|
|
mBogusNode = nsnull;
|
|
}
|
|
|
|
// Try to recreate the bogus node if needed.
|
|
CreateBogusNodeIfNeeded(selection);
|
|
}
|