Roman
Roman

Reputation: 11

How to create a success message in JS

I am trying to create a project - RSS reader and I have a problem. After I adding new RSS Feed I don't have a message under URL input about successful RSS load.

I tried everything and I can't find a problem. What can I try next?

app.js:

import i18next from 'i18next';
import { initView } from './view.js';
import { createSchema } from './validate.js';
import { fetchRss } from './rss.js';
import { parseRSS } from './parser.js';
import { checkForUpdates } from './updater.js';

export default () => {
  const state = {
    urls: [],
    isValid: true,
    error: '',
    feeds: [],
    posts: [],
    readPostIds: [],
    loading: false,
  };

  i18next.init({
    lng: 'ru',
    resources: {
      ru: {
        translation: {
          success: 'RSS успешно загружен',
          invalidUrl: 'Ссылка должна быть валидным URL',
          existingUrl: 'RSS уже существует',
          required: 'Не должно быть пустым',
          networkError: 'Ошибка сети',
          invalidRSS: 'Ресурс не содержит валидный RSS',
        },
      },
    },
  });

  const watchedState = initView(state, i18next);

  const form = document.querySelector('.rss-form');
  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const url = formData.get('url').trim();

    const schema = createSchema(watchedState.urls);

    try {
      if (watchedState.urls.includes(url)) {
        throw new Error('existingUrl');
      }

      await schema.validate(url);
      watchedState.loading = true;

      const rssData = await fetchRss(url);

      const { feed, posts } = parseRSS(rssData);

      watchedState.feeds.push(feed);
      watchedState.posts.push(...posts.map((post, index) => ({
        ...post,
        id: `post-${index}`,
      })));
      watchedState.urls.push(url);

      watchedState.isValid = true;
      watchedState.error = '';

      watchedState.feeds = [...watchedState.feeds];
      watchedState.posts = [...watchedState.posts];
      watchedState.urls = [...watchedState.urls];

      if (watchedState.urls.length === 1 && !watchedState.loading) {
        setTimeout(() => checkForUpdates(watchedState), 1000);
      }

    } catch (err) {
      if (err.name === 'ValidationError') {
        watchedState.error = err.message;
      } else {
        watchedState.error = i18next.t(err.message);
      }
      watchedState.isValid = false;
    } finally {
      watchedState.loading = false;
    }
  });
};

view.js

import onChange from 'on-change';
import { Modal } from 'bootstrap';
export const initView = (state, i18next) => {
  const input = document.querySelector('#url-input');
  const feedback = document.querySelector('.feedback');
  const feedsContainer = document.querySelector('.feeds');
  const postsContainer = document.querySelector('.posts');

  const modalTitle = document.querySelector('.modal-title');
  const modalBody = document.querySelector('.modal-body');
  const fullArticleLink = document.querySelector('.full-article');
  const modalElement = document.getElementById('modal');
  const modal = new Modal(modalElement);

  const handlePostClick = (post) => {
    if (!post.title || !post.description || !post.link) {
      return;
    }

    modalTitle.textContent = post.title;
    modalBody.innerHTML = post.description;
    fullArticleLink.href = post.link;
    modal.show();
  };

  const renderFeeds = () => {
    feedsContainer.innerHTML = `
      <h4 class="${state.feeds.length === 0 ? 'd-none' : ''} mb-4">Фиды</h4>
      ${state.feeds.map((feed) => `
        <div class="feed mb-4">
          <h6>${feed.title}</h6>
          <p>${feed.description}</p>
        </div>
      `).join('')}
    `;
  };

  const renderPosts = () => {
    postsContainer.innerHTML = `
      <h4 class="${state.posts.length === 0 ? 'd-none' : ''} mb-4">Посты</h4>
      ${state.posts.map((post) => `
        <div class="post mb-3 d-flex justify-content-between align-items-center">
          <a href="${post.link}" target="_blank" class="post-link ${state.readPostIds.includes(post.id) ? 'text-secondary fw-normal' : 'fw-bold'}" data-post-id="${post.id}">
            ${post.title}
          </a>
          <button class="btn btn-outline-primary btn-sm" data-post-id="${post.id}">Просмотр</button>
        </div>
      `).join('')}
    `;

    document.querySelectorAll('.post-link').forEach((link) => {
      link.addEventListener('click', (e) => {
        const postId = e.target.dataset.postId;
        if (postId && !state.readPostIds.includes(postId)) {
          state.readPostIds.push(postId);
          e.target.classList.add('text-muted', 'fw-normal');
          e.target.classList.remove('fw-bold')
        }
      });
    });

    document.querySelectorAll('.btn-outline-primary').forEach((button) => {
      button.addEventListener('click', (e) => {
        e.preventDefault();
        const postId = e.target.dataset.postId;
        const post = state.posts.find((p) => p.id === postId);
        if (post) {
          handlePostClick(post);
          if (!state.readPostIds.includes(postId)) {
            state.readPostIds.push(postId);
            const postLink = document.querySelector(`.post-link[data-post-id="${postId}"]`)
            if (postLink) {
              postLink.classList.add('text-muted', 'fw-normal');
              postLink.classList.remove('fw-bold')
            }
          }
        }
      });
    });
  };

  return onChange(state, (path) => {
    if (path === 'isValid' || path === 'error') {
      if (state.isValid) {
        feedback.textContent = i18next.t('success');
        feedback.classList.replace('text-danger', 'text-success');
        input.classList.remove('is-invalid');
        input.value = '';
        input.focus();
      } else {
        feedback.textContent = i18next.t(state.error);
        feedback.classList.replace('text-success', 'text-danger');
        input.classList.add('is-invalid');
      }
    }

    if (path === 'feeds') {
      renderFeeds();
    }

    if (path === 'posts' || path === 'readPostIds') {
      renderPosts();
    }
  });
};

updater.js

import { fetchRss } from "./rss.js";
import { parseRSS } from "./parser.js"

export const checkForUpdates = async (state) => {
    try {
        for (const url of state.urls) {
            const rssData = await fetchRss(url);
            const { posts: newPosts } = parseRSS(rssData)
            
            const existingUrl = state.posts.map((post) => post.link);
            const uniqueNewPosts = newPosts.filter(
                (post) => !existingUrl.includes(post.link)
            );

            if (uniqueNewPosts.length > 0) {
                state.posts.unshift(...uniqueNewPosts);
            }
        }
    } catch (err) {
        console.log('Error', err);
    } finally {
        setTimeout(() => checkForUpdates(state), 5000)
    }
}

I tried to change state

Upvotes: 1

Views: 43

Answers (1)

Wiimm
Wiimm

Reputation: 3568

Often I use an empty <div> in combination with css and javascript.

HMTL:

any code
<div id=success></div>
any code

CSS:

#success { /* layout */ }

Javasript:

function printSuccess ( msg ) /* msg may contain HTML tags */
{
  var e = document.getElementById("success");
  if (e)
    e.innerHTML = msg;
}

Upvotes: 0

Related Questions