Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display JSON feeds (blog posts, news, etc.) as cards from another website #1157

Open
dtrucs opened this issue Jun 4, 2024 · 0 comments
Open
Labels
documentation Improvements or additions to documentation

Comments

@dtrucs
Copy link
Collaborator

dtrucs commented Jun 4, 2024

If you want to display articles from another website, you can do so using custom HTML templates.

Demo Screenshot

image

JSON Feed

To retrieve data from another website, the well-known feeds are RSS or Atom, but the best format for communicating remains JSON, so we decided to develop a script that uses them all: JSON Feed.

So, if you wish to copy/paste the following codes into your customization but with your own feed, it must be formatted in JSON Feed or you must edit the following script to adapt it.

Template

Common

Let's try the following code and copy it into a template file like customization/html/homeTop.html:

<div
  data-widget="feed"
  data-url="https://www.ecrins-parcnational.fr/flux_actus.json"
  data-limit="6"
  data-title="Découvrir les dernières actualités"
></div>
  • data-widget="feed" (mandatory): all HTML tags with this data attribute and the value “feed” will be parsed by the corresponding script.
  • data-url (mandatory): this is the feed source. If left blank, nothing happens.
  • data-limit (optional - default Infinity): is the number of elements to be displayed.
  • data-title (optional): allows to add a title to the section

Internationalization

If the feed is only available in one language, we suggest you paste it only in the suffixed template: homeTop-en.html.
If you have feeds available in different languages, you can duplicate the template with the language code as a suffix as above, or you can use the {{ language }} variable.
Using the HTML example above, let's assume that JSON feeds are available via these urls https://www.ecrins-parcnational.fr/en/flux_actus.json for the English version and https://www.ecrins-parcnational.fr/fr/flux_actus.json for the French version. You can therefore only define homeTop.html with the following content:

<div
  data-widget="feed"
  data-url="https://www.ecrins-parcnational.fr/{{ language }}/flux_actus.json"
></div>

Widget in details page

It works in the same way.
You can create the file customization/html/details/feedWidget.html and paste the following template:

<div
  data-widget="feed"
  data-url="https://www.ecrins-parcnational.fr/flux_actus.json"
  data-limit="6"
></div>

Don't forget to call the feedWidget in the desired details page in customization/config/details.json.

{
  "sections": {
    "trek": [
      {
        "name": "feedWidget",
        "display": true,
        "anchor": true,
        "order": 200
      }
    ]
  }
}

The main difference with the common template is that you don't have to define the data-title attribute, as the title is automatically defined in translated files under the key details.[file-widget-name].
With our example, we need to add these lines in customization/translations/fr.json :

{
+  "details": {
+    "feedWidget": "Actualités"
+  }
}

Script:

All this won't work without the associated script. You can copy/paste the script below into customization/html/scriptsHeader.html:

<script type="module">
  async function getFeedContent(url) {
    const storageUrl = 'feedWidget:' + url;
    if (window[storageUrl]) {
      return window[storageUrl];
    }
    if (!url) return null;
    try {
      const feedContent = await fetch(url).then(response => response.json());
      window[storageUrl] = feedContent;
      return feedContent;
    } catch {
      return null;
    }
  }
  async function generateFeedTemplate(Widget) {
    const {
      dataset: { url, limit = 'Infinity', title },
    } = Widget;

    Widget.classList.add('empty:hidden');

    const rawfeed = await getFeedContent(url);

    if (!rawfeed) return;

    // JSONFeed format
    const feed = Array.isArray(rawfeed) ? rawfeed : rawfeed.items;

    if (feed.length === 0) return;

    const Title =
      title &&
      Object.assign(document.createElement('h3'), {
        className: 'mb-2 desktop:mb-6 text-H2 desktop:text-H2 font-bold',
        textContent: title,
      });

    const List = Object.assign(document.createElement('ul'), {
      className:
        'flex items-start gap-4 desktop:gap-6 overflow-x-auto overflow-y-hidden scroll-smooth snap-x pb-5 mt-4 mb-2',
    });
    feed.slice(0, limit).forEach(item => {
      const Item = Object.assign(document.createElement('li'), {
        className:
          'relative border border-solid border-greySoft rounded-lg flex-none mx-1 overflow-hidden hover:border-blackSemiTransparent transition duration-500 w-70',
      });
      const image = item.image ?? item.banner_image;
      const ItemImg =
        image &&
        Object.assign(document.createElement('img'), {
          loading: 'lazy',
          src: item.image ?? item.banner_image,
          className: 'overflow-hidden size-full object-cover object-center',
          alt: '',
        });
      const ItemContent = Object.assign(document.createElement('div'), {
        className: 'flex flex-col gap-2 p-4',
      });
      const ItemTitle = Object.assign(document.createElement('h4'), {
        className: 'text-xl',
      });
      const ItemLink = Object.assign(document.createElement('a'), {
        className:
          "text-primary1 hover:text-primary3 focus:text-primary3 before:content-[''] before:absolute before:inset-0",
        href: item.url,
        target: '_blank',
        rel: 'noopener noreferrer',
        textContent: item.title,
      });
      const date = new Intl.DateTimeFormat(document.documentElement.lang).format(
        new Date(item.date_published),
      );
      const summary = item.summary ?? item.content_text;
      let ItemSummary = summary
        ? Object.assign(document.createElement('p'), {
            className: 'line-clamp-2 desktop:line-clamp-5',
            textContent: date + ' - ' + (item.summary ?? item.content_text),
          })
        : Object.assign(document.createElement('div'), {
            className: 'content-WYSIWYG line-clamp-2 desktop:line-clamp-5',
            innerHTML: date + item.content_html,
          });

      ItemTitle.append(ItemLink);
      ItemContent.append(ItemTitle, ItemSummary);
      ItemImg && Item.append(ItemImg);
      Item.append(ItemContent);
      List.append(Item);
    });
    Title && Widget.append(Title);
    Widget.append(List);
  }

  function initFeedWidget() {
    window.setTimeout(() => {
      Array.from(document.querySelectorAll('[data-widget="feed"]'), generateFeedTemplate);
    }, 200);
  }

  // Wait for the load of next router
  const routeChange = setInterval(async function () {
    if (window.next && window.next.router) {
      window.next.router.events.on('routeChangeComplete', initFeedWidget);
      initFeedWidget();
      clearInterval(routeChange);
    }
  }, 100);
</script>

A few explanations about the script:

  • The first action is to add a className to the template in order to hide it if it is empty.
  • If there is no data-url defined, or if there is an error when retrieving the data, or if the data returns 0 elements, the script stops execution and nothing is displayed.
  • The source URL must provide a JSON feed format, otherwise it will try to loop inside as if the response were the contents of the items key of the JSON feed. No other checks are made, so if your JSON is not properly formatted, errors may occur.
  • For the card content, it tries to get the value of summary, if this key has no value, it tries the value of content_text. And if this key also has no value, it finally tries with content_html. Be careful with the last property: you have to trust the source because it executes a innerHTML.
@dtrucs dtrucs added the documentation Improvements or additions to documentation label Jun 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

1 participant