NL2BR and extended article

Random stuff about serendipity. Discussion, Questions, Paraphernalia.
Post Reply
stephanbrunker
Regular
Posts: 15
Joined: Sat Nov 25, 2017 1:08 am

NL2BR and extended article

Post by stephanbrunker » Sat Nov 25, 2017 3:01 pm

The NL2BR Plugin acts a little strange between the introduction and extended part of an article. In the general template for the extended view, both parts are in separate <div> containers, so it is not possible to continue the extended part in the last line of the introduction, if you don't want a break there.

Then, the plugin removes one newline at the end of the data, so you have to add three newlines to get a big break between both article parts. In the article overview, that doesn't look good because you've then the break between the introduction and the "continue reading ..." line.

Is anybody here who knows the code of this plugin inside out and can work on a fix? Otherwise, i have to try that myself. The best solution would be to change the general template for the extended view that both article parts would be inside <span> containers, but only one shared <div> container, so the extended parts continues in the same line if you do nothing (no newlines at all). The main problem seems, that the plugin cannot know if an extension is following and with <p> tags, you have to change the tag of the introduction to add a margin on the bottom of the introduction if there are newlines at the beginning of the extended part. The other way round is not better, because then it should have a margin when an extension follows and no when a "continue reading" is following. I have to work that out if nobody else does ...

User avatar
yellowled
Regular
Posts: 6959
Joined: Fri Jan 13, 2006 12:46 pm
Location: Eutin, Germany
Contact:

Re: NL2BR and extended article

Post by yellowled » Sat Nov 25, 2017 4:29 pm

stephanbrunker wrote:In the general template for the extended view, both parts are in separate <div> containers, so it is not possible to continue the extended part in the last line of the introduction, if you don't want a break there.
I don't really get what is the issue here. Can you give an example what you would want to do here? Also, can you please provide a URL to the blog so that we can have a look at what we're dealing with here?
stephanbrunker wrote:Then, the plugin removes one newline at the end of the data, so you have to add three newlines to get a big break between both article parts.
You're not actually supposed to use newlines for visual styles, that's what CSS is for. That being said, it is kind of hard to apply CSS to text generated using NL2BR since NL2BR doesn't really structure text. But either way, if you want vertical space between entry and extended entry, but not in the article list, you should to that by giving the extended entry's wrapper div margin-top using CSS.
stephanbrunker wrote:The best solution would be to change the general template for the extended view that both article parts would be inside <span> containers, but only one shared <div> container, so the extended parts continues in the same line if you do nothing (no newlines at all).]/quote]
You can do that using CSS as well, by the way. A div container has display: block; per default, changing that do display: inline-block; for these two wrapper divs should give you the same result.

That being said, I don't see a need for a "fix" here, just a very, very special use case in a rarely used plugin. (I'll admit I'm not a fan of NL2BR since it kind of promotes bad markup, though.)

YL
amazon Wishlist - Serendipity-Podcast (German only, sorry)

stephanbrunker
Regular
Posts: 15
Joined: Sat Nov 25, 2017 1:08 am

Re: NL2BR and extended article

Post by stephanbrunker » Sat Nov 25, 2017 7:22 pm

Okay, if there is another way to get what I want instead of the NL2BR (was it in the standard setup?), i don't say no. Basically, I want my paragraphs in <p> containers and a markup to do that in the editor. But if possible, that i have not to define explicit every paragraph.

The problem in pictures:
I don't want a break in the article listing:
s9y-1.JPG
s9y-1.JPG (111.78 KiB) Viewed 1957 times
But then you also don't have one in the extended view, except of the CRLF with the new <div>
s9y-2.JPG
s9y-2.JPG (127.58 KiB) Viewed 1957 times
even if you have two newlines in the editor:
s9y-3.JPG
s9y-3.JPG (194.94 KiB) Viewed 1957 times
Last edited by stephanbrunker on Sat Nov 25, 2017 7:32 pm, edited 2 times in total.

stephanbrunker
Regular
Posts: 15
Joined: Sat Nov 25, 2017 1:08 am

