dsfr_typesense-2.1.x-dev/js/search.js

js/search.js
((Drupal, TypesenseInstantSearchAdapter, instantsearch) => {
  /**
   * Handles the "Read more" / "Read less" functionality.
   */
  Drupal.behaviors.readMore = {
    attach(context) {
      const hitItems = once('read-more', '.hit-item', context);
      hitItems.forEach((hitItem) => {
        const readMoreLink = hitItem.querySelector('[data-action="read-more"]');
        const readLessLink = hitItem.querySelector('[data-action="read-less"]');

        if (readMoreLink) {
          readMoreLink.addEventListener('click', (e) => {
            e.preventDefault();
            const container = e.target.closest('.hit-body');
            container.querySelector('.text-container').classList.remove('truncated');
            e.target.style.display = 'none';
            if (readLessLink) readLessLink.style.display = 'inline';
          });
        }

        if (readLessLink) {
          readLessLink.addEventListener('click', (e) => {
            e.preventDefault();
            const container = e.target.closest('.hit-body');
            container.querySelector('.text-container').classList.add('truncated');
            e.target.style.display = 'none';
            if (readMoreLink) readMoreLink.style.display = 'inline';
          });
        }
      });
    },
  };

  /**
   * Initializes the Typesense InstantSearch functionality.
   */
  Drupal.behaviors.search = {
    attach(context, settings) {
      const searchbox = once('searchbox', '#searchbox', context).shift();
      if (!searchbox) {
        return;
      }

      const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
        server: {
          apiKey: settings.search_api_typesense.api_key,
          nodes: [
            {
              host: settings.search_api_typesense.host,
              port: settings.search_api_typesense.port,
              protocol: settings.search_api_typesense.protocol,
            },
          ],
        },
        additionalSearchParameters: {
          query_by: settings.search_api_typesense.query_by_fields,
          query_by_weights: settings.search_api_typesense.query_by_weights,
          sort_by: settings.search_api_typesense.sort_by_fields,
          exclude_fields: 'embedding',
          exhaustive_search: true,
        },
      });
      const { searchClient } = typesenseInstantsearchAdapter;

      const search = instantsearch({
        searchClient,
        indexName: settings.search_api_typesense.index,
        routing: true,
      });

      // Custom render function for hits.
      const renderHits = (renderOptions, isFirstRender) => {
        const { hits, widgetParams } = renderOptions;
        const container = document.querySelector(widgetParams.container);

        // Build the HTML for all hits.
        container.innerHTML = `
          <ul class="ais-Hits-list">
            ${hits
              .map(item => {
                const url = item.url || '#';
                const renderedItem = item.rendered_item || '';
                const isLongText = renderedItem.length > 280;

                // Use InstantSearch's highlighting function.
                const highlightedTitle = instantsearch.highlight({ attribute: 'title', hit: item });
                const highlightedBody = instantsearch.highlight({ attribute: 'rendered_item', hit: item });

                return `
                  <li class="ais-Hits-item">
                    <article class="hit-item" data-hit-id="${item.objectID}">
                      <h3 class="hit-title">
                        <a href="${url}">${highlightedTitle}</a>
                      </h3>
                      <div class="hit-body">
                        <div class="text-container ${isLongText ? 'truncated' : ''}">
                          ${highlightedBody}
                        </div>
                        ${isLongText ? `
                          <a href="#" class="read-more" data-action="read-more">Lire la suite</a>
                          <a href="#" class="read-less" style="display: none;" data-action="read-less">Réduire</a>
                        ` : ''}
                      </div>
                    </article>
                  </li>
                `;
              })
              .join('')}
          </ul>
        `;

        // IMPORTANT: Attach Drupal behaviors to the newly created content.
        Drupal.attachBehaviors(container);
      };

      // Create the custom hits widget.
      const customHits = instantsearch.connectors.connectHits(renderHits);

      search.addWidgets([
        instantsearch.widgets.configure({
          hitsPerPage: 12,
        }),
        instantsearch.widgets.searchBox({
          container: '#searchbox',
          placeholder: 'Search...',
        }),
        customHits({
          container: '#hits',
        }),
        instantsearch.widgets.pagination({
          container: '#pagination',
        }),
        instantsearch.widgets.stats({
          container: '#stats',
        }),
      ]);

      settings.search_api_typesense.facet_string_fields.forEach((facet) => {
        search.addWidgets([
          instantsearch.widgets.refinementList({
            container: `#${facet}`,
            attribute: facet,
            searchable: true,
          }),
        ]);
      });

      settings.search_api_typesense.facet_number_fields.forEach((facet) => {
        search.addWidgets([
          instantsearch.widgets.refinementList({
            container: `#${facet}`,
            attribute: facet,
            searchable: true,
          }),
        ]);
      });

      search.start();
    },
  };
})(Drupal, TypesenseInstantSearchAdapter, instantsearch); // eslint-disable-line

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc