diff --git a/exec/SlyEdit.js b/exec/SlyEdit.js
index 49ac58445f5fe228361eceb04c0948516c41d745..cbeea5d2eb60e1f0a7b444f1692195575ec194f9 100644
--- a/exec/SlyEdit.js
+++ b/exec/SlyEdit.js
@@ -27,6 +27,13 @@
  *                              key codes for those keys on December 17, 2018.  SlyEdit
  *                              should still work with older and newer builds of
  *                              Synchronet, with or without the updated sbbsdefs.js.
+ * 2017-12-25 Eric Oulashin     Version 1.54
+ *                              Improved quoting with author initials when a >
+ *                              character exists in the quote lines: Not mistaking
+ *                              the preceding text as a quote prefix if it has 3
+ *                              or more non-space characters before the >.  Also
+ *                              fixed an issue where wrapped quote lines were
+ *                              sometimes missing the quote line prefix.
  */
 
 /* Command-line arguments:
@@ -50,14 +57,14 @@ var EDITOR_STYLE = "DCT";
 // The second command-line argument (argv[1]) can change this.
 if (typeof(argv[1]) != "undefined")
 {
-   var styleUpper = argv[1].toUpperCase();
-   // Make sure styleUpper is valid before setting EDITOR_STYLE.
-   if (styleUpper == "DCT")
-      EDITOR_STYLE = "DCT";
-   else if (styleUpper == "ICE")
-      EDITOR_STYLE = "ICE";
-   else if (styleUpper == "RANDOM")
-      EDITOR_STYLE = (Math.floor(Math.random()*2) == 0) ? "DCT" : "ICE";
+	var styleUpper = argv[1].toUpperCase();
+	// Make sure styleUpper is valid before setting EDITOR_STYLE.
+	if (styleUpper == "DCT")
+		EDITOR_STYLE = "DCT";
+	else if (styleUpper == "ICE")
+		EDITOR_STYLE = "ICE";
+	else if (styleUpper == "RANDOM")
+		EDITOR_STYLE = (Math.floor(Math.random()*2) == 0) ? "DCT" : "ICE";
 }
 
 // Load sbbsdefs.js and SlyEdit's misc. defs first
@@ -104,8 +111,8 @@ if (!console.term_supports(USER_ANSI))
 }
 
 // Constants
-const EDITOR_VERSION = "1.53";
-const EDITOR_VER_DATE = "2017-12-19";
+const EDITOR_VERSION = "1.54";
+const EDITOR_VER_DATE = "2017-12-25";
 
 
 // Program variables
diff --git a/exec/SlyEdit_Misc.js b/exec/SlyEdit_Misc.js
index fb951ad6c0f4c9d3eb14b512329018a64da7b5dc..5265724451ee8896629780aeb97b022b8580b5dc 100644
--- a/exec/SlyEdit_Misc.js
+++ b/exec/SlyEdit_Misc.js
@@ -14,60 +14,22 @@
  * 2009-08-22 Eric Oulashin     Version 1.00
  *                              Initial public release
  * ....Removed some comments...
- * 2014-11-01 Eric Oulashin     Added getKeyWithESCChars(), along with the key definitions
- *                              KEY_PAGE_UP and KEY_PAGE_DOWN, to support inputting the
- *                              PageUp & PageDown keys from the user.
- * 2014-11-08 Eric Oulashin     Updated wrapQuoteLinesUsingAuthorInitials() and
- *                              wrapQuoteLines_NoAuthorInitials() so that if the
- *                              current line's indentation differs from the previous
- *                              line's indentation, it will mark a new section for
- *                              the quote lines so that lines of different paragraphs
- *                              don't get wrapped together.
- * 2014-11-09 Eric Oulashin     Bug fix in wrapTextLines(): For the edge case when
- *                              text is trimmed from the end of the last line in
- *                              the paragraph, it will insert a new line in the
- *                              array at the end of the paragraph for the trimmed
- *                              text.  For lines before the last line in the
- *                              paragraph, it will just prepend the text to the
- *                              next line in the array.  Also, updated
- *                              wrapQuoteLinesUsingAuthorInitials() to trim leading
- *                              spaces from non-quote text sections to leave more
- *                              room for wrapping the lines and to avoid having
- *                              whole sections of quote lines that start with
- *                              several spaces.  Also made a similar update to
- *                              wrapQuoteLines_NoAuthorInitials().
- * 2014-11-11 Eric Oulashin     Bug fix in wrapTextLines(): After wrapping a line
- *                              of text, there was a section of code that would
- *                              check to see if the next line is blank and add
- *                              another line if so, which was causing an extra
- *                              blank line to be added in some situations where
- *                              that shouldn't happen.  Seems to be fixed after
- *                              an update.  Also, made another bug fix in that
- *                              function where sometimes a leading space would
- *                              be added to wrapped text on a new line.  That
- *                              seems to be fixed as well.
- * 2015-07-10 Eric Oulashin     Bug fix in wrapTextLines(): If the pLineWidth
- *                              parameter is less than 0 or not a number, then
- *                              the function will simply return, to avoid bad
- *                              behavior.  I noticed what looked like an infinite
- *                              loop if pLineWidth was negative.
- *                              Bug fix in wrapQuoteLinesUsingAuthorInitials():
- *                              When wrapping text lines, it will only call
- *                              wrapTextLines() if the maximum line length is
- *                              positive.
- * 2016-04-23 Eric Oulashin     Updated to load text.js for text.dat definitions.
- *                              Updated displayCommandList() to use console.pause()
- *                              instead of a custom pause routine so that
- *                              Synchronet can use a custom pause script if one
- *                              is configured in text.dat.
- * 2017-08-04 Eric Oulashin     Updated wrapQuoteLines(), wrapQuoteLinesUsingAuthorInitials(),
- *                              and wrapQuoteLines_NoAuthorInitials() - Added a
- *                              parameter for whether or not to trim spaces from
- *                              quote lines.
  * 2017-12-16 Eric Oulashin     Updated ReadSlyEditConfigFile() to include the
  *                              allowEditQuoteLines option.
  * 2017-12-18 Eric Oulashin     Update the KEY_PAGE_UP and KEY_PAGE_DOWN keys to
  *                              ensure they mat what's in sbbsdef.js
+ * 2017-12-24 Eric Oulashin     Updated firstNonQuoteTxtIndex() to better handle
+ *                              lines with 3 non-space characters before a >, to
+ *                              not consider those sequences a quote when using
+ *                              author initials.  When using author initials,
+ *                              SlyEdit considers a quote sequence to only have 2
+ *                              non-space characters (such as "EO>").  Also
+ *                              updated wrapQuoteLines() - Added an optional
+ *                              parameter for the lineInfo object array so it
+ *                              can be updated when lines are split (for quoting
+ *                              with author initials).  That should fix an
+ *                              issue where some wrapped/split quote lines
+ *                              were missing the quote line prefix.
  */
  
  load("text.js");