Re: NL2BR and extended article

Post by stephanbrunker » Sat Nov 25, 2017 7:26 pm

And that is the html output if you use NL2BR to generate <p> tags:
s9y-4.JPG
s9y-4.JPG (120.66 KiB) Viewed 1957 times
there is always that newline coming with the <div ... id="extended"> for the extended part of the content.

Any other plugins to generate paragraphs easily like writing this post in the forum? <p></p> will work, but it is not really positive to a fluid writing.

And as I wrote, the class "break" for the first part of the body is just wrong, because it does that break anyway because of the </div>. There sould be a class "whiteline", but only if an <div ... extended> is following.

stephanbrunker
Regular
Posts: 15
Joined: Sat Nov 25, 2017 1:08 am

Re: NL2BR and extended article

Post by stephanbrunker » Sun Nov 26, 2017 2:35 am

I worked it out:
s9y-5.JPG
s9y-5.JPG (147.54 KiB) Viewed 1953 times
s9y-6.JPG
s9y-6.JPG (135.79 KiB) Viewed 1953 times
s9y-7.JPG
s9y-7.JPG (101.21 KiB) Viewed 1953 times
I changed only the nl to <p> part. The body part has now always the <p class="break"> as last one, e.g. has no margin at the bottom, so you can change that globally with the CSS if you want, equally for extended and not extended articles. If you put a newline at the end of the body or at the beginning of the extended part, a margin is added at the first paragraph of the extended part with the classes "newlinetopbottom" or "newlinetop" . The only thing what wasn't possible is to continue the extended part in the same line because that would be incompatible with caching, because at compiletime the plugin cannot know if the extended part is following or not and for that, you'd have to add the closing </p> dynamically.

I created a pull request on Github and now i'm very curious if it is going to be accepted or not. Normally nobody wants me to fix something :?

User avatar
yellowled
Regular
Posts: 6959
Joined: Fri Jan 13, 2006 12:46 pm
Location: Eutin, Germany
Contact:

Re: NL2BR and extended article

Post by yellowled » Mon Nov 27, 2017 12:44 pm

stephanbrunker wrote:Okay, if there is another way to get what I want instead of the NL2BR (was it in the standard setup?), i don't say no. Basically, I want my paragraphs in <p> containers and a markup to do that in the editor.
Option A: Keep using NL2BR (I'm not a fan of it), but switch it to use the NL2P option. That does just that – basically, instead of inserting <br> elements where you create line breaks, it creates the proper <p> elements.
Option B: Switch to the Markdown plugin instead. (Offers more possibilities.)
Option C: (not sure if you ruled that out before) Use the WYSIWYG editor. (In that case, disable NL2BR for entries and extended entries, but keep it – or use Markdown instead – for comments.)
stephanbrunker wrote:I don't want a break in the article listing:
As I stated before, line breaks and converting them to either <br> or <p> is not really the proper way to implement this anymore. :)

If I was to put into words what you're trying to do, I'd say: "Have a clearly visibly margin between entry and extended entry if both are visible." That only happens if the div with the id="extended" is emitted (which is single entry view), so I'd give that container a top margin using CSS:

Code: Select all

#extended {
    margin-top: 1em;
}
The NL2BR plugin has traditionally been a subject of discussion for a long time. Personally, I think it's obsolete. No, strike that – personally, I think it should be removed altogether, but that's a harsh opinion that not everyone can share. That's why it got the NL2P option and all those confusing settings.

Personally, I don't think a plugin like this should need configuring, it should "just work", which is why I always suggest using the Markdown plugin instead. However, Markdown is not quite as intuitive (because it offers more flexibility).

Just know that if your PR is not accepted, it might just be because no one really sees the need for this fix which, as far as I can tell, is tailored to your very specific needs. It is definitely not because we "don't want you to fix something". On the contrary, we welcome every contribution to s9y – we just have to decide what is actually worth putting in the core. :)

YL
amazon Wishlist - Serendipity-Podcast (German only, sorry)

