summaryrefslogtreecommitdiffstats
path: root/browser/components/pocket/content/panels/js/components/ArticleList
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/pocket/content/panels/js/components/ArticleList')
-rw-r--r--browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx139
-rw-r--r--browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss65
2 files changed, 204 insertions, 0 deletions
diff --git a/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx
new file mode 100644
index 0000000000..25679bc638
--- /dev/null
+++ b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import React, { useState } from "react";
+import TelemetryLink from "../TelemetryLink/TelemetryLink";
+
+function ArticleUrl(props) {
+ // We turn off the link if we're either a saved article, or if the url doesn't exist.
+ if (props.savedArticle || !props.url) {
+ return (
+ <div className="stp_article_list_saved_article">{props.children}</div>
+ );
+ }
+ return (
+ <TelemetryLink
+ className="stp_article_list_link"
+ href={props.url}
+ source={props.source}
+ position={props.position}
+ model={props.model}
+ >
+ {props.children}
+ </TelemetryLink>
+ );
+}
+
+function Article(props) {
+ function encodeThumbnail(rawSource) {
+ return rawSource
+ ? `https://img-getpocket.cdn.mozilla.net/80x80/filters:format(jpeg):quality(60):no_upscale():strip_exif()/${encodeURIComponent(
+ rawSource
+ )}`
+ : null;
+ }
+
+ const [thumbnailLoaded, setThumbnailLoaded] = useState(false);
+ const [thumbnailLoadFailed, setThumbnailLoadFailed] = useState(false);
+
+ const {
+ article,
+ savedArticle,
+ position,
+ source,
+ model,
+ utmParams,
+ openInPocketReader,
+ } = props;
+
+ if (!article.url && !article.resolved_url && !article.given_url) {
+ return null;
+ }
+ const url = new URL(article.url || article.resolved_url || article.given_url);
+ const urlSearchParams = new URLSearchParams(utmParams);
+
+ if (
+ openInPocketReader &&
+ article.item_id &&
+ !url.href.match(/getpocket\.com\/read/)
+ ) {
+ url.href = `https://getpocket.com/read/${article.item_id}`;
+ }
+
+ for (let [key, val] of urlSearchParams.entries()) {
+ url.searchParams.set(key, val);
+ }
+
+ // Using array notation because there is a key titled `1` (`images` is an object)
+ const thumbnail =
+ article.thumbnail ||
+ encodeThumbnail(article?.top_image_url || article?.images?.["1"]?.src);
+ const alt = article.alt || "thumbnail image";
+ const title = article.title || article.resolved_title || article.given_title;
+ // Sometimes domain_metadata is not there, depending on the source.
+ const publisher =
+ article.publisher ||
+ article.domain_metadata?.name ||
+ article.resolved_domain;
+
+ return (
+ <li className="stp_article_list_item">
+ <ArticleUrl
+ url={url.href}
+ savedArticle={savedArticle}
+ position={position}
+ source={source}
+ model={model}
+ utmParams={utmParams}
+ >
+ <>
+ {thumbnail && !thumbnailLoadFailed ? (
+ <img
+ className="stp_article_list_thumb"
+ src={thumbnail}
+ alt={alt}
+ width="40"
+ height="40"
+ onLoad={() => {
+ setThumbnailLoaded(true);
+ }}
+ onError={() => {
+ setThumbnailLoadFailed(true);
+ }}
+ style={{
+ visibility: thumbnailLoaded ? `visible` : `hidden`,
+ }}
+ />
+ ) : (
+ <div className="stp_article_list_thumb_placeholder" />
+ )}
+ <div className="stp_article_list_meta">
+ <header className="stp_article_list_header">{title}</header>
+ <p className="stp_article_list_publisher">{publisher}</p>
+ </div>
+ </>
+ </ArticleUrl>
+ </li>
+ );
+}
+
+function ArticleList(props) {
+ return (
+ <ul className="stp_article_list">
+ {props.articles?.map((article, position) => (
+ <Article
+ article={article}
+ savedArticle={props.savedArticle}
+ position={position}
+ source={props.source}
+ model={props.model}
+ utmParams={props.utmParams}
+ openInPocketReader={props.openInPocketReader}
+ />
+ ))}
+ </ul>
+ );
+}
+
+export default ArticleList;
diff --git a/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss
new file mode 100644
index 0000000000..261367433d
--- /dev/null
+++ b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss
@@ -0,0 +1,65 @@
+.stp_article_list {
+ padding: 0;
+ list-style: none;
+
+ .stp_article_list_saved_article,
+ .stp_article_list_link {
+ display: flex;
+ border-radius: 4px;
+ padding: 8px;
+ margin: 0 -8px;
+ }
+
+ .stp_article_list_link {
+ &:hover, &:focus {
+ text-decoration: none;
+ background-color: #ECECEE;
+
+ @include theme_dark {
+ background-color: #2B2A33;
+ }
+ }
+ }
+
+ .stp_article_list_thumb,
+ .stp_article_list_thumb_placeholder {
+ width: 40px;
+ height: 40px;
+ border-radius: 4px;
+ margin-inline-end: 8px;
+ background-color: #ECECEE;
+ flex-shrink: 0;
+ }
+
+ .stp_article_list_header {
+ font-style: normal;
+ font-weight: 600;
+ font-size: 0.95rem;
+ line-height: 1.18rem;
+ color: #15141A;
+ margin: 0 0 4px;
+
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ overflow: hidden;
+ word-break: break-word;
+
+ @include theme_dark {
+ color: #FBFBFE;
+ }
+ }
+
+ .stp_article_list_publisher {
+ font-style: normal;
+ font-weight: normal;
+ font-size: 0.95rem;
+ line-height: 1.18rem;
+ color: #52525E;
+ margin: 4px 0 0;
+
+ @include theme_dark {
+ color: #CFCFD8;
+ }
+ }
+}