@@ -211,27 +173,27 @@ function TextLine(pText, pHardNewlineEnd, pIsQuoteLine)
 	this.text = "";               // The line text
 	this.hardNewlineEnd = false; // Whether or not the line has a hard newline at the end
 	this.isQuoteLine = false;    // Whether or not this is a quote line
-   // Copy the parameters if they are valid.
-   if ((pText != null) && (typeof(pText) == "string"))
-      this.text = pText;
-   if ((pHardNewlineEnd != null) && (typeof(pHardNewlineEnd) == "boolean"))
-      this.hardNewlineEnd = pHardNewlineEnd;
-   if ((pIsQuoteLine != null) && (typeof(pIsQuoteLine) == "boolean"))
-      this.isQuoteLine = pIsQuoteLine;
+	// Copy the parameters if they are valid.
+	if ((pText != null) && (typeof(pText) == "string"))
+		this.text = pText;
+	if ((pHardNewlineEnd != null) && (typeof(pHardNewlineEnd) == "boolean"))
+		this.hardNewlineEnd = pHardNewlineEnd;
+	if ((pIsQuoteLine != null) && (typeof(pIsQuoteLine) == "boolean"))
+		this.isQuoteLine = pIsQuoteLine;
 
 	// NEW & EXPERIMENTAL:
-   // For color support
-   this.attrs = new Array(); // An array of attributes for the line
-   // Functions
-   this.length = TextLine_Length;
-   this.print = TextLine_Print;
-   this.doMacroTxtReplacement = TextLine_doMacroTxtReplacement;
-   this.getWord = TextLine_getWord;
+	// For color support
+	this.attrs = new Array(); // An array of attributes for the line
+	// Functions
+	this.length = TextLine_Length;
+	this.print = TextLine_Print;
+	this.doMacroTxtReplacement = TextLine_doMacroTxtReplacement;
+	this.getWord = TextLine_getWord;
 }
 // For the TextLine class: Returns the length of the text.
 function TextLine_Length()
 {
-   return this.text.length;
+	return this.text.length;
 }
 // For  the TextLine class: Prints the text line, using its text attributes.
 //
@@ -239,10 +201,10 @@ function TextLine_Length()
 //  pClearToEOL: Boolean - Whether or not to clear to the end of the line
 function TextLine_Print(pClearToEOL)
 {
-   console.print(this.text);
+	console.print(this.text);
 
-   if (pClearToEOL)
-      console.cleartoeol();
+	if (pClearToEOL)
+		console.cleartoeol();
 }
 // Performs text replacement (AKA macro replacement) in the text line.
 //
