views:

106

answers:

2

I'm trying to isolate (and then manipulate) quote block formatted in common newsreader and email-client manner.

The HTML:

<p>
  Hello there!
  <br />
  I'm great, how are you?
  <br />
  <br />
  Someone wrote:
  <br />
  > Greetings,
  <br />
  > How are you?
</p>

I need to target all the lines that start with >, and hide them as a collapsable block. In the above example everything bellow "Someone wrote:" would then be hidden, saved as a variable and the end result produced by JS would be:

<p>
  Hello there!
  <br />
  I'm great, how are you?
  <br />
  <br />
  Someone wrote:
  <br />
  <a href="#">Click to expand</a>
</p>

Gmail does the same thing, but it serverside wraps the quotation block in a <div>, due to the specific nature of my project the complete process has to be done by JS alone. I'm working with the jQuery framework.

Thanks in advance!

+2  A: 

I don't believe jquery parses text like this. You'll have to parse it yourself for lines that start with '>' and edit the string as you'd like. Then you can use jquery to act on the elements you added.

Malfist
+1  A: 

I put together an example for you at this pastebin. Here is the code with comments added.

HTML

<p>
  Hello there!
  <br />
  I'm great, how are you?
  <br />
  <br />
  Someone wrote:
  <br />
  > Greetings,
  <br />
  > How are you?
  <br />
  <br />
  Someone else wrote:
  <br />
  > I like turtles
  <br />
  <br />
  > Someone odd person wrote:
  <br />
  > > You smell like cheese
  <br />
  > > and now I'm hungry
  <br />
  <br />
  and that's the end,
  <br />
  of all of this.
</p>

Script

$(document).ready(function(){
 // link text to inform users to click to expand
 var lnk = '[+]';
 // variable to look for stating it's a new reply
 var newrply = 'wrote:';
 // reply indicator (HTML escape code for ' > ' to exclude any HTML that might be found
 var isrply = '&gt; ';
 // collect html and split it into an array
 var txt = $('p').html().split('<br>');
 // flag showing that the text is within a reply block
 var rply = false;
 // cycle through each portion of text
 $.each(txt, function(i){
  // look for a new reply
  if (this.match(newrply)){
   // if within a reply and it finds a new reply, close previous
   var tmp = (rply) ? '</div>' : '';
   // add link
   txt[i] = tmp + txt[i] + ' <a href="#" class="replylink">' + lnk + '</a>';
   // go to next variable in array and add wrapper, this makes sure the <br> is outside the reply (formatting purposes)
   txt[i+1] = '<div class="reply">' + txt[i+1];
   // look for reply indicator or text that is <5 characters in length
   // (in the HTML above, the array value will have carriage return plus two spaces for each <br>)
  } else if (this.match(isrply) || txt[i].length < 5) {
   rply = true;
  } else {
   rply = false;
   // close the reply, add the close to the previous array element (most likely a <br>)
   txt[i-1] = txt[i-1] + '</div>';
  }
  // close the reply at the end of the array
  if(i == txt.length) {
   txt[i-1] = txt[i-1] + '</div>';
  }
 })
 // join the array and add it back
 $('p').html( txt.join('<br>') );
 // hide the replies
 $('.reply').hide();
 // add toggle view
 $('.replylink').click(function(){
  $(this).next().next('.reply').toggle();
  return false;
 })
})

I changed the link to just a '[+]' to toggle the view but I didn't bother changing it to '[-]' when the reply is open. I figured the code was getting long enough as it is for this example.


