diff --git a/app/assets/images/overflow-horizontal.svg b/app/assets/images/overflow-horizontal.svg new file mode 100644 index 000000000..a30ec09f8 --- /dev/null +++ b/app/assets/images/overflow-horizontal.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/small-comment.svg b/app/assets/images/small-comment.svg new file mode 100644 index 000000000..545f53a49 --- /dev/null +++ b/app/assets/images/small-comment.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/small-heart.svg b/app/assets/images/small-heart.svg new file mode 100644 index 000000000..281c5ea50 --- /dev/null +++ b/app/assets/images/small-heart.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/small-save-filled.svg b/app/assets/images/small-save-filled.svg new file mode 100644 index 000000000..c6d94c514 --- /dev/null +++ b/app/assets/images/small-save-filled.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/small-save.svg b/app/assets/images/small-save.svg new file mode 100644 index 000000000..71e171ba7 --- /dev/null +++ b/app/assets/images/small-save.svg @@ -0,0 +1 @@ + diff --git a/app/assets/javascripts/initializers/initScrolling.js.erb b/app/assets/javascripts/initializers/initScrolling.js.erb index 663d9d353..901783156 100644 --- a/app/assets/javascripts/initializers/initScrolling.js.erb +++ b/app/assets/javascripts/initializers/initScrolling.js.erb @@ -44,7 +44,7 @@ function insertNext(params, buildCallback) { var distanceFromBottom = document.documentElement.scrollHeight - document.body.scrollTop; var newNode = document.createElement("div"); newNode.innerHTML = newFollowersHTML; - var singleArticles = document.getElementsByClassName("single-article"); + var singleArticles = document.querySelectorAll(".single-article, .crayons-story"); var lastElement = singleArticles[singleArticles.length - 1]; insertAfter(newNode, lastElement); if (nextPage > 0) { @@ -133,7 +133,7 @@ function insertVideos(videoArticles) { var newNode = document.createElement("div"); newNode.innerHTML = newVideosHTML; newNode.className += "video-collection" - var singleArticles = document.getElementsByClassName("single-article"); + var singleArticles = document.querySelectorAll(".single-article, .crayons-story"); var lastElement = singleArticles[singleArticles.length - 1]; insertAfter(newNode, lastElement); if (nextPage > 0) { @@ -155,7 +155,7 @@ function insertArticles(articles) { if ( !['/', '/top/week', '/top/month', '/top/year', '/top/infinity', '/latest'].includes(window.location.pathname) && existingEl && existingEl.parentElement && - existingEl.parentElement.classList.contains('single-article-small-pic') && + existingEl.parentElement.classList.contains('crayons-story') && !document.getElementById("video-player-"+article.id)) { existingEl.parentElement.outerHTML = buildArticleHTML(article); } else if(!existingEl) { @@ -166,8 +166,37 @@ function insertArticles(articles) { }); var distanceFromBottom = document.documentElement.scrollHeight - document.body.scrollTop; var newNode = document.createElement("div"); + newNode.classList.add('paged-stories'); newNode.innerHTML = newArticlesHTML; - var singleArticles = document.getElementsByClassName("single-article"); + + newNode.addEventListener('click', (event) => { + const { classList } = event.target; + + // This looks a little messy, but it's the only + // way to make the entire card clickable. + if ( + classList.contains('crayons-story') || + classList.contains('crayons-story__top') || + classList.contains('crayons-story__body') || + classList.contains('crayons-story__indention') || + classList.contains('crayons-story__title') || + classList.contains('crayons-story__tags') || + classList.contains('crayons-story__bottom') + ) { + let element = event.target; + let { articlePath } = element.dataset; + + while (!articlePath) { + articlePath = element.dataset.articlePath; + element = element.parentElement; + } + + InstantClick.preload(articlePath); + InstantClick.display(articlePath); + } + }) + + var singleArticles = document.querySelectorAll(".single-article, .crayons-story"); var lastElement = singleArticles[singleArticles.length - 1]; insertAfter(newNode, lastElement); if (nextPage > 0) { @@ -287,7 +316,8 @@ function fetchNextPageIfNearBottom() { } function checkIfNearBottomOfPage() { - if (document.getElementsByClassName("single-article").length < 2 || window.location.search.indexOf("q=") > -1 ) { + if ((document.getElementsByClassName("crayons-story").length < 2 && document.getElementsByClassName("single-article").length < 2) + || window.location.search.indexOf("q=") > -1 ) { document.getElementById("loading-articles").style.display = "none"; done = true; } else { diff --git a/app/assets/javascripts/initializers/initializeArticleDate.js b/app/assets/javascripts/initializers/initializeArticleDate.js index 6c325e76c..a11ff1c81 100644 --- a/app/assets/javascripts/initializers/initializeArticleDate.js +++ b/app/assets/javascripts/initializers/initializeArticleDate.js @@ -1,11 +1,11 @@ -'use strict'; + /* Show article date/time according to user's locale */ /* global addLocalizedDateTimeToElementsTitles */ function initializeArticleDate() { var articlesDates = document.querySelectorAll( - '.single-article time, article time, .single-other-article time', + '.crayons-story time, article time, .single-other-article time', ); addLocalizedDateTimeToElementsTitles(articlesDates, 'datetime'); diff --git a/app/assets/javascripts/initializers/initializePodcastPlayback.js b/app/assets/javascripts/initializers/initializePodcastPlayback.js index 1d3a1185e..21c90d3ae 100644 --- a/app/assets/javascripts/initializers/initializePodcastPlayback.js +++ b/app/assets/javascripts/initializers/initializePodcastPlayback.js @@ -1,4 +1,4 @@ -'use strict'; + /** * This script hunts for podcast's "Record" for both the podcast_episde's diff --git a/app/assets/javascripts/initializers/initializeReadingListIcons.js b/app/assets/javascripts/initializers/initializeReadingListIcons.js index dd10d8f7c..1b09efe0e 100644 --- a/app/assets/javascripts/initializers/initializeReadingListIcons.js +++ b/app/assets/javascripts/initializers/initializeReadingListIcons.js @@ -131,11 +131,11 @@ function addHoverEffectToReadingListButtons() { // `this` local variable container.addEventListener( 'mouseover', - readingListButtonMouseHandler.bind('UNSAVE'), + readingListButtonMouseHandler.bind('Unsave'), ); container.addEventListener( 'mouseout', - readingListButtonMouseHandler.bind('SAVED'), + readingListButtonMouseHandler.bind('Saved'), ); }); } diff --git a/app/assets/javascripts/utilities/buildArticleHTML.js.erb b/app/assets/javascripts/utilities/buildArticleHTML.js.erb index ac8d42999..e2f1c3b56 100644 --- a/app/assets/javascripts/utilities/buildArticleHTML.js.erb +++ b/app/assets/javascripts/utilities/buildArticleHTML.js.erb @@ -20,34 +20,45 @@ function buildArticleHTML(article) { var tagList = article.tag_list || article.cached_tag_list_array if(tagList) { tagList.forEach(function(t){ - tagString = tagString + '#'+t+'\n' + tagString = tagString + '#'+t+'\n' }); } - var commentsCountHTML = "" - if ((article.comments_count || '0') > 0 && article.class_name != "User") { - commentsCountHTML = '
" alt="chat" />
' + + var commentsDisplay = '' + var commentsCount = '0' + + if ((article.comments_count || '0') > 0) { + var commentsCount = (article.comments_count || '0') } + if (article.class_name != "User") { + var commentsDisplay = ''+commentsCount+'' + } + var flareTag = "" if (container){ var currentTag = JSON.parse(container.dataset.params).tag } if (article.flare_tag && currentTag != article.flare_tag.name) { - flareTag = "#"+article.flare_tag.name+"" + flareTag = "#"+article.flare_tag.name+"" } if (article.class_name == "PodcastEpisode"){ - flareTag = "podcast" + flareTag = "podcast" } if (article.class_name == "Comment"){ - flareTag = "comment" + flareTag = "comment" } if (article.class_name == "User"){ - flareTag = "person" + flareTag = "person" } + var rc = article.positive_reactions_count - var reactionsCountHTML = '' - if ((rc || '0') > 0 && article.class_name != "User") { - var reactionsCountHTML = '
" alt="heart" />
' + var reactionsCount = (rc || '0') + var reactionsDisplay = ''; + + if (article.class_name != "User") { + reactionsDisplay = ''+reactionsCount+'' } + var picUrl; var profileUsername; var userName; @@ -61,9 +72,49 @@ function buildArticleHTML(article) { userName = article.user.name; } var orgHeadline = ""; + var forOrganization = ""; + var organizationLogo = ""; + var organizationClasses = "crayons-avatar--l"; + + if (article.organization && !document.getElementById("organization-article-index")) { - orgHeadline = '
'+article.organization.name+' logo'+article.organization.name+'
' + var organizationLogo = ''; + var forOrganization = ' for '+article.organization.name+''; + var organizationClasses = "crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted"; } + + var timeAgoInWords = ''; + if (article.published_at_int) { + timeAgoInWords = timeAgo({ oldTimeInSeconds: article.published_at_int }); + } + + var publishDate = ''; + if (article.readable_publish_date) { + if (article.published_timestamp) { + publishDate = ''; + } else { + publishDate = ''; + } + } + + var meta = '
\ +
\ + '+organizationLogo+'\ + \ + '+profileUsername+' profile\ + \ +
\ +
\ +

\ + '+filterXSS(article.user.name)+'\ + '+forOrganization+'\ +

\ + \ + '+publishDate+'\ + \ +
\ +
'; + var bodyTextSnippet = ""; var searchSnippetHTML = ""; if (article.highlight && article.highlight.body_text.length > 0){ @@ -74,64 +125,55 @@ function buildArticleHTML(article) { } bodyTextSnippet = startingEllipsis + article.highlight.body_text.join("...") + "…"; if (bodyTextSnippet.length > 0){ - searchSnippetHTML = '
' + bodyTextSnippet + '
' + searchSnippetHTML = '
' + bodyTextSnippet + '
' } } + + var readingTimeHTML = ''; + if (article.class_name === "Article") { + // we have ` ... || null` for the case article.reading_time is undefined + readingTimeHTML = ''+ ((article.reading_time || null) < 1 ? '1 min' : article.reading_time + ' min') +' read' + } + var saveButton = ''; if (article.class_name === "Article") { - saveButton = '' } else if (article.class_name === "User") { - saveButton = '' } - var publishDate = ''; - if (article.readable_publish_date) { - if (article.published_timestamp) { - publishDate = '・' + ''; - } else { - publishDate = '・' + ''; - } - } - - var readingTimeHTML = ''; - if (article.class_name === "Article") { - // we have ` ... || null` for the case article.reading_time is undefined - readingTimeHTML = ''+ ((article.reading_time || null) < 1 ? '1 min' : article.reading_time + ' min') +' read' - } - var videoHTML = ''; if (article.cloudinary_video_url) { - videoHTML = '
" alt="video camera">'+(article.video_duration_string || article.video_duration_in_minutes)+'
' + videoHTML = '
" alt="video camera">'+(article.video_duration_string || article.video_duration_in_minutes)+'
' } - var timeAgoInWords = ''; - if (article.published_at_int) { - timeAgoInWords = timeAgo({ oldTimeInSeconds: article.published_at_int }); - } - - return '
\ - '+videoHTML+'\ - '+orgHeadline+'\ -
\ - \ - '+profileUsername+' profile\ - \ -
\ - \ -
\ -

'+flareTag+filterXSS(article.title)+'

\ - '+searchSnippetHTML+'\ + return '
\ + '; } } diff --git a/app/assets/stylesheets/_mixins.scss b/app/assets/stylesheets/_mixins.scss index 92bd95e38..e81bfc205 100644 --- a/app/assets/stylesheets/_mixins.scss +++ b/app/assets/stylesheets/_mixins.scss @@ -51,8 +51,8 @@ @include themeable(box-shadow, theme-container-box-shadow, $bold-shadow); @media screen and (min-width: 950px) { - border-radius: 3px; - margin-bottom: 12px; + border-radius: var(--radius); + margin-bottom: var(--su-2); } } diff --git a/app/assets/stylesheets/articles.scss b/app/assets/stylesheets/articles.scss index fda346902..23c288b37 100644 --- a/app/assets/stylesheets/articles.scss +++ b/app/assets/stylesheets/articles.scss @@ -3,6 +3,7 @@ // You can use Sass (SCSS) here: http://sass-lang.com/ @import 'variables'; @import '_mixins'; +@import 'config/import'; .home { position: relative; @@ -355,34 +356,6 @@ display: none; } -// More loading // TODO -.single-article-loading { - border-radius: 3px; - animation-duration: 1.25s; - animation-fill-mode: forwards; - animation-iteration-count: infinite; - animation-name: placeHolderShimmer; - animation-timing-function: linear; - background: linear-gradient( - to right, - rgba(255, 255, 255, 0.1) 10%, - rgba(0, 0, 0, 0.05) 25%, - rgba(255, 255, 255, 0.1) 40% - ); - box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.025); - background-size: 800px 104px; - position: relative; -} - -@keyframes placeHolderShimmer { - 0% { - background-position: -468px 0; - } - 100% { - background-position: 468px 0; - } -} - // Generally Feed... .articles-list { margin: auto; @@ -415,10 +388,6 @@ } } - a { - color: var(--card-color); - } - .placeholder-div { // TOOD: what is this? display: block; @@ -562,11 +531,12 @@ padding: 16px 16px 100px 16px; // TODO: my, oh my... h3 { margin: 0px; - font-size: 20px; + font-size: var(--fs-xl); font-weight: 700; line-height: 28px; padding: 0px; word-break: break-word; + color: var(--card-color); .tag-identifier { font-size: 14px; background: $blue; @@ -582,11 +552,18 @@ background: $black; } } + a { + color: inherit; + + &:hover { + color: var(--link-color-hover); + } + } } - @media screen and (min-width: 450px) { + @media (min-width: $breakpoint-s) { h3 { - font-size: 27px; line-height: 32px; + font-size: var(--fs-2xl); .tag-identifier { font-size: 15px; padding: 2px 6px 3px; @@ -655,21 +632,7 @@ } } } - .search-snippet { - font-size: 15px; - padding: 6px 0px 2px; - color: lighten($dark-gray, 8%); - line-height: 21px; - span { - background: darken($light-gray, 2%); - padding: 1px; - } - em { - font-weight: 600; - color: darken($dark-gray, 6%); - font-style: normal; - } - } + p { margin: 6px auto; line-height: 20px; diff --git a/app/assets/stylesheets/components/avatars.scss b/app/assets/stylesheets/components/avatars.scss index 55cdc927a..92a5e479b 100644 --- a/app/assets/stylesheets/components/avatars.scss +++ b/app/assets/stylesheets/components/avatars.scss @@ -24,6 +24,7 @@ border-radius: $radius; width: 100%; height: 100%; + display: inline-block; vertical-align: bottom; // todo: do we need it? } diff --git a/app/assets/stylesheets/components/comments.scss b/app/assets/stylesheets/components/comments.scss new file mode 100644 index 000000000..c3b4d9c3b --- /dev/null +++ b/app/assets/stylesheets/components/comments.scss @@ -0,0 +1,35 @@ +@import '../config/import'; + +.crayons-comment { + font-size: var(--fs-s); + + &__meta { + display: flex; + align-items: flex-start; + + a { + display: flex; + align-items: flex-start; + } + } + + &__body { + margin-top: calc(var(--su-1) * -1); + padding-left: var(--su-7); + + a:hover { + text-decoration: underline; //todo: default for all in-content links + } + + p { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + } + + & + & { + margin-top: var(--su-4); + } +} diff --git a/app/assets/stylesheets/components/loaders.scss b/app/assets/stylesheets/components/loaders.scss new file mode 100644 index 000000000..ada455e66 --- /dev/null +++ b/app/assets/stylesheets/components/loaders.scss @@ -0,0 +1,34 @@ +@import '../config/import'; + +.crayons-scaffold { + border-radius: var(--radius); + background: var(--body-color); + opacity: 0.025; +} + +.crayons-scaffold-loading { + border-radius: var(--radius); + animation-duration: 1.25s; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-name: placeHolderShimmer; + animation-timing-function: linear; + background: linear-gradient( + to right, + rgba(255, 255, 255, 0.1) 10%, + rgba(0, 0, 0, 0.05) 25%, + rgba(255, 255, 255, 0.1) 40% + ); + box-shadow: inset 0 0 0 200px rgba(0, 0, 0, 0.025); + background-size: 800px 104px; + position: relative; +} + +@keyframes placeHolderShimmer { + 0% { + background-position: -468px 0; + } + 100% { + background-position: 468px 0; + } +} diff --git a/app/assets/stylesheets/components/stories.scss b/app/assets/stylesheets/components/stories.scss new file mode 100644 index 000000000..0b201ce01 --- /dev/null +++ b/app/assets/stylesheets/components/stories.scss @@ -0,0 +1,303 @@ +@import '../variables'; +@import '../config/import'; + +.crayons-story { + background: var(--card-bg); + font-size: var(--fs-base); // Todo: remove when ready. + box-shadow: $bold-shadow, 0 0 0 1px var(--story-border); // we will kill it at some point, don't worry. just not yet. + + @media (min-width: $breakpoint-m) { + border-radius: var(--radius); + margin-bottom: var( + --su-2 + ); // This is basically spacing between each feed card. + } + + --story-padding: var(--su-3) var(--su-2) var(--su-2); + --title-font-size: var(--fs-xl); + + @media (min-width: $breakpoint-s) { + --story-padding: var(--su-4); + --title-font-size: var(--fs-2xl); + + &--featured { + --title-font-size: var(--fs-3xl); + } + } + + // Defining additional colors. + &__secondary { + color: var(--card-color-secondary); + + &[href]:hover, + [enabled]:hover { + color: var(--card-color); + } + } + + &__tertiary { + color: var(--card-color-tertiary); + + &[href]:hover { + color: var(--card-color); + } + } + + // Cover section for promoted (first) card. + &__cover { + display: block; + + &__image { + width: 100%; + height: auto; + padding-bottom: 42%; + background-size: cover; + background-position: center center; + + @media (min-width: $breakpoint-m) { + border-radius: var(--radius) var(--radius) 0 0; + } + } + } + + &__body { + padding: var(--story-padding); + } + + &__indention { + padding-left: calc( + var(--su-7) + var(--su-2) + ); // avatar_width + avatar_margin + } + + &__top { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--su-2); + } + + &__overflow { + color: var(--card-color-tertiary); + @media (min-width: $breakpoint-s) { + display: none; + } + &:hover { + color: var(--card-color); + } + } + + &:hover { + .crayons-story__overflow { + display: inline-flex; + } + } + + &__meta { + display: flex; + align-items: center; + line-height: var(--lh-tight); + font-size: var(--fs-s); + } + + &__author-pic { + position: relative; + margin-right: var(--su-2); + } + + &__tags { + margin-bottom: var(--su-2); + margin-left: calc(var(--su-1) * -1); + } + + &__title { + color: var(--card-color); + line-height: var(--lh-tight); + margin-bottom: var(--su-2); + font-size: var(--title-font-size); + + a { + color: inherit; + display: block; + + &:hover { + color: var(--link-color-hover); + } + } + } + + &__flare-tag { + font-weight: normal; + font-family: var(--ff-accent); + font-size: 0.6em; + padding: 0.3em; + vertical-align: middle; + position: relative; + top: -2px; + line-height: 1em; + display: inline-block; + border-radius: var(--radius); + letter-spacing: -0.05em; + background: var(--base-60); + margin-right: var(--su-1); + color: var(--base-inverted); + } + + &__bottom { + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--fs-s); + line-height: var(--lh-tight); + } + + &__details { + display: flex; + margin-left: calc(var(--su-2) * -1); + } + + &__comments { + cursor: default; + background: var(--story-comments-bg); + padding: var(--story-padding); + padding-top: var(--su-4); + border-radius: 0 0 var(--radius) var(--radius); + margin-top: calc(var(--su-1) * -1); + + &__headline { + font-weight: var(--fw-bold); + font-size: var(--fs-s); + color: var(--card-color-secondary); + margin-bottom: var(--su-3); + padding-left: calc(var(--su-6) + var(--su-4)); + } + + &__actions { + padding-left: calc(var(--su-6) + var(--su-4)); + padding-top: var(--su-4); + } + } + + &__snippet { + color: var(--card-secondary-color); + font-size: inherit; + em { + font-weight: var(--fw-medium); + color: var(--card-color); + background: var(--accent-warning-lighter); + } + } + + &__headline { + font-family: var(--ff-accent); + font-size: var(--fs-s); + color: var(--card-color-tertiary); + margin-bottom: var(--su-4); + + a { + color: inherit; + display: block; + + &:hover { + color: var(--link-color-hover); + } + } + } + + &__save { + display: flex; + align-items: center; + } + + &__video { + position: relative; + padding-top: 56%; + display: block; + background-color: var(--base-100); + background-size: cover; + + img { + width: 100%; + } + + &__time { + position: absolute; + bottom: var(--su-4); + right: var(--su-4); + background: rgba(0, 0, 0, 0.8); + color: var(--base-inverted); + border-radius: var(--radius); + padding: var(--su-1); + font-size: var(--fs-s); + font-family: var(--ff-accent); + } + } +} + +.crayons-podcast-episode { + display: flex; + align-items: flex-start; + margin-bottom: var(--su-4); + + &:last-child { + margin-bottom: 0; + } + + &__cover { + width: var(--su-7); + height: var(--su-7); + margin-right: var(--su-2); + + img { + width: 100%; + height: 100%; + border-radius: var(--radius); + } + } + + &__author { + font-size: var(--fs-s); + color: var(--card-color-secondary); + } + + &__title { + color: var(--card-color); + line-height: var(--lh-tight); + margin-bottom: var(--su-1); + font-size: var(--fs-xl); + + @media (min-width: $breakpoint-s) { + font-size: var(--fs-2xl); + } + + a { + color: inherit; + display: block; + + &:hover { + color: var(--link-color-hover); + } + } + } +} + +// Legacy +.crayons-story { + .crayons-btn { + .bm-initial { + display: inline-flex; + } + .bm-success { + display: none; + } + + &.selected { + .bm-initial { + display: none; + } + .bm-success { + display: inline-flex; + } + } + } +} diff --git a/app/assets/stylesheets/components/tags.scss b/app/assets/stylesheets/components/tags.scss new file mode 100644 index 000000000..881cd452f --- /dev/null +++ b/app/assets/stylesheets/components/tags.scss @@ -0,0 +1,24 @@ +@import '../config/import'; + +.crayons-tag { + font-family: var(--ff-accent); + font-size: var(--fs-xs); + color: var(--tag-color); + line-height: var(--lh-tight); + border-radius: var(--radius); + padding: var(--su-1); + display: inline-block; + letter-spacing: -0.05em; + + @media (min-width: $breakpoint-s) { + font-size: var(--fs-s); + } + + &__prefix { + opacity: 0.5; + } + + &:hover { + color: var(--tag-color-hover); + } +} diff --git a/app/assets/stylesheets/config/_colors.scss b/app/assets/stylesheets/config/_colors.scss index eca79d9bd..741cfeb09 100644 --- a/app/assets/stylesheets/config/_colors.scss +++ b/app/assets/stylesheets/config/_colors.scss @@ -69,7 +69,9 @@ // Main colors --body-bg: #f9fafa; // todo: replace with `var(--base-10);` when ready. - --body-color: var(--base-100); + --body-color: var( + --base-100 + ); // todo: replace with `var(--base-100);` when ready. --body-color-inverted: var(--base-inverted); // Main content containers @@ -187,4 +189,12 @@ --indicator-critical-color: var(--body-color-inverted); --indicator-default-bg: var(--button-secondary-bg); --indicator-default-color: var(--button-secondary-color); + + // Tags + --tag-color: var(--base-70); + --tag-color-hover: var(--base-100); + + // Story + --story-comments-bg: var(--base-0); + --story-border: transparent; } diff --git a/app/assets/stylesheets/crayons.scss b/app/assets/stylesheets/crayons.scss index b9312464c..4a6c49e04 100644 --- a/app/assets/stylesheets/crayons.scss +++ b/app/assets/stylesheets/crayons.scss @@ -10,10 +10,14 @@ @import 'components/banners'; @import 'components/buttons'; @import 'components/cards'; +@import 'components/comments'; @import 'components/dropdowns'; @import 'components/forms'; @import 'components/indicators'; +@import 'components/loaders'; @import 'components/modals'; @import 'components/navigation'; @import 'components/notices'; @import 'components/snackbars'; +@import 'components/stories'; +@import 'components/tags'; diff --git a/app/assets/stylesheets/themes/hacker.scss b/app/assets/stylesheets/themes/hacker.scss index 0a549175b..c53e24602 100644 --- a/app/assets/stylesheets/themes/hacker.scss +++ b/app/assets/stylesheets/themes/hacker.scss @@ -69,7 +69,7 @@ --card-color: var(--accent-brand); --card-color-secondary: var(--accent-brand-darker); --card-color-tertiary: var(--base-50); - --card-secondary-bg: var(--base-10); + --card-secondary-bg: var(--base-inverted); --card-secondary-color: var(--base-90); --card-headline-color: var(--accent-brand-lighter); @@ -179,4 +179,12 @@ --indicator-critical-color: var(--body-color-inverted); --indicator-default-bg: var(--button-secondary-bg); --indicator-default-color: var(--button-secondary-color); + + // Tags + --tag-color: var(--base-70); + --tag-color-hover: var(--base-100); + + // Story + --story-comments-bg: rgba(255, 255, 255, 0.05); + --story-border: rgba(255, 255, 255, 0.2); } diff --git a/app/assets/stylesheets/themes/minimal.scss b/app/assets/stylesheets/themes/minimal.scss index 1466efc4f..d3181f9e0 100644 --- a/app/assets/stylesheets/themes/minimal.scss +++ b/app/assets/stylesheets/themes/minimal.scss @@ -177,4 +177,12 @@ --indicator-critical-color: var(--body-color-inverted); --indicator-default-bg: var(--button-secondary-bg); --indicator-default-color: var(--button-secondary-color); + + // Tags + --tag-color: var(--base-70); + --tag-color-hover: var(--base-100); + + // Story + --story-comments-bg: rgba(0, 0, 0, 0.02); + --story-border: transparent; } diff --git a/app/assets/stylesheets/themes/night.scss b/app/assets/stylesheets/themes/night.scss index 0f8e0ec58..dd9c8af48 100644 --- a/app/assets/stylesheets/themes/night.scss +++ b/app/assets/stylesheets/themes/night.scss @@ -69,7 +69,7 @@ --card-color: var(--base-100); --card-color-secondary: var(--base-70); --card-color-tertiary: var(--base-50); - --card-secondary-bg: var(--base-10); + --card-secondary-bg: var(--base-a5); --card-secondary-color: var(--base-90); --card-headline-color: var(--base-100); @@ -179,4 +179,12 @@ --indicator-critical-color: var(--body-color); --indicator-default-bg: var(--button-secondary-bg); --indicator-default-color: var(--button-secondary-color); + + // Tags + --tag-color: var(--base-70); + --tag-color-hover: var(--base-100); + + // Story + --story-comments-bg: rgba(0, 0, 0, 0.2); + --story-border: rgba(255, 255, 255, 0.15); } diff --git a/app/assets/stylesheets/themes/pink.scss b/app/assets/stylesheets/themes/pink.scss index 544a516e2..76fa78772 100644 --- a/app/assets/stylesheets/themes/pink.scss +++ b/app/assets/stylesheets/themes/pink.scss @@ -177,4 +177,12 @@ --indicator-critical-color: var(--body-color-inverted); --indicator-dimmed-bg: var(--base-30); --indicator-dimmed-color: var(--base-70); + + // Tags + --tag-color: var(--base-70); + --tag-color-hover: var(--base-100); + + // Story + --story-comments-bg: rgba(255, 255, 255, 0.05); + --story-border: var(--accent-brand); } diff --git a/app/decorators/comment_decorator.rb b/app/decorators/comment_decorator.rb index 3f5d5c2ff..2022ed671 100644 --- a/app/decorators/comment_decorator.rb +++ b/app/decorators/comment_decorator.rb @@ -10,4 +10,8 @@ class CommentDecorator < ApplicationDecorator created_at.utc.iso8601 end + + def published_at_int + created_at.to_i + end end diff --git a/app/javascript/articles/Article.jsx b/app/javascript/articles/Article.jsx index 5f54be8e4..3acc70d69 100644 --- a/app/javascript/articles/Article.jsx +++ b/app/javascript/articles/Article.jsx @@ -2,141 +2,121 @@ import { h } from 'preact'; import PropTypes from 'prop-types'; import { articlePropTypes } from '../src/components/common-prop-types/article-prop-types'; import { + ArticleCoverImage, CommentsCount, + CommentsList, ContentTitle, - OrganizationHeadline, - PublishDate, - ReadingTime, + Meta, SaveButton, SearchSnippet, TagList, ReactionsCount, + ReadingTime, + Video, } from './components'; import { PodcastArticle } from './PodcastArticle'; -/* global timeAgo */ - export const Article = ({ article, - currentTag, + isFeatured, isBookmarked, - reactionsIcon, - commentsIcon, - videoIcon, bookmarkClick, }) => { if (article && article.type_of === 'podcast_episodes') { return ; } - const timeAgoIndicator = timeAgo({ - oldTimeInSeconds: article.published_at_int, - formatter: (x) => x, - }); + const clickableClassList = [ + 'crayons-story', + 'crayons-story__top', + 'crayons-story__body', + 'crayons-story__indention', + 'crayons-story__title', + 'crayons-story__tags', + 'crayons-story__bottom', + ]; return ( -
- {article.cloudinary_video_url && ( - -
- video camera - {article.video_duration_in_minutes} -
-
- )} +
{ + const { classList } = event.target; - -
- - {`${article.user.username} - -
- -
- - {article.class_name === 'Article' && ( - // eslint-disable-next-line no-underscore-dangle - - )} -
-
-

- - {filterXSS( - article.class_name === 'User' - ? article.user.username - : article.user.name, - )} - {article.readable_publish_date ? '・' : ''} - {article.readable_publish_date && ( - - )} - {article.published_at_int ? ( - - {timeAgoIndicator.length > 0 ? `(${timeAgoIndicator})` : ''} - - ) : null} - -

+ {article.cloudinary_video_url &&
+ {isFeatured && } +
+
+ +
+ +
+ + + + {article.class_name === 'Article' && ( + // eslint-disable-next-line no-underscore-dangle + + )} + +
+ {article.class_name !== 'User' && ( +
+ + +
+ )} + +
+ + + +
+
+
+
+ + {article.top_comments && article.top_comments.length > 0 && ( + + )} +
+ ); }; Article.defaultProps = { - currentTag: null, isBookmarked: false, + isFeatured: false, }; Article.propTypes = { article: articlePropTypes.isRequired, - currentTag: PropTypes.string, isBookmarked: PropTypes.bool, - reactionsIcon: PropTypes.string.isRequired, - commentsIcon: PropTypes.string.isRequired, - videoIcon: PropTypes.string.isRequired, + isFeatured: PropTypes.bool, bookmarkClick: PropTypes.func.isRequired, }; diff --git a/app/javascript/articles/FeaturedArticle.jsx b/app/javascript/articles/FeaturedArticle.jsx deleted file mode 100644 index 7375db670..000000000 --- a/app/javascript/articles/FeaturedArticle.jsx +++ /dev/null @@ -1,113 +0,0 @@ -import { h } from 'preact'; -import PropTypes from 'prop-types'; -import { - TagList, - SaveButton, - ReadingTime, - ReactionsCount, - CommentsCount, -} from './components'; -import { articlePropTypes } from '../src/components/common-prop-types'; - -export const FeaturedArticle = ({ - article, - isBookmarked, - reactionsIcon, - commentsIcon, - bookmarkClick, -}) => { - return ( -
-
- {article.title} - -
- - -
- ); -}; - -FeaturedArticle.defaultProps = { - isBookmarked: false, -}; - -FeaturedArticle.propTypes = { - article: articlePropTypes.isRequired, - isBookmarked: PropTypes.bool, - reactionsIcon: PropTypes.string.isRequired, - commentsIcon: PropTypes.string.isRequired, - bookmarkClick: PropTypes.func.isRequired, -}; diff --git a/app/javascript/articles/Feed.jsx b/app/javascript/articles/Feed.jsx index 595d4c59a..568168d0e 100644 --- a/app/javascript/articles/Feed.jsx +++ b/app/javascript/articles/Feed.jsx @@ -1,6 +1,5 @@ import { h, Component } from 'preact'; import PropTypes from 'prop-types'; -import { FEED_ICONS } from '../packs/feedIcons.js.erb'; /* global userData sendHapticMessage showModal buttonFormData renderNewSidebarCount */ @@ -145,7 +144,6 @@ export class Feed extends Component { > {renderFeed({ feedItems, - feedIcons: FEED_ICONS, podcastEpisodes, bookmarkedFeedItems, bookmarkClick: this.bookmarkClick, diff --git a/app/javascript/articles/LoadingArticle.jsx b/app/javascript/articles/LoadingArticle.jsx index 4a5dd0f1f..79abd8bea 100644 --- a/app/javascript/articles/LoadingArticle.jsx +++ b/app/javascript/articles/LoadingArticle.jsx @@ -2,29 +2,30 @@ import { h } from 'preact'; import PropTypes from 'prop-types'; export const LoadingArticle = ({ version }) => { - const bigPicArea = version === 'featured' ?
: ''; - const smallPic = - version === 'featured' ? ( - '' - ) : ( -
-
+ const cover = + version === 'featured' && ( +
+
); return ( -
- {bigPicArea} - {smallPic} -
-

 

+
+ {cover} +
+
+
+
+
+
+
+
+
+
+
-

-   -

-
); }; diff --git a/app/javascript/articles/__stories__/Article.stories.jsx b/app/javascript/articles/__stories__/Article.stories.jsx index 7f6f0cae5..563389b0b 100644 --- a/app/javascript/articles/__stories__/Article.stories.jsx +++ b/app/javascript/articles/__stories__/Article.stories.jsx @@ -6,21 +6,14 @@ import { article, articleWithOrganization, articleWithSnippetResult, - articleWithReadingTimeGreaterThan1, articleWithReactions, articleWithComments, - assetPath, + featuredArticle, } from '../__tests__/utilities/articleUtilities'; import { articleDecorator } from './articleDecorator'; import '../../../assets/stylesheets/articles.scss'; -const ICONS = { - REACTIONS_ICON: assetPath('reactions-stack.png'), - COMMENTS_ICON: assetPath('comments-bubble.png'), - VIDEO_ICON: assetPath('video-camera.svg'), -}; - const commonProps = { bookmarkClick: action('Saved/unsaved article'), }; @@ -34,9 +27,6 @@ export default { export const DefaultArticle = () => (
( +
+); + +IsFeatured.story = { + name: 'is featured', +}; + export const WithOrganization = () => (
(
(
( -
-); - -WithReadingTime.story = { - name: 'with reading time', -}; - export const WithReactions = () => (
(
(
( - -); - -Default.story = { name: 'default' }; - -export const OnReadingList = () => ( - -); - -OnReadingList.story = { name: 'is on reading List' }; diff --git a/app/javascript/articles/__stories__/LoadingArticle.stories.jsx b/app/javascript/articles/__stories__/LoadingArticle.stories.jsx index 7728bf599..6df24fc27 100644 --- a/app/javascript/articles/__stories__/LoadingArticle.stories.jsx +++ b/app/javascript/articles/__stories__/LoadingArticle.stories.jsx @@ -4,20 +4,21 @@ import '../../../assets/stylesheets/articles.scss'; import { articleDecorator } from './articleDecorator'; export default { - title: 'App Components/Article', + title: 'App Components/Article Loading', + component: LoadingArticle, decorators: [articleDecorator], }; -export const ArticleLoading = () => ; +export const DefaultArticle = () => ; -ArticleLoading.story = { - name: 'article loading', +DefaultArticle.story = { + name: 'default', }; -export const FeaturedArticleLoading = () => ( +export const FeaturedLoading = () => ( ); -FeaturedArticleLoading.story = { - name: 'featured article loading', +FeaturedLoading.story = { + name: 'featured', }; diff --git a/app/javascript/articles/__tests__/Article.test.jsx b/app/javascript/articles/__tests__/Article.test.jsx index e3fa3f77e..0852c8f7f 100644 --- a/app/javascript/articles/__tests__/Article.test.jsx +++ b/app/javascript/articles/__tests__/Article.test.jsx @@ -5,7 +5,6 @@ import { article, articleWithOrganization, articleWithSnippetResult, - articleWithReadingTimeGreaterThan1, articleWithReactions, videoArticle, articleWithComments, @@ -36,6 +35,19 @@ describe('
component', () => { expect(tree).toMatchSnapshot(); }); + it('should render a featured article', () => { + const tree = render( +
, + ); + expect(tree).toMatchSnapshot(); + }); + it('should render with an organization', () => { const tree = render(
component', () => { expect(tree).toMatchSnapshot(); }); - it('should render with a reading time', () => { - const tree = render( -
, - ); - expect(tree).toMatchSnapshot(); - }); - it('should render with reactions', () => { const tree = render(
component', () => { - it('should render a standard article', () => { - const tree = render( - , - ); - expect(tree).toMatchSnapshot(); - }); - - it('should render with the reactions icon when the reactions count is 0', () => { - const tree = render( - , - ); - expect(tree).toMatchSnapshot(); - }); - - it('should render a bookmarked featured article', () => { - const tree = render( - , - ); - expect(tree).toMatchSnapshot(); - }); -}); diff --git a/app/javascript/articles/__tests__/__snapshots__/Article.test.jsx.snap b/app/javascript/articles/__tests__/__snapshots__/Article.test.jsx.snap index 869069b7b..d5fa32116 100644 --- a/app/javascript/articles/__tests__/__snapshots__/Article.test.jsx.snap +++ b/app/javascript/articles/__tests__/__snapshots__/Article.test.jsx.snap @@ -1,5 +1,193 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`
component should render a featured article 1`] = ` + +`; + exports[`
component should render a podcast article 1`] = `
component should render a podcast article 1`] = ` `; exports[`
- -

- - Henri Gibson・ - - - (just now) - - -

- -
- - heart - - -
-
+ `; exports[`
component should render a standard article 1`] = ` -
- - Emil99 profile - -
- - -

- - Stella Macejkovic・ - - - (just now) - - -

- -
- - heart - - -
- - undefined min read - - -
+
`; exports[`
+ `; exports[`
component should render a video article 1`] = ` -
- -
- video camera - 10 -
-
- - Nova_Luettgen profile - -
- -
-

- monitor recontextualize -

-
-
-

- - Henri Gibson・ - - - (just now) - - -

- - - - undefined min read - - -
+
`; exports[`
component should render a video article with a flare tag 1`] = ` -
- -
- video camera - 10 -
-
- - Nova_Luettgen profile - -
- -
-

- - #javascript - - monitor recontextualize -

-
-
-

- - Henri Gibson・ - - - (just now) - - -

- - - - undefined min read - - -
+
`; exports[`
component should render as saved on reading list 1`] = ` -
- - Emil99 profile - -
- +
+

+ + + #javascript + + Unbranded Home Loan Account + +

+ +
+
+ + + + + 428 + + + + + + + 213 + + +
+
+ + undefined min read + +
+
+
+
+
+ - -

- - Stella Macejkovic・ - - - (just now) - - -

- -
- - chat - - -
-
- - heart - - -
- - undefined min read - - -
+
`; exports[`
+
+
- February 18 - - - (just now) - - - - -
- - heart - - -
- - undefined min read - - -
-`; - -exports[`
+ `; exports[`
component should render with a snippet result 1`] = ` -
- - Emil99 profile - -
- - -

- - Stella Macejkovic・ - - - (just now) - - -

- -
- - heart - - -
- - undefined min read - - -
+
`; exports[`
component should render with an organization 1`] = ` -
- - Web info-mediaries logo - Web info-mediaries - -
-
- - Emil99 profile - -
- - -

- - Stella Macejkovic・ - - - (just now) - - -

- -
- - heart - - -
- - undefined min read - - -
+
`; exports[`
component should render with comments 1`] = ` -
- - Emil99 profile - -
- +
+

+ + + #javascript + + Unbranded Home Loan Account + +

+ +
+
+ + + + + 428 + + + + + + + 213 + + +
+
+ + undefined min read + +
+
+
+
+
+ - -

- - Stella Macejkovic・ - - - (just now) - - -

- -
- - chat - - -
-
- - heart - - -
- - undefined min read - - -
+
`; exports[`
- -

- - Stella Macejkovic・ - - - (just now) - - -

- -
- - heart - - -
- - undefined min read - - -
+ `; diff --git a/app/javascript/articles/__tests__/__snapshots__/ArticleLoading.test.jsx.snap b/app/javascript/articles/__tests__/__snapshots__/ArticleLoading.test.jsx.snap index fb72a9b54..aabef8392 100644 --- a/app/javascript/articles/__tests__/__snapshots__/ArticleLoading.test.jsx.snap +++ b/app/javascript/articles/__tests__/__snapshots__/ArticleLoading.test.jsx.snap @@ -2,41 +2,35 @@ exports[` should render 1`] = `
-
-
-

-   -

+