@@ -270,89 +232,88 @@ function TextLine_Print(pClearToEOL)
 //                                   (boolean)
 function TextLine_doMacroTxtReplacement(pTxtReplacements, pCharIndex, pUseRegex)
 {
-   var retObj = new Object();
-   retObj.textLineIndex = pCharIndex;
-   retObj.wordLenDiff = 0;
-   retObj.wordStartIdx = 0;
-   retObj.newTextEndIdx = 0;
-   retObj.newTextLen = 0;
-   retObj.madeTxtReplacement = false;
-
-   var wordObj = this.getWord(retObj.textLineIndex);
-   if (wordObj.foundWord)
-   {
-      retObj.wordStartIdx = wordObj.startIdx;
-      retObj.newTextLen = wordObj.word.length;
-
-      // See if the word starts with a capital letter; if so, we'll capitalize
-      // the replacement word.
-      var firstCharUpper = false;
-      var txtReplacement = "";
-      if (pUseRegex)
-      {
-         // Since a regular expression might have more characters in addition
-         // to the actual word, we need to go through all the replacement strings
-         // in pTxtReplacements and use the first one that changes the text.
-         for (var prop in pTxtReplacements)
-         {
-            if (pTxtReplacements.hasOwnProperty(prop))
-            {
-               var regex = new RegExp(prop);
-               txtReplacement = wordObj.word.replace(regex, pTxtReplacements[prop]);
-               retObj.madeTxtReplacement = (txtReplacement != wordObj.word);
-               // If a text replacement was made, then check and see if the first
-               // letter in the original text was uppercase, and if so, make the
-               // first letter in the new text (txtReplacement) uppercase.
-               if (retObj.madeTxtReplacement)
-               {
-                  if (firstLetterIsUppercase(wordObj.word))
-                  {
-                     var letterInfo = getFirstLetterFromStr(txtReplacement);
-                     if (letterInfo.idx > -1)
-                     {
-                        txtReplacement = txtReplacement.substr(0, letterInfo.idx)
-                                       + letterInfo.letter.toUpperCase()
-                                       + txtReplacement.substr(letterInfo.idx+1);
-                     }
-                  }
-                  // Now that we've made a text replacement, stop going through
-                  // pTxtReplacements looking for a matching regex.
-                  break;
-               }
-            }
-         }
-      }
-      else
-      {
-         // Not using a regular expression.
-         firstCharUpper = (wordObj.word.charAt(0) == wordObj.word.charAt(0).toUpperCase());
-         // Convert the word to all uppercase to do the case-insensitive lookup
-         // in pTxtReplacements.
-         wordObj.word = wordObj.word.toUpperCase();
-         if (pTxtReplacements.hasOwnProperty(wordObj.word))
-         {
-            txtReplacement = pTxtReplacements[wordObj.word];
-            retObj.madeTxtReplacement = true;
-         }
-      }
-      if (retObj.madeTxtReplacement)
-      {
-         if (firstCharUpper)
-            txtReplacement = txtReplacement.charAt(0).toUpperCase() + txtReplacement.substr(1);
-         this.text = this.text.substr(0, wordObj.startIdx) + txtReplacement
-                   + this.text.substr(wordObj.endIndex+1);
-         // Based on the difference in word length, update the data that
-         // matters (retObj.textLineIndex, which keeps track of the index of the current line).
-         // Note: The horizontal cursor position variable should be replaced after calling this
-         // function.
-         retObj.wordLenDiff = txtReplacement.length - wordObj.word.length;
-         retObj.textLineIndex += retObj.wordLenDiff;
-         retObj.newTextEndIdx = wordObj.endIndex + retObj.wordLenDiff;
-         retObj.newTextLen = txtReplacement.length;
-      }
-   }
+	var retObj = new Object();
+	retObj.textLineIndex = pCharIndex;
+	retObj.wordLenDiff = 0;
+	retObj.wordStartIdx = 0;
+	retObj.newTextEndIdx = 0;
+	retObj.newTextLen = 0;
+	retObj.madeTxtReplacement = false;
+
+	var wordObj = this.getWord(retObj.textLineIndex);
+	if (wordObj.foundWord)
+	{
+		retObj.wordStartIdx = wordObj.startIdx;
+		retObj.newTextLen = wordObj.word.length;
+
+		// See if the word starts with a capital letter; if so, we'll capitalize
+		// the replacement word.
+		var firstCharUpper = false;
+		var txtReplacement = "";
+		if (pUseRegex)
+		{
+			// Since a regular expression might have more characters in addition
+			// to the actual word, we need to go through all the replacement strings
+			// in pTxtReplacements and use the first one that changes the text.
+			for (var prop in pTxtReplacements)
+			{
+				if (pTxtReplacements.hasOwnProperty(prop))
+				{
+					var regex = new RegExp(prop);
+					txtReplacement = wordObj.word.replace(regex, pTxtReplacements[prop]);
+					retObj.madeTxtReplacement = (txtReplacement != wordObj.word);
+					// If a text replacement was made, then check and see if the first
+					// letter in the original text was uppercase, and if so, make the
+					// first letter in the new text (txtReplacement) uppercase.
+					if (retObj.madeTxtReplacement)
+					{
+						if (firstLetterIsUppercase(wordObj.word))
+						{
+							var letterInfo = getFirstLetterFromStr(txtReplacement);
+							if (letterInfo.idx > -1)
+							{
+								txtReplacement = txtReplacement.substr(0, letterInfo.idx)
+								               + letterInfo.letter.toUpperCase()
+								               + txtReplacement.substr(letterInfo.idx+1);
+							}
+						}
+						// Now that we've made a text replacement, stop going through
+						// pTxtReplacements looking for a matching regex.
+						break;
+					}
+				}
+			}
+		}
+		else
+		{
+			// Not using a regular expression.
+			firstCharUpper = (wordObj.word.charAt(0) == wordObj.word.charAt(0).toUpperCase());
+			// Convert the word to all uppercase to do the case-insensitive lookup
+			// in pTxtReplacements.
+			wordObj.word = wordObj.word.toUpperCase();
+			if (pTxtReplacements.hasOwnProperty(wordObj.word))
+			{
+				txtReplacement = pTxtReplacements[wordObj.word];
+				retObj.madeTxtReplacement = true;
+			}
+		}
+		if (retObj.madeTxtReplacement)
+		{
+			if (firstCharUpper)
+			txtReplacement = txtReplacement.charAt(0).toUpperCase() + txtReplacement.substr(1);
+			this.text = this.text.substr(0, wordObj.startIdx) + txtReplacement + this.text.substr(wordObj.endIndex+1);
+			// Based on the difference in word length, update the data that
+			// matters (retObj.textLineIndex, which keeps track of the index of the current line).
+			// Note: The horizontal cursor position variable should be replaced after calling this
+			// function.
+			retObj.wordLenDiff = txtReplacement.length - wordObj.word.length;
+			retObj.textLineIndex += retObj.wordLenDiff;
+			retObj.newTextEndIdx = wordObj.endIndex + retObj.wordLenDiff;
+			retObj.newTextLen = txtReplacement.length;
+		}
+	}
 
-   return retObj;
+	return retObj;
 }
 // Returns the word in a text line at a given index.  If the index
 // is at a space, then this function will return the word before
@@ -374,39 +335,40 @@ function TextLine_doMacroTxtReplacement(pTxtReplacements, pCharIndex, pUseRegex)
 //                         This includes any control/color codes, etc.
 function TextLine_getWord(pCharIndex)
 {
-   var retObj = new Object();
-   retObj.foundWord = false;
-   retObj.word = "";
-   retObj.plainWord = "";
-   retObj.startIdx = 0;
-   retObj.endIndex = 0;
-
-   // Parameter checking
-   if ((pCharIndex < 0) || (pCharIndex >= this.text.length))
-      return retObj;
+	var retObj = {
+		foundWord: false,
+		word: "",
+		plainWord: "",
+		startIdx: 0,
+		endIndex: 0
+	};
 
-   // If pCharIndex specifies the index of a space, then look for a non-space
-   // character before it.
-   var charIndex = pCharIndex;
-   while (this.text.charAt(charIndex) == " ")
-      --charIndex;
-   // Look for the start & end of the word based on the indexes of a space
-   // before and at/after the given character index.
-   var wordStartIdx = charIndex;
-   var wordEndIdx = charIndex;
-   while ((this.text.charAt(wordStartIdx) != " ") && (wordStartIdx >= 0))
-      --wordStartIdx;
-   ++wordStartIdx;
-   while ((this.text.charAt(wordEndIdx) != " ") && (wordEndIdx < this.text.length))
-      ++wordEndIdx;
-   --wordEndIdx;
-
-   retObj.foundWord = true;
-   retObj.startIdx = wordStartIdx;
-   retObj.endIndex = wordEndIdx;
-   retObj.word = this.text.substring(wordStartIdx, wordEndIdx+1);
-   retObj.plainWord = strip_ctrl(retObj.word);
-   return retObj;
+	// Parameter checking
+	if ((pCharIndex < 0) || (pCharIndex >= this.text.length))
+		return retObj;
+
+	// If pCharIndex specifies the index of a space, then look for a non-space
+	// character before it.
+	var charIndex = pCharIndex;
+	while (this.text.charAt(charIndex) == " ")
+		--charIndex;
+	// Look for the start & end of the word based on the indexes of a space
+	// before and at/after the given character index.
+	var wordStartIdx = charIndex;
+	var wordEndIdx = charIndex;
+	while ((this.text.charAt(wordStartIdx) != " ") && (wordStartIdx >= 0))
+		--wordStartIdx;
+	++wordStartIdx;
+	while ((this.text.charAt(wordEndIdx) != " ") && (wordEndIdx < this.text.length))
+		++wordEndIdx;
+	--wordEndIdx;
+
+	retObj.foundWord = true;
+	retObj.startIdx = wordStartIdx;
+	retObj.endIndex = wordEndIdx;
+	retObj.word = this.text.substring(wordStartIdx, wordEndIdx+1);
+	retObj.plainWord = strip_ctrl(retObj.word);
+	return retObj;
 }
 
 
@@ -415,12 +377,12 @@ function TextLine_getWord(pCharIndex)
 // IceEdit and DCT Edit styles).
 function AbortConfirmFuncParams()
 {
-   this.editTop = gEditTop;
-   this.editBottom = gEditBottom;
-   this.editWidth = gEditWidth;
-   this.editHeight = gEditHeight;
-   this.editLinesIndex = gEditLinesIndex;
-   this.displayMessageRectangle = displayMessageRectangle;
+	this.editTop = gEditTop;
+	this.editBottom = gEditBottom;
+	this.editWidth = gEditWidth;
+	this.editHeight = gEditHeight;
+	this.editLinesIndex = gEditLinesIndex;
+	this.displayMessageRectangle = displayMessageRectangle;
 }
 
 //////
@@ -429,7 +391,7 @@ function AbortConfirmFuncParams()
 // Returns the minimum width for a ChoiceScrollbox
 function ChoiceScrollbox_MinWidth()
 {
-   return 73; // To leave room for the navigation text in the bottom border
+	return 73; // To leave room for the navigation text in the bottom border
 }
 
 // ChoiceScrollbox constructor
@@ -2370,9 +2332,12 @@ function toggleAttr(pAttrType, pAttrs, pNewAttr)
 //  pLineWidth: The maximum width of each line
 //  pIdxesRequiringNL (OUT): Optional - An array to contain the indexes of original
 //                           wrapped lines that required a new line to be added.
+//  pLineInfos (IN/OUT): Optional - An array of lineInfo objects previously generated
+//                       for the unwrapped lines - This will be updated if lines are
+//                       wrapped.
 //
 // Return value: The number of new lines added
