Highrise Rich Edit

By Kevin Ashworth Last update May 7, 2010 — Installed 212 times.

There are 6 previous versions of this script.

// Highrise Rich Edit
// Based on a script that is Copyright (c) 2009, Todd Moon (toddmoon.com) & jrhyley ( http://www.flickr.com/people/jrhyley/ )
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// ==UserScript==
// @name					Highrise Rich Edit
// @description		Adds a simple rich edit interface (Italic, Bold, Underline, Strike, Blockquote, Link, Image, Small) to most text areas in Highrise.
// @namespace		 http://www.toddmoon.com/GreaseMonkey/
// @include			 http://*highrisehq.com/*
// @include			 https://*highrisehq.com/*
// ==/UserScript==

		// == CONSTANTS == //
		
		// KSA: remove server from url
		var MYHIGHRISE = window.location.hostname;
		
		var COMMAND_FLAGS = {
			ITALICS: 1,
			BOLD: 2,
			UNDERLINE: 4,
			STRIKE: 8,
			QUOTE: 16,
			LINK: 32,
			IMAGE: 64,
			SMALL: 128
		}
		
		// == LIFECYCLE == //
		
		//Find existing text areas to add rich controls to.
		textAreas = document.evaluate(
			"//textarea",
			document,
			null,
			XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
			null
		);
		
		//Add the rich editor to the existing text areas.
		for ( var i = 0; i < textAreas.snapshotLength; i++)
		{
			var textArea = textAreas.snapshotItem(i);
			
			// if this is not the extra special hidden textarea from the "invite to group" widget
			if ( !textArea.style || !textArea.style.display || textArea.style.display.toLowerCase() != "none" )
			{
				var controlBar = new ControlBar( 
					COMMAND_FLAGS.ITALICS |
					COMMAND_FLAGS.BOLD |
					COMMAND_FLAGS.UNDERLINE |
					COMMAND_FLAGS.STRIKE |
					COMMAND_FLAGS.QUOTE |
					COMMAND_FLAGS.LINK |
					COMMAND_FLAGS.IMAGE |
					COMMAND_FLAGS.SMALL
				);
				
				controlBar.inject( textArea );
			}
		}
		
		var pathSegments = getLowercasePathSegments( document.location.pathname );
						
		// == CLASSES == //
		
		function ControlBar( commandFlags )
		{
			this.inject = function( targetTextArea )
			{
				var controlBar = document.createElement("div");
				
				controlBar.setAttribute('style', 'margin: 2px 0px; font-size: 12px;');
				
				if ( ( commandFlags & COMMAND_FLAGS.ITALICS ) == COMMAND_FLAGS.ITALICS )
				{
					var item = new ControlBarItem(
						"<i>italic</i>",
						function()
						{
							TagSelection( targetTextArea, "<i>", "</i>" );
						}
					);
					
					controlBar.appendChild( item.create() );
				}	
				
				if ( ( commandFlags & COMMAND_FLAGS.BOLD ) == COMMAND_FLAGS.BOLD )
				{
					var item = new ControlBarItem(
						"<b>bold</b>",
						function()
						{
							TagSelection( targetTextArea, "<b>", "</b>" );
						}
					);
					
					controlBar.appendChild( item.create() );
				}	
				
				if ( ( commandFlags & COMMAND_FLAGS.UNDERLINE ) == COMMAND_FLAGS.UNDERLINE )
				{
					var item = new ControlBarItem(
						"<span class='u'>underline</span>",
						function()
						{
							TagSelection( targetTextArea, "<span class='u'>", "</span>" );
						}
					);
					
					controlBar.appendChild( item.create() );
				}	
				
				if ( ( commandFlags & COMMAND_FLAGS.STRIKE ) == COMMAND_FLAGS.STRIKE )
				{
					var item = new ControlBarItem(
						"<span class='s'>strike</span>",
						function()
						{
							TagSelection( targetTextArea, "<span class='s'>", "</span>" );
						}
					);
					
					controlBar.appendChild( item.create() );
				}
				
				if ( ( commandFlags & COMMAND_FLAGS.QUOTE ) == COMMAND_FLAGS.QUOTE )
				{
					var item = new ControlBarItem(
						"quote",
						function()
						{
							TagSelection( targetTextArea, "<blockquote>", "</blockquote>" );
						}
					);
					
					controlBar.appendChild( item.create() );
				}
				
				if ( ( commandFlags & COMMAND_FLAGS.LINK ) == COMMAND_FLAGS.LINK )
				{
					var item = new ControlBarItem(
						"link",
						function()
						{
							LinkSelection( targetTextArea );
						}
					);
					
					controlBar.appendChild( item.create() );
				}
				
				if ( ( commandFlags & COMMAND_FLAGS.IMAGE ) == COMMAND_FLAGS.IMAGE )
				{
					var item = new ControlBarItem(
						"image",
						function()
						{
							EmbedImage( targetTextArea );
						}
					);
					
					controlBar.appendChild( item.create() );
				}
				
				if ( ( commandFlags & COMMAND_FLAGS.SMALL ) == COMMAND_FLAGS.SMALL )
				{
					var item = new ControlBarItem(
						"small",
						function()
						{
							TagSelection( targetTextArea, "<small>", "</small>" );
						}
					);
					
					controlBar.appendChild( item.create() );
				}
				
				targetTextArea.parentNode.insertBefore( controlBar, targetTextArea );
			};
		}
		
		function ControlBarItem( label, editFunction )
		{
			this.create = function() 
			{
				var link = document.createElement("a");
				
				link.innerHTML = label;
				link.href = "javascript:;";
				link.setAttribute('style','Margin-Right: 8px; text-decoration: none;');
				
				link.execute = editFunction;
				
				addEvent( link, "click", "execute" );
				
				return link;	
			}
		}
		
		function DescriptionDivControlBarLoader( descriptionDiv, commandFlags )
		{
			this.initialize = function()
			{
				if ( typeof( descriptionDiv.startEditing ) == 'function' )
				{	
					//This may seem backwards. Why create a new pointer to the existing function, then create a new function that calls the new pointer?
					//Wouldn't it be simpler to make a new "wrapper" function that simply calls the old one and set the onclick to the new function?
					//Yes, but I want to make sure the onclick is still called "startEditing" so that other scripts can also extend it using this exact same pattern.
					
					//richEdit_OriginalStartEditing needs to be a name unique to your script if you want to follow this pattern.
					descriptionDiv.richEdit_OriginalStartEditing = descriptionDiv.startEditing;
					
					descriptionDiv.addControlBar = function()
					{
						var nodes = document.evaluate(
							"./div/form/textarea[@name='content']",
							this.parentNode,
							null,
							XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
							null
						);
						
						if ( nodes && nodes.snapshotLength > 0 )
						{
							var textArea = nodes.snapshotItem(0);
							
							var controlBar = new ControlBar( commandFlags );
							controlBar.inject( textArea );
						}
					}
					
					descriptionDiv.startEditing = function() {
						descriptionDiv.richEdit_OriginalStartEditing();
						descriptionDiv.addControlBar();
					};
					
					descriptionDiv.onclick = descriptionDiv.startEditing;
				}
			}
		}
		
		// == FUNCTIONS == //
		
		function TagSelection( targetTextArea, tagOpen, tagClose )
		{	
			if ( targetTextArea.selectionStart || targetTextArea.selectionStart == 0 ) //relies on this property.
			{	
				//record scroll top to restore it later.
				var scrollTop = targetTextArea.scrollTop;
					
				// work around Mozilla Bug #190382
				if ( targetTextArea.selectionEnd > targetTextArea.value.length )
				{
					targetTextArea.selectionEnd = targetTextArea.value.length;
				}
				
				//We will restore the selection later, so record the current selection.
				var selectionStart = targetTextArea.selectionStart;
				var selectionEnd = targetTextArea.selectionEnd;
				
				targetTextArea.value = 
					targetTextArea.value.substring( 0, selectionStart ) + //text leading up to the selection start
					tagOpen + 
					targetTextArea.value.substring( selectionStart, selectionEnd ) + //selected text
					tagClose + 
					targetTextArea.value.substring( selectionEnd ); //text after the selection end
				
				targetTextArea.selectionStart = selectionStart + tagOpen.length;
				targetTextArea.selectionEnd = selectionEnd + tagOpen.length;
				
				targetTextArea.scrollTop = scrollTop;
			}	
		}
		
		function LinkSelection( targetTextArea )
		{
			var url = prompt( "Enter the URL:", "" );
		
			if ( url != null )
			{
				// KSA: remove server from url
				var h1 = "https://" + MYHIGHRISE;
				var h2 = "http://" + MYHIGHRISE;
				if (url.indexOf(h1) == 0)
					url = url.substring(h1.length,url.length);
				else if (url.indexOf(h2) == 0)
					url = url.substring(h2.length,url.length);
				TagSelection( targetTextArea, '<a href="' + url + '">', '</a>' );
			}
		}
		
		
		function EmbedImage( targetTextArea )
		{
			var url = prompt( "Enter the image URL:", "" );
		
			if ( url != null )
			{
				TagSelection( targetTextArea, '<img src="' + url + '"/>', '' );
			}
		}
		
		//Finds path segments in the given path. Removes the protocol and domain name if present. Returns an array of the segments.
		function getLowercasePathSegments( path )
		{
			//replace preceding protocol and domain and then any preceding or trailing slashes then split on the remaining slashes.
			return path.toLowerCase().replace( /^https?:\/\/[^\/]*/, "" ).replace(/^\/+|\/+$/g,"").split("/");
		}
		
		//Delegated event wire-up utitlity. Using this allows you to use the "this" keyword in a delegated function.
		function addEvent( target, eventName, handlerName )
		{
			target.addEventListener(eventName, function(e){target[handlerName](e);}, false);
		}