Edit in JSFiddle

<article>
    <h1 class="post-title">Weekly Widget 1 -&nbsp;TreeCombo</h1>
    <div class="author-time">18 January 2013 by justinbmeyer</div>
    <p>I, <a href="http://twitter.com/justinbmeyer">@justinbmeyer</a>, am 
        going to post a weekly widget made with CanJS. I hope to continue
        this for as long as I have widgets to write. If you 
        <strong>want to see something</strong>, please tweet it to 
        <a href="http://twitter.com/justinbmeyer">@canjs</a>. These posts
        are going to be quick and dirty. Eventually, I will put 
        these up on <a href="http://canjs.us/recipes.html">CanJS’s recipe page</a> with
        a full description.
    </p>
    <p>I’m starting with the <code>TreeCombo</code> below:</p>

    <iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/sTLhX/35/embedded/result,html,js"         
           allowfullscreen="allowfullscreen" frameborder="0">JSFiddle</iframe>

    <h2 id="what-it-does">What it does</h2>

    <p>The tree combo allows you to select items within and
    navigate through a hierarchial collection
    of data. Click “→” to see child items. Click the
    breadcrumb navigation at the top to return to parent items.</p>

    <p>It also displays a list of the items the user has selected under

        “You’ve selected:”.</p>
</article>
<article>
    <h1 class="post-title">Weekly Widget 2 - 2-Way Mustache&nbsp;Helpers
	</h1>
	<div class="author-time">27 January 2013 by justinbmeyer</div>
	<p>This article shows how to make 2-way binding mustache helpers
        and use them in a basic form.</p>

    <p>By default, CanJS supports 1-way binding templates. 
        1-way binding means that if data changes,
        the template is automatically updated. For example,
        if <code>attendee.name</code> changes, the following input’s value is updated:
    </p>

    <pre><code class="xml"><span class="tag">&lt;<span class="title">input</span> <span
        class="attribute">type</span>=<span class="value">'text'</span> <span 
        class="attribute">value</span>=<span 
        class="value">"{{attendee.name}}"</span>/&gt;</span>
    </code></pre>

    <p>2-way binding means the reverse is also true. If the user
    types a new value, the <code>attendee.name</code> is automatically updated. 
    This article shows how roll your own
    2-way mustache helpers that can be used like:</p>

    <pre><code class="xml"><span class="tag">&lt;<span class="title">input</span> <span 
        class="attribute">type</span>=<span class="value">'text'</span> {{<span 
        class="attribute">value</span> <span
        class="attribute">attendee.name</span>}}/&gt;</span>
<span 
        class="tag">&lt;<span class="title">input</span> <span 
        class="attribute">value</span>=<span class="value">"yes"</span> <span 
        class="attribute">type</span>=<span class="value">"radio"</span> {{<span 
        class="attribute">checked</span> <span 
            class="attribute">attendee.attending</span>}}/&gt;</span>
        </code></pre>
</article>
<article>
    <h1 class="post-title">Weekly Widget 3 - Paginated&nbsp;Grid
	</h1>
    <div class="author-time">01 February 2013 by justinbmeyer</div>
    <p>This weekly widget was suggested 
    by <a href="https://twitter.com/Cherif_b/status/296765112386203649">@Cherif_b</a>.
    The article covers how to create a basic paginated
    grid. But even more important, it gives a glipse
    at how we organize large applications. </p>
    <h2 id="the-app">The app</h2>
    <p>The app allows you to paginate through a list of links:</p>
    <iframe style="width: 100%; height: 300px"     
    src="http://jsfiddle.net/SyEXx/4/embedded/result,html,js" 
    allowfullscreen="allowfullscreen"     
    frameborder="0">JSFiddle</iframe>
    <h2 id="what-it-does">What it does</h2>
    <p>You can go through each page by clicking the “Next” and “Prev” 
    links. Those buttons become gray when clicking on one would
    move to a page without data.</p>
    <p>The app also supports bookmarking of the current page and 
    forward/back button support.</p>
