Skip to content

Commit

Permalink
feat: feeds watcher
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhailmogilnikov committed Jan 10, 2024
1 parent 781b301 commit 12e4334
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 95 deletions.
1 change: 1 addition & 0 deletions src/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default (locales) => {
formState: strings.formStates.init,
feedList: [],
feedback: null,
updaterCounter: 0,
};

const watchedState = view(state, locales);
Expand Down
45 changes: 38 additions & 7 deletions src/scripts/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,52 @@ import * as yup from 'yup';
import axios from 'axios';
import domElements from '../utils/domElements.js';
import strings from '../utils/strings.js';
import { normalizeUrl, parseRss, parseXML } from './utilities.js';
import {
allOriginsUrl,
checkPostsChanges,
normalizeUrl,
parseRss,
parseXML,
} from './utilities.js';
import parsedData from '../utils/parsedData.js';

const schema = yup.string().required('this is a required field').url();

const feedsWatcher = (watchedState) => {
const requests = parsedData.feeds.map((feed) => axios
.get(allOriginsUrl(feed.url))
.then((response) => {
const rssDocument = parseXML(response);
const changes = checkPostsChanges(rssDocument, feed.id);
console.log(changes);
if (changes.length > 0) {
console.log('change posts');
parsedData.posts = [...changes, ...parsedData.posts];
watchedState.updaterCounter += 1;
}
})
.catch(() => {}));

Promise.all(requests).then(() => {
setTimeout(() => {
feedsWatcher(watchedState);
}, 5000);
});
};

