Search code examples
apostrophe-cms

How to truncate from rich text?


I want to truncate a text from rich text as below. I tried many things, but they did not work.

                    {% for piece in data.pieces %}

                        <div class="col-md-4 col-sm-6">
                            <div class="single-product mb-55 animated fadeInUp" data-animate="fadeInUp" data-delay=".1" style="animation-duration: 0.6s; animation-delay: 0.1s;">
                                <span class="tip">{{ piece.title }}</span>
                                <img src="img/products/[email protected]" data-rjs="2" alt="" data-rjs-processed="true" width="237" height="193">

                                <h3 class="h5">
                                   {{ apos.area(piece, 'body') }}
                                </h3>

                            </div>
                        </div>

                        <!-- End of Single Product -->
                    {% endfor %}

I need to truncate this part. I don't know if there is other way to display the description:

{{ apos.area(piece, 'body') }}

Solution

  • There are a number of different ways you could solve this, you don't provide much in terms of requirements so these would need to finessed to meet your needs.

    Note: Using Nunjuck's truncate filter against rich-text (markup) will include all HTML tags and spaces in code against the truncate limit. The examples below use Nunjuck's truncate. This could lead to broken tags in your markup, should a tag be opened, the limit be reached, and then never closed. I would recommend implementing something like truncate-html as a project-level Nunjucks helper. See Implementing custom Nunjucks helpers in Apostrophe

    Provide area-level truncate settings for your rich-text-widgets

    In the schema for your piece, when defining the body area, add some flag to your apostrophe-rich-text options

        {
          name: 'body',
          label: 'Body',
          type: 'area',
          options: {
            widgets: {
              'apostrophe-rich-text': {
                toolbar:[...], // toolbar settings
                styles:[...], // styles settings
                truncate: 250 // truncates after 250 characters
              }
            }
          }
        }
    

    Then, at a project level override of the apostrophe-rich-text-widgets template, watch for those settings to be passed to the rich-text widget. Use Nunjucks built-in truncate filter to alter the text content in the widget.

    In lib/modules/apostrophe-rich-text-widgets/views/widget.html

    <div data-rich-text class="apos-rich-text">
      {% if data.options.truncate %}
        {{ data.widget.content | safe | truncate(data.options.truncate) }}  
      {% else %}
        {{ data.widget.content | safe }}  
      {% endif %}
    </div>
    

    Advantages: This would work with the setup you've provided above without altering your current data structure of body.

    Disadvantages: This method is brute-force. Your specifying this option at the area level, so all rich-text-widgets within this area will receive the options and they will all be truncated accordingly. This same method would work better if you were using a singleton to save your description because you could tightly control where the truncate options happen.

    Wrap your rich-text widgets into new widgets with truncate options

    This method involves creating a new widget that contains a rich-text singleton and options for controlling if and how much text to truncate. I'm going to call the widget truncate-rich-text

    In lib/modules/truncate-rich-text-widgets/index.js (create this file and path)

    module.exports = {
      extend: 'apostrophe-widgets',
      label: 'Truncate Rich Text',
      addFields: [
        {
          name: 'content',
          label: 'Content',
          type: 'singleton',
          widgetType: 'apostrophe-rich-text',
          options: {
            toolbar:[],
            styles:[]
          }
        },
        {
          name: 'truncate',
          label: 'Truncate this text?',
          type: 'boolean',
          choices: [
            { label: 'Yes', value: true, showFields: ['truncateCharacters'] },
            { label: 'No', value: false }
          ]
        },
        {
          name: 'truncateCharacters',
          label: 'Character limit',
          type: 'integer'
        }
      ]        
    };
    

    Then in lib/modules/truncate-rich-text-widgets/views/widget.html (create this)

    <div class="truncate-rich-text">
      {% if data.widget.truncate %}
        {{ data.widget.content.items[0].content | safe | truncate(data.widget.truncateCharacters) }}
      {% else %}
        {{ data.widget.content.items[0].content | safe }}
      {% endif %}
    </div>
    

    Finally, add this new module to your app.js configuration.

    var path = require('path');
    var apos = require('apostrophe')({
      shortName: 'myProject',
      modules: {
        // ... other modules
        'truncate-rich-text-widgets': {},
      }
    });
    

    Advantages: This provides more fine-grained control over what text widgets get truncated and can be used more widely across your site.

    Disadvantages: This would be a new widget in your site configuration, which means you'd have to migrate your content to use it. This also takes a lot of the in-context'ness of editing text in Apostrophe out of the editor user experience, which is a bummer.

    Like I said, there are multiple ways to achieve this, and there are also UX subtleties to different situations (maybe you only show truncated text to logged-out users while leaving the full content in for editors because it would be confusing for your text to keep disappearing on save). Hopefully this will open up a few pathways for you to find the right implementation.