</article>
(function(){

	
	/**
	 * $('.body').more({
	 *   moreHTML: "<a href='javascript://' class='more'>...</a>",
	 *   moreWidth: 50,
	 *   lessHTML: " <a href='javascript://' class='less'>less</a>",
     *   lines: 2
	 * })
	 */
	$.fn.more = function(options){
		// setup defaults
			options = $.extend({
				lessHTML: " <a href='javascript://' class='less'>-</a>",
				moreHTML: " <a href='javascript://' class='more'>+</a>",
				moreWidth: 50,
                lines: 2
			},options||{});
			
	    this.each(function(el){
			var $el = $(this);
			
			// save current HTML for later
			$el.data('originalHTML', $el.html())

			// the active range we will be moving around
			var range = $el.range(),
				// the end of the body's text for comparison
				end = range.clone().collapse(false).start("-1"),
				// the start of the body's text for comparison
				start = nextChar(range.collapse().end("+1"),end).clone(),
				// the current line's first character's coordinates
				prevRect = start.rect(),
				// how many lines we've come across
				lines = 0;
			
			// go until we reach the end of the body 
			while(range.compare("START_TO_START",end) != 0){
				range.end("+1").start("+1");
                
				var rect = range.rect();
				// if the charcter is on a new line
				if( rect && (rect.top -prevRect.top  > 4) ) {
					lines++;
					// quit on second line
					if(lines == options.lines){
						break;
					}
					prevStart = range.clone()
					prevRect = rect;
				}
			}
			
			if(lines === options.lines){
				// backup to previous line
				range.end('-1').start('-1');
			}
			
			// get the last visible text element
			prevChar(range, start)
			
			var movedLeft = false,
                offset = $el.offset(),
				width = $el.width();
            
			// keep moving back until there is room for more
			while(range.compare("START_TO_START",start) != -1 ){
				if( range.rect(true).left <= (offset.left+width-options.moreWidth) ) {
					break;
				}
				movedLeft = true;
				range.end("-1").start("-1")
			}
			// exit if we don't need to add more button
			if(!movedLeft && (lines < options.lines ) ) {
				return
			}
			
			var parent = range.start().container;
			// remove remaining text
			if( parent.nodeType === Node.TEXT_NODE ||
			 	parent.nodeType === Node.CDATA_SECTION_NODE ) {
			     parent.nodeValue = parent.nodeValue.slice(0,range.start().offset+1)
			}
			var removeAfter =  parent;
			// remove everything after
			while(removeAfter !== this){
				var parentEl = removeAfter.parentNode,
					childNodes = parentEl.childNodes,
					index = $.inArray(removeAfter,childNodes );
				
				for(var i = parentEl.childNodes.length-1; i > index; i--){
					parentEl.removeChild( childNodes[i] );
				}
				removeAfter = parentEl;
			}
			
	        // add more after parent
	        if( parent.nodeType === Node.TEXT_NODE ||
			 	parent.nodeType === Node.CDATA_SECTION_NODE ) {
			     parent = parent.parentElement
			}
	      	$(parent).append(options.moreHTML);
	      	$el.data('shortenedHTML',$el.html())
                // show more / hide listeners
				.on("click","a.more",function(){
					$el.html($el.data('originalHTML')+options.lessHTML)
				})
				.on("click","a.less",function(){
					$el.html($el.data('shortenedHTML'))
				});
	    })
	};
	
    // Moves the range until it hits something other than a space
	var nextChar = function(range, boundary){
		while(/[\s\n]/.test(range) && range.compare("END_TO_END",boundary) != 1){
			range.start("+1").end("+1")
		}
		return range;
	};
	// Moves the range until it hits something other than a space
	var prevChar = function(range, boundary){
		while(/[\s\n]/.test(range) && range.compare("START_TO_START",boundary) != -1){
			range.start("-1").end("-1")
		}
		return range;
	}
    
})();

$("article").more({lines: 5, moreWidth: 20})