stephanbrunker
Regular
Posts: 15
Joined: Sat Nov 25, 2017 1:08 am

Re: NL2BR and extended article

Post by stephanbrunker » Mon Dec 04, 2017 3:52 am

So, I spent some time with that plugin and improved it to "does simply work". I didn't count the hours ...

Basically, i left the nl2br untouched and upgraded the nl2p version to something which is universally usable. Means: if you write simple text, it formats the article with paragraphs, depending on your newlines. That did it before ... more or less. But now, it also can digest any html input, and that according to the specifications. So, out of:

Code: Select all

this is some text

next line
<div>
<ol>
<li>first point
<li>second point
no paragraphs allowed in ol
</ol>
</div>
some text
with break
it generates this output:

Code: Select all

<p_class="wl_bottom">this is some text</p>
<p_class="wl_bottom">next line</p>
<div>
<ol>
<li>first point
<li>second point
no paragraphs allowed in ol
</ol>
</div>
<p class="wl_top">some text</p>
<p class="break">with break</p>
The CSS would be accordingly with margin on top,bottom, both or none(=break).
If you want to test it, i put the core into a short demonstration of the html coding:
http://s521456705.online.de/webspace/testnl2br.php

I also rejuvenated the isolation tag feature, this was not working with nl2p (contrary to the description), in the demonstrator it is <nl>, in the plugin it gets the tags from the config.

This extended version makes paragraphs only where they are allowed, simply put. You can even make the Tags in Uppercase, or insert white spaces ... It is open to discussion if it should automatically delete the last nl in the article / extended article or push that newline from article to extended, but the core is the formatting of plain text seasoned with html tags.

I've not put it into a second Pull Request yet, because it's too late and I have to tidy up the code a bit.

So, it does what it should better than before, it extends the functionality of something existing without breaking the previous functionality and i'm really tired if nobody appreciates that. Because there is simply no reason why it shouldn't be integrated in the core and that happened too often to me in the past, so that I even wrote a post about that: http://www.intjblog.de/index.php?/archi ... Hilfe.html

Oh, and because the plugin now splits the text into content and html tags and keeps a stack of the active tags, it wouldn't be that much work to automatically fix overlappings, like a <p> inside a <em>. According to the specs, the paragraph closes the inline element, and now it is easily possible to close the inline element before and open it again on the new paragraph. Not implemented yet, but the structure now allows that. If somebody wants that. I think that not everyone wants to use a full-fledged WYSIWYG-Editor and the standard interface uses html tags and you need simply something to make your paragraphs, because I don't want to write all the <p> tags by hand.

But the preliminary version is like this:

