Jump to content
bernhard

How to reliably wrap the first word of a headline in a <span>?

Recommended Posts

Hi,

I want to wrap the first line of every H2 and H3 in a <span></span> tag so that I can do further CSS styling on that element. The problem is that the markup comes from a CKEditor field, so it can have different versions:

<h2>This is a headline</h2>
<h2><span>This</span> is a headline</h2> // result

<h2 foo="bar">This is a headline</h2>
<h2 foo="bar"><span>This</span> is a headline</h2> // result

<h2 foo="bar"><strong>This is a headline</strong></h2>
<h2 foo="bar"><strong><span>This</span> is a headline</strong></h2> // result

Any tips for me? Domdocument? Any 3rd-party dom parser? Or Regex? Thx for your help! 🙂 

Share this post


Link to post
Share on other sites

Not sure if this will suit your needs, but in javascript (jquery) you could do:

$('h2').each(function() {
   var $element = $(this);
   $element.html($element.html().replace(/^(\w+)/, '<span>$1</span>'));
 });

 

 

  • Like 1

Share this post


Link to post
Share on other sites
preg_replace('~(<h[23]\\b.*?>(?:<[a-z][^>]*>)*)(\w+)~ism', '$1<span>$2</span>', $headline);

quick&dirty in PHP

  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites

quick, dirty & ready 🙂

$this->addHookAfter('Page::render', function ($event) {

    if ($this->page->template != 'admin') {

        // just an example I use in almost every PW site: add responsive wrappers around iFrames:
        if (strpos($event->return, 'videoWrapper') === false) {
            $event->return = str_replace("<iframe", "\n<div class='videoWrapper'><iframe", $event->return);
            $event->return = str_replace("</iframe>", "</iframe></div>", $event->return);
        }
        // another example I use in almost every PW site: add responsive wrappers around tables:
        if (strpos($event->return, 'scrollTable') === false) {
            $event->return = str_replace("<table", "\n<div class='scrollTable'><table", $event->return);
            $event->return = str_replace("</table>", "</table></div>", $event->return);
        }

        $cleaned = preg_replace('~(<h[23]\\b.*?>(?:<[a-z][^>]*>)*)(\w+)~ism', '$1<span>$2</span>', $event->return); // BitPoet's regex magic
        $event->return = preg_replace('/<p>\\s*?(<a .*?><img.*?><\\/a>|<img.*?>)?\\s*<\\/p>/s', '\1', $cleaned); // remove default paragraphs around images in CKE

    }

});

 

Edited by dragan
added comments to code snippet
  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Hi everybody,

I want to share what I came up with. Background: The goal was to get a color-accent to every headline on my new project. First thought was to use CSS-only with ::before pseudo element, but the problem is, that the headline is a either a block element (then position absolute left 0 leads to the accent being on the very left even for text-align:center headlines). Or if the headline is inline-block I had some other weird problem that I can't remember right now. Also multiline headlines where tricky. That's why I wanted to add a real element at the beginning of every headline.

Wrapping the first word was actually not necessary. I'm adding an empty <i></i> now right behind the opening <h2> or <h3> tag, so the regex get's even simpler.

Here's the result:

S4bHoWo.png

cv2xcQU.png

6vYrk49.png

Here's the LESS if someone is interested:

// apply style to all elements with tm-fancy attribute
// this attribute is added to all h2+h3 via textformatter
*[tm-fancy] {
  font-family: @tm-font-fancy;
  i:first-of-type { position: relative; }
  i:first-of-type::before {
    content: '';
    position: absolute;
    left: -15px;
    bottom: 5px;
    width: 120px;
    height: 20px;
    background: @tm-secondary;
    z-index: -1;
    border-radius: 2px;
  }
}
// adjust width and height for h3
h3[tm-fancy] i:first-of-type::before { width: 100px; height: 15px; }

And here's the very simple textformatter that I can apply now an every textarea field:

<?php namespace ProcessWire;
class TextformatterFancyHeadlines extends Textformatter {
  public static function getModuleInfo() {
    return array(
      'title' => 'FancyHeadlines', 
      'version' => 100, 
      'summary' => "Add <i></i> to headlines for color accents.", 
    ); 
  }

  public function format(&$str) {
    // early exit if there are not h2 or h3 elements
    if(strpos($str, "<h2") < 0 AND strpos($str, "<h3") <0) return;

    // add attribute and inline element via regex
    $re = '~(<h[23][^>]*)(>)~ism';
    $str = preg_replace($re, "$1 tm-fancy$2<i></i>", $str);
  }
}

uGyjpi1.png

Thx for your help 🙂 

 

PS: If anybody knows a better solution please share it 🙂 Maybe there's a CSS-only way of doing that?

  • Like 4

Share this post


Link to post
Share on other sites
3 hours ago, bernhard said:

Maybe there's a CSS-only way of doing that?

here's a quick try: https://codepen.io/dragan1700/pen/bGNZBEK?editors=1100

Whatever you do: don't ever try to do stuff like "underline only the last line of a text block": it's a major cause of migraine. Without using JS this can't be done (yet) only with CSS alone. (Well - not unless you do PDFs, of course, or don't have to make stuff responsive...). A client recently came up with BS like that (which was designed by another agency), and we had a hard time explaining why this was not a good idea.

  • Like 2

Share this post


Link to post
Share on other sites

I would just probably add a new ckeditor span with a class so editors could apply manually. That would also allow excluding headlines.

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
On 1/25/2020 at 3:44 PM, tpr said:

I would just probably add a new ckeditor span with a class so editors could apply manually. That would also allow excluding headlines.

Hi @tprI've just tried to do what you suggested, but I failed 😞 

My first try was to set a custom style, but that is not exactly what I want, because these are inline styles that are applied to the current selection. I want to add a new TAG to the list of format tags:

QQEc6ao.png

Eg Heading 2 FANCY which would add <h2 class="tm-fancy">...</h2>

This did unfortunately not help/work: https://stackoverflow.com/questions/17230809/how-to-add-a-custom-paragraph-format-in-ckeditor

Has anybody ever added a custom paragraph format (block element) instead of a custom style? I'll try the inline style solution though...

Share this post


Link to post
Share on other sites

You can simply edit

/**
 * mystyles.js
 *
 * This file may be used when you have "Styles" as one of the items in your toolbar.
 *
 * For a more comprehensive example, see the file ./ckeditor-[version]/styles.js
 *
 */
CKEDITOR.stylesSet.add( 'mystyles', [ 
 { name: 'Inline Code', element: 'code' }, 
...
 { name: 'Heading Underline Span', element: 'span', attributes: { 'class': 'heading_underline' } }, 
 ...
] );

and in your field definition (tab input), add "Styles" to the toolbar.

  • Like 1

Share this post


Link to post
Share on other sites

Here I added some code snippets that may help:

 

  • Like 1

Share this post


Link to post
Share on other sites
16 hours ago, bernhard said:

My first try was to set a custom style, but that is not exactly what I want, because these are inline styles that are applied to the current selection. I want to add a new TAG to the list of format tags:

Yeah, thx for your links, but as I said what I actually tried to do was to add a new paragraph format, not an inline style 😉 I've managed to add an inline style already. I think that option should also work fine. It's actually a little bit more flexible, because then the client can apply the special font to any tag (H2, H3 and P)...

CKEDITOR.stylesSet.add( 'mystyles', [
  { name: 'Handschrift', element: 'span', attributes: { 'class':'tm-fancy' } },
  ...

Extra Allowed Content also needs to be set when ACF is active:

4rk7LZl.png

Share this post


Link to post
Share on other sites

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...