-function wrapTextLines(pLineArr, pStartLineIndex, pEndIndex, pLineWidth, pIdxesRequiringNL)
+function wrapTextLines(pLineArr, pStartLineIndex, pEndIndex, pLineWidth, pIdxesRequiringNL, pLineInfos)
 {
 	// Validate parameters
 	if (pLineArr == null)
@@ -2418,38 +2383,25 @@ function wrapTextLines(pLineArr, pStartLineIndex, pEndIndex, pLineWidth, pIdxesR
 				// Append a space to the end of the trimmed text if it doesn't have one.
 				if ((trimmedText.length > 0) && (trimmedText.charAt(trimmedText.length-1) != " "))
 					trimmedText += " "
-				// 2015-01-11: The following commented-out code is older - It has a bug
-				// that would sometimes cause an extra empty line to be inserted.
-				/*
-				// If the next line is blank, then append another blank
-				// line there to preserve the message's formatting.
-				//if (pLineArr[i+1].length == 0)
-				if (false) // For testing, to see if the above check really isn't necessary
-				{
-					pLineArr.splice(i+1, 0, "");
-					// If the current line index is before the specified end index, then
-					// increment the end index since we've added a line in order to continue
-					// wrapping the lines.
-					if (i < pEndIndex-1)
-						++pEndIndex;
-
-					if (pNewLineIndexesIsArray)
-						pIdxesRequiringNL.push(i);
-				}
-				else
-				{
-					// Since the next line is not blank, then append a space
-					// to the end of the trimmed text if it doesn't have one.
-					if ((trimmedText.length > 0) && (trimmedText.charAt(trimmedText.length-1) != " "))
-						trimmedText += " "
-				}
-				*/
 				// Prepend the trimmed text to the next line.  If the next line's index
 				// is within the paragraph we're wrapping, then go ahead and prepend the
 				// text to the next line.  Otherwise, add a new line to the array and
 				// add the text to the new line.
 				if (i+1 < pEndIndex)
+				{
 					pLineArr[i+1] = trimmedText + pLineArr[i+1];
+					// Copy the current line's lineInfo object to the next
+					// one in the array
+					if (typeof(pLineInfos) == "object")
+					{
+						if (pLineInfos.length > i+1)
+						{
+							pLineInfos[i+1].startIndex = pLineInfos[i].startIndex;
+							pLineInfos[i+1].quoteLevel = pLineInfos[i].quoteLevel;
+							pLineInfos[i+1].begOfLine = pLineInfos[i].begOfLine;
+						}
+					}
+				}
 				else
 				{
 					// Add the trimmed text on a new line in the array.  Then, if the
@@ -2463,8 +2415,12 @@ function wrapTextLines(pLineArr, pStartLineIndex, pEndIndex, pLineWidth, pIdxesR
 					else
 					{
 						if (pNewLineIndexesIsArray)
-						pIdxesRequiringNL.push(i);
+							pIdxesRequiringNL.push(i);
 					}
+					// Append a lineInfo object to pLineInfos as a copy of the
+					// last one in the array.
+					if (typeof(pLineInfos) == "object")
+						pLineInfos.push(pLineInfos[pLineInfos.length-1]);
 				}
 			}
 			else
@@ -2482,6 +2438,7 @@ function wrapTextLines(pLineArr, pStartLineIndex, pEndIndex, pLineWidth, pIdxesR
 			}
 		}
 	}
+
 	return(pLineArr.length - origNumLines);
 }
 