const request = (watchedState, normalizedValue) => {
axios
.get(
`https://allorigins.hexlet.app/get?disableCache=true&url=${encodeURIComponent(
normalizedValue,
)}`,
)
.get(allOriginsUrl(normalizedValue))
.then((response) => {
try {
const rssDocument = parseXML(response);
parseRss(rssDocument);
parseRss(rssDocument, normalizedValue);

if (watchedState.feedList.length === 0) {
setTimeout(() => {
feedsWatcher(watchedState);
}, 5000);
}

watchedState.feedList.push(domElements.form.input?.value);
watchedState.feedback = strings.feedback.loaded;
Expand Down
52 changes: 46 additions & 6 deletions src/scripts/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import parsedData from '../utils/parsedData.js';

const normalizeUrl = (url) => url.trim().toLowerCase();

const allOriginsUrl = (url) => `https://allorigins.hexlet.app/get?disableCache=true&url=${encodeURIComponent(
url,
)}`;

const parseXML = (response) => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(response.data.contents, 'text/xml');
Expand All @@ -13,18 +17,22 @@ const parseXML = (response) => {
throw new Error('not a RSS document');
}

return parser.parseFromString(response.data.contents, 'text/xml');
return xmlDoc;
};

const parseRss = (rssDocument) => {
const parseRss = (rssDocument, url) => {
const items = rssDocument.querySelectorAll('item');

const feedId = _.uniqueId('feed_');

items.forEach((item) => {
const title = item.querySelector('title');
const description = item.querySelector('description');
const link = item.querySelector('link');

parsedData.posts.push({
id: _.uniqueId('item_'),
// id: _.uniqueId('item_'),
feedId,
title: title.textContent,
description: description.textContent,
link: link.textContent,
Expand All @@ -34,11 +42,43 @@ const parseRss = (rssDocument) => {
const feedTitle = rssDocument.querySelector('title');
const feedDesc = rssDocument.querySelector('description');

parsedData.feed.push({
id: _.uniqueId('feed_'),
parsedData.feeds.push({
id: feedId,
url,
title: feedTitle.textContent,
description: feedDesc.textContent,
});
};

export { normalizeUrl, parseXML, parseRss };
const checkPostsChanges = (rssDocument, feedId) => {
const items = rssDocument.querySelectorAll('item');
const feedPosts = parsedData.posts.filter((post) => post.feedId === feedId);

return Array.from(items).reduce((acc, item) => {
const title = item.querySelector('title');
const description = item.querySelector('description');
const link = item.querySelector('link');

const isPostExist = feedPosts.find(
(findPost) => findPost.title === title.textContent,
);

if (isPostExist === undefined) {
return [
...acc,
{
feedId,
title: title.textContent,
description: description.textContent,
link: link.textContent,
},
];
}

return acc;
}, []);
};

export {
normalizeUrl, parseXML, parseRss, allOriginsUrl, checkPostsChanges,
};
167 changes: 86 additions & 81 deletions src/scripts/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,98 +51,100 @@ const renderFeedback = (value, locales) => {
}
};

const renderFeedList = (locales) => {
domElements.lists.feeds.textContent = '';

const renderFeed = () => {
const feedWrapper = document.createElement('div');
feedWrapper.classList.add('card', 'border-0');

const feedTitleWrapper = document.createElement('div');
feedTitleWrapper.classList.add('card-body');

const feedTitleH4 = document.createElement('h4');
feedTitleH4.classList.add('card-title', 'h4');
feedTitleH4.textContent = locales.t('feedsTitle');

const feedUl = document.createElement('ul');
feedUl.classList.add('list-group', 'border-0', 'rounded-0');

feedTitleWrapper.append(feedTitleH4);
const renderFeed = (locales) => {
const feedWrapper = document.createElement('div');
feedWrapper.classList.add('card', 'border-0');

parsedData.feed.forEach((feed) => {
const feedLi = document.createElement('li');
feedLi.classList.add('list-group-item', 'border-0', 'border-end-0');
const feedTitleWrapper = document.createElement('div');
feedTitleWrapper.classList.add('card-body');

const feedH3 = document.createElement('h3');
feedH3.classList.add('h6', 'm-0');
feedH3.textContent = feed.title;
const feedTitleH4 = document.createElement('h4');
feedTitleH4.classList.add('card-title', 'h4');
feedTitleH4.textContent = locales.t('feedsTitle');

const feedP = document.createElement('p');
feedP.classList.add('m-0', 'small', 'text-black-50');
feedP.textContent = feed.description;
const feedUl = document.createElement('ul');
feedUl.classList.add('list-group', 'border-0', 'rounded-0');

feedLi.append(feedH3, feedP);
feedUl.append(feedLi);
});
feedTitleWrapper.append(feedTitleH4);

feedWrapper.append(feedTitleWrapper, feedUl);
domElements.lists.feeds.append(feedWrapper);
};
parsedData.feeds.forEach((feed) => {
const feedLi = document.createElement('li');
feedLi.classList.add('list-group-item', 'border-0', 'border-end-0');

const renderPosts = () => {
const postsWrapper = document.createElement('div');
postsWrapper.classList.add('card', 'border-0');
const feedH3 = document.createElement('h3');
feedH3.classList.add('h6', 'm-0');
feedH3.textContent = feed.title;

const postsTitleWrapper = document.createElement('div');
postsTitleWrapper.classList.add('card-body');
const feedP = document.createElement('p');
feedP.classList.add('m-0', 'small', 'text-black-50');
feedP.textContent = feed.description;

const postsTitleH4 = document.createElement('h4');
postsTitleH4.classList.add('card-title', 'h4');
postsTitleH4.textContent = locales.t('postsTitle');
feedLi.append(feedH3, feedP);
feedUl.append(feedLi);
});

const postsUl = document.createElement('ul');
postsUl.classList.add('list-group', 'border-0', 'rounded-0');
feedWrapper.append(feedTitleWrapper, feedUl);

postsTitleWrapper.append(postsTitleH4);
domElements.lists.feeds.textContent = '';
domElements.lists.feeds.append(feedWrapper);
};

parsedData.posts.forEach((post) => {
const postLi = document.createElement('div');
postLi.classList.add(
'list-group-item',
'd-flex',
'justify-content-between',
'align-items-start',
'border-0',
'border-end-0',
);
const renderPosts = (locales) => {
const postsWrapper = document.createElement('div');
postsWrapper.classList.add('card', 'border-0');

const postsTitleWrapper = document.createElement('div');
postsTitleWrapper.classList.add('card-body');

const postsTitleH4 = document.createElement('h4');
postsTitleH4.classList.add('card-title', 'h4');
postsTitleH4.textContent = locales.t('postsTitle');

const postsUl = document.createElement('ul');
postsUl.classList.add('list-group', 'border-0', 'rounded-0');

postsTitleWrapper.append(postsTitleH4);

parsedData.posts.forEach((post) => {
const postLi = document.createElement('div');
postLi.classList.add(
'list-group-item',
'd-flex',
'justify-content-between',
'align-items-start',
'border-0',
'border-end-0',
);

const postLink = document.createElement('a');
postLink.setAttribute('href', post.link);
postLink.classList.add('fw-bold');
postLink.setAttribute('data-id', post.id);
postLink.setAttribute('target', '_blank');
postLink.setAttribute('rel', 'noopener noreferrer');
postLink.textContent = post.title;

const postButton = document.createElement('button');
postButton.setAttribute('type', 'button');
postButton.classList.add('btn', 'btn-outline-primary', 'btn-sm');
postButton.setAttribute('data-id', post.id);
postButton.setAttribute('data-bs-toggle', 'modal');
postButton.setAttribute('data-bs-target', '#modal');
postButton.textContent = locales.t('posts.button');

postLi.append(postLink, postButton);
postsUl.append(postLi);
});

postsWrapper.append(postsTitleWrapper, postsUl);

domElements.lists.posts.textContent = '';
domElements.lists.posts.append(postsWrapper);
};

const postLink = document.createElement('a');
postLink.setAttribute('href', post.link);
postLink.classList.add('fw-bold');
postLink.setAttribute('data-id', post.id);
postLink.setAttribute('target', '_blank');
postLink.setAttribute('rel', 'noopener noreferrer');
postLink.textContent = post.title;

const postButton = document.createElement('button');
postButton.setAttribute('type', 'button');
postButton.classList.add('btn', 'btn-outline-primary', 'btn-sm');
postButton.setAttribute('data-id', post.id);
postButton.setAttribute('data-bs-toggle', 'modal');
postButton.setAttribute('data-bs-target', '#modal');
postButton.textContent = locales.t('posts.button');

postLi.append(postLink, postButton);
postsUl.append(postLi);
});

postsWrapper.append(postsTitleWrapper, postsUl);
domElements.lists.posts.append(postsWrapper);
};

renderFeed();
renderPosts();
const renderNewFeed = (locales) => {
renderFeed(locales);
renderPosts(locales);
};

const render = (locales) => (path, value) => {
Expand All @@ -153,8 +155,11 @@ const render = (locales) => (path, value) => {
case 'feedback':
renderFeedback(value, locales);
break;
case 'updaterCounter':
renderPosts(locales);
break;
case 'feedList':
renderFeedList(locales);
renderNewFeed(locales);
break;
default:
throw new Error(`Error: unresolved path: ${path}`);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/parsedData.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
posts: [],
feed: [],
feeds: [],
};

0 comments on commit 12e4334

Please sign in to comment.