docbrown/app/javascript/packs/homePageFeed.jsx
rhymes 99ddd31058
Pin posts to feed (#13807)
* Add SiteConfig.feed_pinned_article and validation

* Display pinned article at the top of feed

* Add (basic) functionality to pin/unpin post

* Admins can pin other users posts as well

* Hide the button if looking at the non pinned post

* Add pinned/unpinned snackbar message

* Rename SiteConfig usage to Settings::General

* Add pinned article to the Admin articles index

* Show the pin post button when there's no pinned article

* Move pinning to a separate controller

* Fix SiteConfig reference

* Hide PinController actions to unauthorized users

* PinnedArticlesController#show action and refactor some of the code

* Add Modal interaction

* Fix modal-pinned checkbox interaction

* Fixed pin/unpin post

* Add ArticleDecorator#pinned? specs

* Add PinnedArticlePolicy and PinnedArticlesController specs

* Add ability to actually pin an article from the admin after submit

* Add partial Cypress pin/unpin spec

* Fix pinned article and add basic Cypress interaction tests

* Add Crayons styling to modal

* Only render the pinned article on the default Feed page

* Use persisted?

* Add some comments

* Update app/javascript/articles/Article.jsx

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update app/javascript/packs/homePageFeed.jsx

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Fix Cypress tests

* Update app/javascript/admin/controllers/article_controller.js

Co-authored-by: Ridhwana <Ridhwana.Khan16@gmail.com>

* Fix pinning in article show page

* Used PinnedArticle domain model

* Fix spec

* Update cypress/integration/adminFlows/articles/pinArticle.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update cypress/integration/adminFlows/articles/pinArticle.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update cypress/integration/adminFlows/articles/pinArticle.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update cypress/integration/adminFlows/articles/pinArticle.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update cypress/integration/adminFlows/articles/pinArticle.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update cypress/integration/articleFlows/pinArticle.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update cypress/integration/articleFlows/pinArticle.spec.js

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Update app/views/admin/articles/index.html.erb

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>

* Fix merge woes

* Add missing article pin post flows

* Add missing admin article flows

* Add Unpin to Admin as well

* Add Audit::Log entries for pin/unpin actions

* Update app/controllers/stories/feeds_controller.rb

Co-authored-by: Michael Kohl <citizen428@dev.to>

* Do not rate limit in E2E tests

* Use .find instead of .filter

* Rename ArticleIdValidator to ExistingArticleIdValidator

* Treat draft and deleted articles the same

* Make sure posts can be pinned after the pinned article is unpublished or deleted

* Use .get directly

* Fix spec and fix PinnedArticlesController#show

* Strengthen pinArticle Cypress tests

* Add Cypress test heading guard

* Add another Cypress test heading guard

* Remove duplicate validator

* Try using the Tools: header instead of the article title

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
Co-authored-by: Ridhwana <Ridhwana.Khan16@gmail.com>
Co-authored-by: Michael Kohl <citizen428@dev.to>
2021-06-04 10:54:53 +02:00

133 lines
3.3 KiB
JavaScript

import { h, render } from 'preact';
import PropTypes from 'prop-types';
import { Article, LoadingArticle } from '../articles';
import { Feed } from '../articles/Feed';
import { TodaysPodcasts, PodcastEpisode } from '../podcasts';
import { articlePropTypes } from '../common-prop-types';
/**
* Sends analytics about the featured article.
*
* @param {number} articleId
*/
function sendFeaturedArticleAnalytics(articleId) {
(function logFeaturedArticleImpression() {
if (!window.ga || !ga.create) {
setTimeout(logFeaturedArticleImpression, 20);
return;
}
ga(
'send',
'event',
'view',
'featured-feed-impression',
`articles-${articleId}`,
null,
);
})();
}
const FeedLoading = () => (
<div>
<LoadingArticle version="featured" />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
</div>
);
const PodcastEpisodes = ({ episodes }) => (
<TodaysPodcasts>
{episodes.map((episode) => (
<PodcastEpisode episode={episode} key={episode.podcast.id} />
))}
</TodaysPodcasts>
);
PodcastEpisodes.defaultProps = {
episodes: [],
};
PodcastEpisodes.propTypes = {
episodes: PropTypes.arrayOf(articlePropTypes),
};
/**
* Renders the main feed.
*/
export const renderFeed = (timeFrame) => {
const feedContainer = document.getElementById('homepage-feed');
render(
<Feed
timeFrame={timeFrame}
renderFeed={({
pinnedArticle,
feedItems,
podcastEpisodes,
bookmarkedFeedItems,
bookmarkClick,
}) => {
if (feedItems.length === 0) {
// Fancy loading ✨
return <FeedLoading />;
}
const commonProps = {
bookmarkClick,
};
const feedStyle = JSON.parse(document.body.dataset.user).feed_style;
const [featuredStory, ...subStories] = feedItems;
if (featuredStory) {
sendFeaturedArticleAnalytics(featuredStory.id);
}
// 1. Show the pinned article first
// 2. Show the featured story next
// 3. Podcast episodes out today
// 4. Rest of the stories for the feed
return (
<div>
{timeFrame === '' && pinnedArticle && (
<Article
{...commonProps}
article={pinnedArticle}
feedStyle={feedStyle}
isBookmarked={bookmarkedFeedItems.has(pinnedArticle.id)}
/>
)}
{featuredStory && (
<Article
{...commonProps}
article={featuredStory}
isFeatured
feedStyle={feedStyle}
isBookmarked={bookmarkedFeedItems.has(featuredStory.id)}
/>
)}
{podcastEpisodes.length > 0 && (
<PodcastEpisodes episodes={podcastEpisodes} />
)}
{(subStories || []).map((story) => (
<Article
{...commonProps}
key={story.id}
article={story}
feedStyle={feedStyle}
isBookmarked={bookmarkedFeedItems.has(story.id)}
/>
))}
</div>
);
}}
/>,
feedContainer,
feedContainer.firstElementChild,
);
};