@@ -2495,16 +2452,17 @@ function wrapTextLines(pLineArr, pStartLineIndex, pEndIndex, pLineWidth, pIdxesR
 //                          This defaults to a blank string.
 function getDefaultQuoteStrObj()
 {
-  var retObj = new Object();
-  retObj.startIndex = -1;
-  retObj.quoteLevel = 0;
-  retObj.begOfLine = ""; // Will store the beginning of the line, before the >
-  retObj.copy = function(pThatQuoteStrObj) {
-    this.startIndex = pThatQuoteStrObj.startIndex;
-    this.quoteLevel = pThatQuoteStrObj.quoteLevel;
-    this.begOfLine = pThatQuoteStrObj.begOfLine;
-  };
-  return retObj;
+	var retObj = {
+		startIndex: -1,
+		quoteLevel: 0,
+		begOfLine: "", // Will store the beginning of the line, before the >
+		copy: function(pThatQuoteStrObj) {
+			this.startIndex = pThatQuoteStrObj.startIndex;
+			this.quoteLevel = pThatQuoteStrObj.quoteLevel;
+			this.begOfLine = pThatQuoteStrObj.begOfLine;
+		}
+	};
+	return retObj;
 }
 
 // Searches a string for the index of the first non-quote character; also finds
@@ -2523,184 +2481,235 @@ function getDefaultQuoteStrObj()
 //                           If pStr is an invalid string, or if a non-quote character
 //                           is not found, this will be -1.
 //               quoteLevel: The number of > characters at the start of the string
-//               begOfLine: The quote text at the beginng of the line
+//               begOfLine: The quote text at the beginning of the line
 function firstNonQuoteTxtIndex(pStr, pUseAuthorInitials, pIndentQuoteLinesWithInitials)
 {
-  // Create the return object with initial values.
-  var retObj = getDefaultQuoteStrObj();  
-
-  // If pStr is not a valid positive-length string, then just return.
-  if ((pStr == null) || (typeof(pStr) != "string") || (pStr.length == 0))
-    return retObj;
-
-  // If using author initials, then do some special checking: If the first >
-  // character is preceded by something other than spaces or 3 non-space characters,
-  // then this string is probably not quoted, so return an object that signifies
-  // such.
-  if (pUseAuthorInitials)
-  {
-    var firstGTCharIdx = pStr.indexOf(">");
-    if (firstGTCharIdx > -1)
-    {
-      // double-quoted text: If there are only spaces, > characters, or
-      // up to 3 characters directly before the >> (without spaces), then
-      // take this as a valid instance of double-quoted text.
-      var upToThreeNonSpacesBefore = false;
-      var onlySpaces = true;
-      var currentChar;
-      for (var srchIdx = 0; (srchIdx < pStr.length) && onlySpaces; ++srchIdx)
-        onlySpaces = (pStr.charAt(srchIdx) == " ");
-      if (!onlySpaces)
-      {
-        var startIdxBeforeGT = firstGTCharIdx - 4;
-        if (startIdxBeforeGT < 0)
-          startIdxBeforeGT = 0;
-        // If the string don't contain a non > followed by a space before the >, then
-        // go ahead and check the first 3 characters before the >.  Otherwise, it's
-        // already disqualified.
-        if (!/[^>] /.test(pStr.substr(startIdxBeforeGT, firstGTCharIdx-startIdxBeforeGT)))
-        {
-          upToThreeNonSpacesBefore = true;
-          var numNonSpaceChars = 0;
-          for (var srchIdx = firstGTCharIdx-1; srchIdx >= startIdxBeforeGT; --srchIdx)
-          {
-            if (pStr.charAt(srchIdx) != " ")
-              ++numNonSpaceChars;
-          }
-          upToThreeNonSpacesBefore = (numNonSpaceChars < 4);
-        }
-      }
+	// Create the return object with initial values.
+	var retObj = getDefaultQuoteStrObj();  
 
-      // If there aren't just spaces or up to 3 non-space characters just before
-      // the first >, then return an object that signifies this situation properly.
-      if (!onlySpaces && !upToThreeNonSpacesBefore)
-      {
-        retObj.startIndex = 0;
-        retObj.quoteLevel = 0;
-        retObj.begOfLine = "";
-        return retObj;
-      }
-    }
-  }
+	// If pStr is not a valid positive-length string, then just return.
+	if ((pStr == null) || (typeof(pStr) != "string") || (pStr.length == 0))
+		return retObj;
 
-  // Look for quote lines that begin with 1 or 2 initials followed by a > (i.e.,
-  // "EO>" or "E>" at the start of the line.  If found, set an index to look for
-  // & count the > characters from the >.
-  var searchStartIndex = 0;
-  // Regex notes:
-  //  \w: Matches any alphanumeric character (word characters) including underscore (short for [a-zA-Z0-9_])
-  //  ?: Supposed to match 0 or 1 occurance, but seems to match 1 or 2
-  // First, look for spaces then 1 or 2 initials followed by a non-space followed
-  // by a >.  If not found, then look for ">>".  If that isn't found, then look
-  // for just 2 characters followed by a >.
-  var lineStartsWithQuoteText = /^ *\w?[^ ]>/.test(pStr);
-  if (pUseAuthorInitials)
-  {
-    if (!lineStartsWithQuoteText)
-      lineStartsWithQuoteText = (pStr.lastIndexOf(">>") > -1);
-    if (!lineStartsWithQuoteText)
-      lineStartsWithQuoteText = /\w{2}>/.test(pStr);
-  }
-  if (lineStartsWithQuoteText)
-  {
-    if (pUseAuthorInitials)
-    {
-      // If the string is an origin line (starting with " * Origin:"), then don't
-      // do much with this line..  Just set the first non-space character in retObj.
-      if (/^ \* Origin:/.test(pStr))
-        retObj.startIndex = 1;
-      else
-      {
-         // First, look for the last instance of ">> " (signifying a multi-quoted line).
-         // If found, increment searchStartIndex by 2 to get past the ">>".
-         var validDoubleQuoteChars = false;
-         searchStartIndex = pStr.lastIndexOf(">> ");
-         if (searchStartIndex > -1)
-           searchStartIndex += 2;
-         else
-         {
-           // If pStr is at least 3 characters long, then starting with the
-           // last 3 characters in pStr, look for an instance of 2 letters
-           // or numbers or underscores followed by a >.  Keep moving back
-           // 1 character at a time until found or until the beginning of
-           // the string is reached.
-           if (pStr.length >= 3)
-           {
-             // Regex notes:
-             //  \w: Matches any alphanumeric character (word characters) including underscore (short for [a-zA-Z0-9_])
-             var substrStartIndex = pStr.length - 3;
-             for (; (substrStartIndex >= 0) && (searchStartIndex < 0); --substrStartIndex)
-               searchStartIndex = pStr.substr(substrStartIndex, 3).search(/\w{2}>/);
-             ++substrStartIndex; // To fix off-by-one
-             if (searchStartIndex > -1)
-               searchStartIndex += substrStartIndex + 3; // To get past the "..>"
-                                                         // Note: I originally had + 4 here..
-             if (searchStartIndex < 0)
-             {
-               searchStartIndex = pStr.indexOf(">");
-               if (searchStartIndex < 0)
-                 searchStartIndex = 0;
-             }
-           }
-           else
-           {
-             searchStartIndex = pStr.indexOf(">");
-             if (searchStartIndex < 0)
-               searchStartIndex = 0;
-           }
-        }
-      }
-    }
-    else
-    {
-      // SlyEdit is not prefixing quote lines with author's initials.
-      searchStartIndex = pStr.indexOf(">");
-      if (searchStartIndex < 0)
-        searchStartIndex = 0;
-    }
-  }
+	// If using author initials, then do some special checking: If the first >
+	// character is preceded by something other than spaces or 3 non-space characters,
+	// then this string is probably not quoted, so return an object that signifies
+	// such.
+	if (pUseAuthorInitials)
+	{
+		var firstGTCharIdx = pStr.indexOf(">");
+		if (firstGTCharIdx > -1)
+		{
+			// double-quoted text: If there are only spaces, > characters, or
+			// up to 3 characters directly before the >> (without spaces), then
+			// take this as a valid instance of double-quoted text.
+			var upToThreeNonSpacesBefore = false;
+			var onlySpaces = true;
+			var currentChar;
+			for (var srchIdx = 0; (srchIdx < pStr.length) && onlySpaces; ++srchIdx)
+				onlySpaces = (pStr.charAt(srchIdx) == " ");
+			if (!onlySpaces)
+			{
+				var startIdxBeforeGT = firstGTCharIdx - 4;
+				if (startIdxBeforeGT < 0)
+					startIdxBeforeGT = 0;
+				// If the string don't contain a non > followed by a space before the >, then
+				// go ahead and check the first 3 characters before the >.  Otherwise, it's
+				// already disqualified.
+				if (!/[^>] /.test(pStr.substr(startIdxBeforeGT, firstGTCharIdx-startIdxBeforeGT)))
+				{
+					upToThreeNonSpacesBefore = true;
+					var numNonSpaceChars = 0;
+					for (var srchIdx = firstGTCharIdx-1; srchIdx >= startIdxBeforeGT; --srchIdx)
+					{
+						if (pStr.charAt(srchIdx) != " ")
+							++numNonSpaceChars;
+					}
+					upToThreeNonSpacesBefore = (numNonSpaceChars < 4);
+				}
+			}
 
-  // Find the quote level and the beginning of the line.
-  // Look for the first non-quote text and quote level in the string.
-  var strChar = "";
-  var j = 0;
-  for (var i = searchStartIndex; i < pStr.length; ++i)
-  {
-    strChar = pStr.charAt(i);
-    if ((strChar != " ") && (strChar != ">"))
-    {
-      // We've found the first non-quote character.
-      retObj.startIndex = i;
-      // Count the number of times the > character appears at the start of
-      // the line, and set quoteLevel to that.
-      if (i >= 0)
-      {
-        for (j = 0; j < i; ++j)
-        {
-          if (pStr.charAt(j) == ">")
-            ++retObj.quoteLevel;
-        }
-      }
-      // Store the beginning of the line in retObj.begOfLine.  And if
-      // SlyEdit is configured to indent quote lines with author initials,
-      // and if the beginning of the line doesn't begin with a space,
-      // then add a space to the beginning of it.
-      retObj.begOfLine = pStr.substr(0, retObj.startIndex);
-      if (pUseAuthorInitials && pIndentQuoteLinesWithInitials && (retObj.begOfLine.length > 0) && (retObj.begOfLine.charAt(0) != " "))
-        retObj.begOfLine = " " + retObj.begOfLine;
-      break;
-    }
-  }
+			// If there aren't just spaces or up to 3 non-space characters just before
+			// the first >, then return an object that signifies this situation properly.
+			if (!onlySpaces && !upToThreeNonSpacesBefore)
+			{
+				retObj.startIndex = 0;
+				retObj.quoteLevel = 0;
+				retObj.begOfLine = "";
+				return retObj;
+			}
+		}
+	}
 
-  // If we haven't found non-quote text but the line starts with quote text,
-  // then set the starting index & quote level in retObj.
-  if (lineStartsWithQuoteText && ((retObj.startIndex == -1) || (retObj.quoteLevel == 0)))
-  {
-    retObj.startIndex = pStr.indexOf(">") + 1;
-    retObj.quoteLevel = 1;
-  }
+	// Look for quote lines that begin with 1 or 2 initials followed by a > (i.e.,
+	// "EO>" or "E>" at the start of the line.  If found, set an index to look for
+	// & count the > characters from the >.
+	var searchStartIndex = 0;
+	// Regex notes:
+	//  \w: Matches any alphanumeric character (word characters) including underscore (short for [a-zA-Z0-9_])
+	//  ?: Supposed to match 0 or 1 occurance, but seems to match 1 or 2
+	// First, look for spaces then 1 or 2 initials followed by a non-space followed
+	// by a >.  If not found, then look for ">>".  If that isn't found, then look
+	// for just 2 characters followed by a >.
+	var lineStartsWithQuoteText = /^ *\w?[^ ]>/.test(pStr);
+	if (pUseAuthorInitials)
+	{
+		if (!lineStartsWithQuoteText)
+			lineStartsWithQuoteText = (pStr.lastIndexOf(">>") > -1);
+		if (!lineStartsWithQuoteText)
+			lineStartsWithQuoteText = /\w{2}>/.test(pStr);
+	}
+	if (lineStartsWithQuoteText)
+	{
+		if (pUseAuthorInitials)
+		{
+			// If the string is an origin line (starting with " * Origin:"), then don't
+			// do much with this line..  Just set the first non-space character in retObj.
+			if (/^ \* Origin:/.test(pStr))
+				retObj.startIndex = 1;
+			else
+			{
+				// First, look for the last instance of ">> " (signifying a multi-quoted line).
+				// If found, increment searchStartIndex by 2 to get past the ">>".
+				var validDoubleQuoteChars = false;
+				searchStartIndex = pStr.lastIndexOf(">> ");
+				if (searchStartIndex > -1)
+					searchStartIndex += 2;
+				else
+				{
+					// If pStr is at least 3 characters long, then starting with the
+					// last 3 characters in pStr, look for an instance of 2 letters
+					// or numbers or underscores followed by a >.  Keep moving back
+					// 1 character at a time until found or until the beginning of
+					// the string is reached.
+					if (pStr.length >= 3)
+					{
+						// Regex notes:
+						//  \w: Matches any alphanumeric character (word characters) including underscore (short for [a-zA-Z0-9_])
+						var substrStartIndex = pStr.length - 3;
+						for (; (substrStartIndex >= 0) && (searchStartIndex < 0); --substrStartIndex)
+							searchStartIndex = pStr.substr(substrStartIndex, 3).search(/^\w{2}>$/);
+						++substrStartIndex; // To fix off-by-one
+						if (searchStartIndex > -1)
+						{
+							searchStartIndex += substrStartIndex + 3; // To get past the "..>"
+							// New (2017-12-24):
+							// If the instance(s) of a > has 3 non-space characters
+							// before it, then assume the > is not part of a quote
+							// prefix, and look for another > earlier in the text string.
+							// When using author initials, SlyEdit assumes a quote prefix
+							// has up to 2 characters before the >.
+							while ((searchStartIndex >= 4) && (pStr.substr(searchStartIndex-4, 4).search(/^[^\s]{3}>$/) >= 0))
+							{
+								searchStartIndex = pStr.lastIndexOf(">", searchStartIndex-2);
+								if (searchStartIndex == -1)
+									searchStartIndex = 0;
+								else
+									++searchStartIndex; // To fix off-by-one
+							}
+						}
+						// Note: I originally had + 4 here..
+						if (searchStartIndex < 0)
+						{
+							searchStartIndex = pStr.indexOf(">");
+							if (searchStartIndex < 0)
+								searchStartIndex = 0;
+						}
+					}
+					else
+					{
+						searchStartIndex = pStr.indexOf(">");
+						if (searchStartIndex < 0)
+							searchStartIndex = 0;
+					}
+				}
+			}
+		}
+		else
+		{
+			// SlyEdit is not prefixing quote lines with author's initials.
+			searchStartIndex = pStr.indexOf(">");
+			if (searchStartIndex < 0)
+				searchStartIndex = 0;
+		}
+	}
 
-  return retObj;
+	// Find the quote level and the beginning of the line.
+	// Look for the first non-quote text and quote level in the string.
+	var strChar = "";
+	var j = 0;
+	for (var i = searchStartIndex; i < pStr.length; ++i)
+	{
+		strChar = pStr.charAt(i);
+		if ((strChar != " ") && (strChar != ">"))
+		{
+			// New (2017-12-24):
+			// If using author initials and there are 3 non-space characters
+			// before the >, then continue to the next character.
+			if (i >= 3)
+			{
+				if (pUseAuthorInitials && (pStr.substr(i-3, 4).search(/^[^\s]{3}>$/) >= 0))
+					continue;
+			}
+
+			// We've found the first non-quote character.
+			retObj.startIndex = i;
+			// Count the number of times the > character appears at the start of
+			// the line, and set quoteLevel to that.
+			if (i >= 0)
+			{
+				for (j = 0; j < i; ++j)
+				{
+					if (pStr.charAt(j) == ">")
+					{
+						// New (2017-12-24):
+						// If using author initials, then increment the quote level
+						// only if there are not 3 non-space characters before the >
+						if (pUseAuthorInitials && (j >= 3))
+						{
+							if (pStr.substr(j-3, 4).search(/^[^\s]{3}>$/) < 0)
+								++retObj.quoteLevel;
+						}
+						else
+							++retObj.quoteLevel;
+					}
+				}
+			}
+			// Store the beginning of the line in retObj.begOfLine.  And if
+			// SlyEdit is configured to indent quote lines with author initials,
+			// and if the beginning of the line doesn't begin with a space,
+			// then add a space to the beginning of it.
+			retObj.begOfLine = pStr.substr(0, retObj.startIndex);
+			if (pUseAuthorInitials && pIndentQuoteLinesWithInitials && (retObj.begOfLine.length > 0) && (retObj.begOfLine.charAt(0) != " "))
+				retObj.begOfLine = " " + retObj.begOfLine;
+			break;
+		}
+	}
+
+	// If we haven't found non-quote text but the line starts with quote text,
+	// then set the starting index & quote level in retObj.
+	if (lineStartsWithQuoteText && ((retObj.startIndex == -1) || (retObj.quoteLevel == 0)))
+	{
+		retObj.startIndex = pStr.indexOf(">") + 1;
+		// New (2017-12-24):
+		var setQuoteLevel = true;
+		// When using author initials in quote lines: If there are 3 non-space
+		// characters before the >, then it's not an actual quote (SlyEdit
+		// considers quote lines with initials to have only 2 characters before
+		// the >).
+		if (pUseAuthorInitials && retObj.startIndex >= 4)
+		{
+			if (pStr.substr(retObj.startIndex-4, 4).search(/^[^\s]{3}>$/) >= 0)
+			{
+				retObj.startIndex = 0;
+				setQuoteLevel = false;
+			}
+		}
+		if (setQuoteLevel)
+			retObj.quoteLevel = 1;
+	}
+
+	return retObj;
 }
 
 // Performs text wrapping on the quote lines.
@@ -2736,15 +2745,15 @@ function wrapQuoteLines(pUseAuthorInitials, pIndentQuoteLinesWithInitials, pTrim
 // will be removed.
 function normalizeGTChars(pStr)
 {
-  if (/^\s*>\s*$/.test(pStr))
-    pStr = ">";
-  else
-  {
-    pStr = pStr.replace(/>\s*>/g, "> >")
-               .replace(/^\s>/, ">")
-               .replace(/^\s*$/, "");
-  }
-  return pStr;
+	if (/^\s*>\s*$/.test(pStr))
+		pStr = ">";
+	else
+	{
+		pStr = pStr.replace(/>\s*>/g, "> >")
+		           .replace(/^\s>/, ">")
+		           .replace(/^\s*$/, "");
+	}
+	return pStr;
 }
 
 // Wraps quote lines and prefixes them with the original author's initials.
@@ -2853,7 +2862,6 @@ function wrapQuoteLinesUsingAuthorInitials(pIndentQuoteLines, pTrimSpacesFromQuo
 	}
 
 	// 3. Go through each section of the quote lines and wrap & quote appropriately
-	// TODO: This loop seems to be looping forever for certain messages
 	var trimSpacesFromQuoteLines = (typeof(pTrimSpacesFromQuoteLines) == "boolean" ? pTrimSpacesFromQuoteLines : true);
 	for (var sIndex = 0; sIndex < quoteSections.length; ++sIndex)
 	{
@@ -2922,7 +2930,7 @@ function wrapQuoteLinesUsingAuthorInitials(pIndentQuoteLines, pTrimSpacesFromQuo
 		{
 			numLinesAdded = wrapTextLines(gQuoteLines, quoteSections[sIndex].startArrIndex,
 			                              quoteSections[sIndex].endArrIndex, maxLineWidth,
-			                              idxesAddedNL);
+			                              idxesAddedNL, lineInfos);
 		}
 
 		// If quote lines were added as a result of wrapping, then determine the