With the new code you posted, I had to make a few changes.

  • It will now work with multiple posts (it processes each "div.post")
  • It will now only find a new reply if the ">" is at the beginning of a new line
  • It uses the rel tag to index each reply since the .next() function of jQuery will find "
    " and the number of these was variable
  • One problem I had was with the click function, I ended up switching to .live because the click event was being triggered twice (I couldn't figure out why, but using live works).
  • Lastly, I left the <a name="" style="color: gray;"/> in the code, but that is not properly formatted HTML... you can't close an <a> tag this way.

New Update:

  • Fixed the script to work with IE, apparently IE uses <BR> instead of <br> so the split wasn't working. I ended up using $.browser.msie even though it isn't recommended. Also, the original script left unopened </div> which is why it broke in IE as well.
  • The rply variable I used before wasn't updating between the iterations of the each function, so I moved it's value into a hidden input tag. I tried making it global, but it just wouldn't cooperate. It's probably not the ideal way to do this, so fix/adjust as you desire.

Required HTML

<input id="replyflag" type="hidden" value="false"/>

Updated Code for IE & new pastbin posting:

$(document).ready(function(){
 $('div.post').each(function(){
  // link text to inform users to click to expand
  var lnk = '[+]';
  // variable to look for stating it's a new reply
  var newrply = 'wrote:';
  // reply indicator (HTML escape code for ' > ' to exclude any HTML that might be found
  var isrply = '&gt;';
  // IE capitalizes the <BR>, collect html and split it into an array
  var splt = ($.browser.msie) ? '<BR>' : '<br>';
  var txt = $(this).find('p:eq(0)').html().split(splt);
  // index of each reply in a post
  var indx = 0;
  // start <div> tag around contents, as the script automatically closes the tag, even without replies
  txt[0] = '<div>' + txt[0];
  // cycle through each portion of text
  $.each(txt, function(i){
   // look for a new reply
   if (this.match(newrply)){
    // if within a reply and it finds a new reply, close previous
    var tmp = ($('#replyflag').val()) ? '</div>' : '';
    // set the "within a reply flag" to true
    $('#replyflag').val(true);
    // increment index
    indx++;
    // add link, the rel attrib contains the index of the reply
    txt[i] = tmp + txt[i] + ' <a href="#" class="replylink" rel="' + indx + '">' + lnk + '</a>';
    // go to next variable in array and add wrapper, this makes sure the <br> is outside the reply (formatting purposes)
    txt[i+1] = '<div class="reply" rel="' + indx + '">' + txt[i+1];
   // look for reply indicator at the beginning of a line or text that is > 3 characters in length, if not there, turn off reply flag.
   } else if (this.substring(0,4)!=isrply | this.length > 3) {
    $('#replyflag').val(false);
   }
   // close the reply at the end of the array
   if (i >= txt.length-1) {
    txt[i] = txt[i] + '</div>';
   }
  })
  // join the array and add it back
  $(this).find('p:eq(0)').html( txt.join('<br>') );
  // hide the replies
  $('.reply').hide();
  // add toggle view (using live because sometimes the click event is called twice and the toggle appears to not work)
  $('.replylink').live('click',function(){
   $(this).parent().find('.reply[rel=' + $(this).attr('rel') + ']').toggle();
   return false;
  })
 })
})
fudgey
Brilliant work, thanks a lot for the effort you've put into this.However, I can't get it to work properly on a production site.This is the HTML of one post as sent by the server;http://pastebin.me/c180b4042a51346bb1baf3d14593fdd0In case of multiple instances, all posts' contents get replaced by text found in the first instance of <p>.Again, thank you for the time you have dedicated to resolve this, the script is absolutely awesome and flawless when applied to properly formatted HTML.
Dolester
I've updated the code to work with the HTML format you provided. If it doesn't work properly on a post with multiple replies, please share this code as well.
fudgey
fudgey, I lack the vocabulary to adequately express my gratitude.Thank you! Works like a charm.
Dolester
Eh, pity it doesn't work in IE6. You can't have it all I guess :)
Dolester
Hmmm, I don't have IE6 installed, but I don't see a reason why it wouldn't work.
fudgey
Ugh, it's not working for me in IE8!... I'll look into it
fudgey
Ok, I got the script working with IE8... I haven't tested it with IE6, but I think it should work now. * Note * second bit of code has been updated and it now needs a hidden input tag to work properly.
fudgey