Code: Select all

	// following w3.org, these elements close p elements automatically:
	var $block_elements = array('table','ul','ol','pre', 'dl',
								'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
								'menu', 'section',
								'address', 'article', 'aside', 'fieldset', 'footer',
								'form', 'header', 'hgroup', 'hr', 'main', 'nav', 'p' 
								);

	var $nested_block_elements = array('div','table','blockquote');

	/* paragraphs aren't allowed in these inline elements -> p closes these elements:
	 * var $inline_elements = array('b', 'big', 'i', 'small', 'tt', 'abbr', 
								'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong',
								'samp', 'var', 'a', 'bdo', 'br', 'map', 'object', 
								'q', 'script', 'span', 'sub', 'sup', 'button', 
								'label', 'select', 'textarea'
								); */

	var $allowed_p_parents = array('blockquote', 'td', 'div', 'article', 'aside', 'dd', 
								'details', 'dl', 'dt', 'footer', 'header', 'summary'
								); 

	/** Make sure that all the tags are in lowercase
	 * @param string text
	 * @return text
	 */
	function tag_tolower($textstring)
	{
		$text = str_split($textstring);
		$tag = false;

		for ($i = 0; $i < count($text); $i++)
		{
			if ($text[$i] == '<' ) { $tag = true; }
			elseif ($tag  && ($text[$i] == ' ' || $text[$i] == '>') )
			{	$tag = false; } 
			elseif ($tag) 
			{	$text[$i] = strtolower($text[$i]); }
		}
		return implode($text);
	}

	/**
	 * remove spaces in end tags
	 * @param string text
	 * @return text
	 */
	function trim_end_tags($textstring)
	{
		$text = str_split($textstring);
		$endtag = false;

		for ($i = 1; $i < count($text); $i++)
		{
			if ($text[$i] == '/' && $text[$i-1] == '<' ) 
			{ 	$endtag = true; }
			elseif ($endtag && $text[$i] == ' ' ) 
			{ 	$text[$i] = ''; }
			elseif ($endtag && $text[$i] == '>') 
			{ 	$endtag = false; }
		}
		return implode($text);
	} 
	
	/**
	 * Insert <p class="whiteline" at paragraphs ending with two newlines
	 * Insert <p class="break" at paragraphs ending with one or no nl
	 * Insert <p class="whitelinetop" at the first paragraph if starting with a nl
	 * Insert <p class="whitelinetopbottom" if the first paragraph is ending with two newlines
	 * @param string text
	 * @param boolean complex operations (not necessary when text is flat)
	 * @return string
	 */
	function nl2p($textstring)
	{
		//check for empty content
		if (empty(trim($textstring))) { return $textstring; }
		
		//check for whiteline on start and end
		$startnl = ( strspn($textstring,"\n") ) ? true : false; 
		$endnl = ( strspn($textstring,"\n",-1 ) ) ? true : false;
		$whiteline = $endnl;
		
		//trim whitespaces and line breaks
		$textstring = trim($textstring);    
		
		if (empty($textstring)) { return $textstring; }    

		$p_top = '<p class="wl_top">';
		$p_bottom = '<p_class="wl_bottom">';
		$p_top_bottom = '<p_class="wl_top_bottom">';
		$p = '<p class="break">';
		$end_p = '</p>';

		// check for newlines in text block
		if ($startnl || $endnl || strpos($textstring,"\n") )
		{
			//split into array
			$text = str_split($textstring);

			$i = count($text) - 1;

			//main operation: convert \n to p, dependent of the context
			//crawling from end to start
			while ($i > 0) {
				//search next /n enclosing text, starting at $i-1
				$i = $this->next_nl_block($i, $text); 
				if ($i == 0) {      //no newlines left
					break;
				} elseif ($whiteline) {
					$text[$i] = "</p>" . $p_bottom;
				} else {
					$text[$i] = "</p>" . $p;
				}

				//look ahead for next paragraph class
				if ($text[$i-1] == "\n") {
				$whiteline = true; 
				$text[$i-1] = ''; 
				$i--;
				} else {
				$whiteline = false;
				}
			}

			//implode 
			$textstring = implode($text);

			//get type of first paragraph
			if ($whiteline && $startnl) {
				$textstring = $p_top_bottom . $textstring . $end_p;
			} elseif ($startnl) {
				$textstring = $p_top . $textstring . $end_p;
			} elseif ($whiteline) {
				$textstring = $p_bottom . $textstring . $end_p;
			} else {
				$textstring = $p . $textstring . $end_p;
			}
			
			//format output
			if ($startnl) { $textstring = "\n" . $textstring; }
			$textstring = str_replace('</p>',"</p>\n",$textstring);
		}

		return $textstring;

	}

	/**
	 * Find next newline separated by text from current position
	 * @param int start
	 * $param array text
	 */
	function next_nl_block($i, $text)
	{
		$skipped = false;
		for ($i--; $i>0; $i-- ) {
			if (!$skipped){
				//see if you skipped over a non-newline (heading to the next block)
				if (strpos($text[$i], "\n") === false) {
					$skipped = true;
				}
			} else if (strpos($text[$i], "\n") !== false) {
				break;
			}
		}
		return $i;
	}


	/** explode textstring into array of substrings
	 * array element can be tag or content
	 * @param text
	 * $return array of tags and contents
	 */
	
	function explode_along_tags($text)
	{
		$startpos = 0;
		$endpos = 0;
		$textarray = array();
		do 
		{
			//find tag start
			$endpos = strpos($text,'<',$startpos);
			if ($endpos === false) 
			{ 
				//no more tags, copy remainder to array
				$endpos = strlen($text);
				if ($endpos - $startpos > 0)
				{	$textarray[] = substr($text,$startpos,$endpos - $startpos); }
				return $textarray; 
			}
			elseif (($endpos - $startpos) > 0) 
			{
				//copy preliminary text to array
				$textarray[] = substr($text,$startpos,$endpos - $startpos);
			}
			$startpos = $endpos;
			//find tag end
			$endpos = strpos($text,'>',$startpos);
			if ($endpos === false) { return false; }
			elseif (($endpos - $startpos) > 1) 
			{
				//copy tag to array
				$textarray[] = substr($text,$startpos,$endpos - $startpos + 1);
				$startpos = $endpos + 1;
			}
			else { return false; }
		} while (1);
		return false;
	}

	function extract_tag($text)
	{
		if ($text[0] != "<") { return false; }
		$n = strcspn($text," >");
		return ltrim(substr($text,0,$n),"</");
	}

	function is_starttag($text) { return ($text[1] == "/") ? false : true; }

	/* 
	 * handles tagged content, apply nl2p if tag allows it
	 * works also for ommitted closing tags
	 * @param: text
	 * return string
	 */
	function tagged_nl2p($text)
	{
		//join config isolation tags into block elements
		//if (!empty($isolate)) { $block_elements = array_merge($block_elements,$isolate); }
		
		//explode string into array of tags and contents
		$textarray = $this->explode_along_tags($text);
		$content = "";
		$start = 0;
		$tagstack = array();
		$isolation_flag = false;
		
		for ($i=0; $i < count($textarray); $i++)
		{
			//delete isolation tags from other arrays
			if ($this->isolationtags) 
			{
				$this->block_elements = array_diff($this->block_elements,$this->isolationtags); 
				$this->allowed_p_parents = array_diff($this->allowed_p_parents,$this->isolationtags);
				$this->nested_block_elements = array_diff($this->nested_block_elements,$this->isolationtags);
			}

			//get tag or false if none
			$tag = $this->extract_tag($textarray[$i]);
			
			//new blocktag - e.g. <table>
			if ($tag && $this->is_starttag($textarray[$i]) 
				&& (in_array($tag, $this->block_elements) || in_array($tag, $this->nested_block_elements) ))
			{
				//merge previous content, apply nl2p if needed and concatenate
				if (!$isolation_flag && empty($tagstack) )
				{
					$content .= $this->nl2p(implode(array_slice($textarray,$start,$i-$start)));
				} elseif (!$isolation_flag && in_array($tagstack[0], $this->allowed_p_parents) )
				{
					$content .= $textarray[$start] 
								. $this->nl2p(implode(array_slice($textarray,$start+1,$i-$start-1)));
				} else
				{
					$content .= implode(array_slice($textarray,$start,$i-$start));
				}
				// clear stack of block elements and insert
				if (in_array($tag, $this->block_elements) )
				{
					$tagstack = array_diff($tagstack, $this->block_elements);
				} 
				array_unshift($tagstack, $tag); 
				$start = $i;
			}
			//new tag which can contain paragraphs and can be inside a blocktag - e.g. <td>
			elseif ($tag && $this->is_starttag($textarray[$i]) && in_array($tag, $this->allowed_p_parents))
			{
				//merge previous content, apply nl2p if needed and concatenate
				if (!$isolation_flag && empty($tagstack) )
				{
					$content .= $this->nl2p(implode(array_slice($textarray,$start,$i-$start)));
				} elseif (!$isolation_flag && in_array($tagstack[0], $this->allowed_p_parents) )
				{
					$content .= $textarray[$start] 
								. $this->nl2p(implode(array_slice($textarray,$start+1,$i-$start-1)));
				} else
				{
					$content .= implode(array_slice($textarray,$start,$i-$start));
				}
				//insert tag into the stack
				array_unshift($tagstack, $tag);
				$start = $i;
			}
			//isolation tag
			elseif($tag && $this->is_starttag($textarray[$i]) && in_array($tag, $this->isolationtags) )
			{
				//merge previous content, apply nl2p if needed and concatenate
				if (!$isolation_flag && empty($tagstack) )
				{
					$content .= $this->nl2p(implode(array_slice($textarray,$start,$i-$start)));
				} elseif (!$isolation_flag && in_array($tagstack[0], $this->allowed_p_parents) )
				{
					$content .= $textarray[$start] 
								. $this->nl2p(implode(array_slice($textarray,$start+1,$i-$start-1)));
				} else
				{
					$content .= implode(array_slice($textarray,$start,$i-$start));
				}
				$isolation_flag = true;
				$start = $i+1;
			}
			//closing isolation tag
			elseif($tag && !$this->is_starttag($textarray[$i]) && in_array($tag, $this->isolationtags) )
			{	
				if ($isolation_flag)
				{
				//content, no nl2p 
				$content .= implode(array_slice($textarray,$start,$i-$start-1));
				$isolation_flag = false;
				$start = $i+1;
				}
			}
			//closing blocktag or p parent - e.g. </table> or </td>
			elseif($tag && !$this->is_starttag($textarray[$i]) && $tag == $tagstack[0]) 
			{	
				//concatenate the start tag
				$content .= $textarray[$start];
				
				//content, apply nl2p if needed
				if (!$isolation_flag && in_array($tagstack[0], $this->allowed_p_parents) )
				{
					$content .= $this->nl2p(implode(array_slice($textarray,$start+1,$i-$start-1)));
				} else
				{
					$content .= implode(array_slice($textarray,$start+1,$i-$start-1));
				}
				//closing tag
				if ($i != $start) { $content .= $textarray[$i]; }
				
				$start = $i+1;
				array_shift($tagstack);
			}
		}

		//merge remainder
		if (!$isolation_flag && empty($tagstack) )
		{
			$content .= $this->nl2p(implode(array_slice($textarray,$start,$i-$start)));
		} elseif (!$isolation_flag && in_array($tagstack[0], $this->allowed_p_parents) )
		{
			$content .= $textarray[$start] 
						. $this->nl2p(implode(array_slice($textarray,$start,$i-$start-1)));
		} else
		{
			$content .= implode(array_slice($textarray,$start,$i-$start-1));
		}
		
		return $content;

	}

User avatar
yellowled
Regular
Posts: 6959
Joined: Fri Jan 13, 2006 12:46 pm
Location: Eutin, Germany
Contact:

Re: NL2BR and extended article

Post by yellowled » Wed Dec 06, 2017 8:40 pm

stephanbrunker wrote:I've not put it into a second Pull Request yet, because it's too late and I have to tidy up the code a bit.
I have to admit that it's too late for me today as well to follow (and understand) you explanations, but be assured that we appreciate your effort and are looking forward to a PR. :)

YL
amazon Wishlist - Serendipity-Podcast (German only, sorry)

stephanbrunker
Regular
Posts: 15
Joined: Sat Nov 25, 2017 1:08 am

Re: NL2BR and extended article

Post by stephanbrunker » Mon Dec 11, 2017 12:44 am

A short update: I'm still working on the code for the new nl2p. But to make it right, its slightly complicated. On my blog, I've it running now and so far I exterminated all the bugs which showed up. The last one was replacing < and > with < > automatically if they are not part of an html tag.

Basically, it should do:
  • split the text into parts defined by the blocktags or nestable block elements (div, table, blockquote)
  • get isolation tags from config
  • apply nl2p to these parts if the parent element allows it and its not isolated
  • inside the block elements, the inline elements (like i,b, ...) are continued over newlines. If a newline appears, the element is closed, the paragraph closed, a new one created and the inline element retrieved.
The long version has just something over 400 lines of code ... but I should have covered all the bases.

That means: You can either just type your text in and nl2p set your paragraphs. Or you can code that text in html at will and it still sets the paragraphs for you.

I've updated the test site too to the latest version, so you can try some content against it.

Post Reply