Bug 1940566 - Add section to manage followed/blocked topics in Customize panel r=home-newtab-reviewers,fluent-reviewers,bolsson,amy
Differential Revision: https://phabricator.services.mozilla.com/D234219
This commit is contained in:
@@ -108,6 +108,11 @@ function templateHTML(options) {
|
|||||||
type="module"
|
type="module"
|
||||||
src="chrome://global/content/elements/moz-button-group.mjs"
|
src="chrome://global/content/elements/moz-button-group.mjs"
|
||||||
></script>
|
></script>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
type="module"
|
||||||
|
src="chrome://global/content/elements/moz-box-button.mjs"
|
||||||
|
></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`.trimLeft();
|
`.trimLeft();
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ for (const type of [
|
|||||||
"TOP_SITES_UPDATED",
|
"TOP_SITES_UPDATED",
|
||||||
"TOTAL_BOOKMARKS_REQUEST",
|
"TOTAL_BOOKMARKS_REQUEST",
|
||||||
"TOTAL_BOOKMARKS_RESPONSE",
|
"TOTAL_BOOKMARKS_RESPONSE",
|
||||||
|
"UNBLOCK_SECTION",
|
||||||
"UNFOLLOW_SECTION",
|
"UNFOLLOW_SECTION",
|
||||||
"UNINIT",
|
"UNINIT",
|
||||||
"UPDATE_PINNED_SEARCH_SHORTCUTS",
|
"UPDATE_PINNED_SEARCH_SHORTCUTS",
|
||||||
|
|||||||
@@ -629,6 +629,14 @@ export class BaseContent extends React.PureComponent {
|
|||||||
prefs["discoverystream.thumbsUpDown.searchTopsitesCompact"];
|
prefs["discoverystream.thumbsUpDown.searchTopsitesCompact"];
|
||||||
const hasThumbsUpDown = prefs["discoverystream.thumbsUpDown.enabled"];
|
const hasThumbsUpDown = prefs["discoverystream.thumbsUpDown.enabled"];
|
||||||
const sectionsEnabled = prefs["discoverystream.sections.enabled"];
|
const sectionsEnabled = prefs["discoverystream.sections.enabled"];
|
||||||
|
const topicLabelsEnabled = prefs["discoverystream.topicLabels.enabled"];
|
||||||
|
const sectionsCustomizeMenuPanelEnabled =
|
||||||
|
prefs["discoverystream.sections.customizeMenuPanel.enabled"];
|
||||||
|
// Logic to show follow/block topic mgmt panel in Customize panel
|
||||||
|
const mayHaveTopicSections =
|
||||||
|
topicLabelsEnabled &&
|
||||||
|
sectionsEnabled &&
|
||||||
|
sectionsCustomizeMenuPanelEnabled;
|
||||||
|
|
||||||
const featureClassName = [
|
const featureClassName = [
|
||||||
weatherEnabled && mayHaveWeather && "has-weather", // Show is weather is enabled/visible
|
weatherEnabled && mayHaveWeather && "has-weather", // Show is weather is enabled/visible
|
||||||
@@ -678,6 +686,7 @@ export class BaseContent extends React.PureComponent {
|
|||||||
wallpapersV2Enabled={wallpapersV2Enabled}
|
wallpapersV2Enabled={wallpapersV2Enabled}
|
||||||
activeWallpaper={activeWallpaper}
|
activeWallpaper={activeWallpaper}
|
||||||
pocketRegion={pocketRegion}
|
pocketRegion={pocketRegion}
|
||||||
|
mayHaveTopicSections={mayHaveTopicSections}
|
||||||
mayHaveSponsoredTopSites={mayHaveSponsoredTopSites}
|
mayHaveSponsoredTopSites={mayHaveSponsoredTopSites}
|
||||||
mayHaveSponsoredStories={mayHaveSponsoredStories}
|
mayHaveSponsoredStories={mayHaveSponsoredStories}
|
||||||
mayHaveWeather={mayHaveWeather}
|
mayHaveWeather={mayHaveWeather}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { actionCreators as ac } from "common/Actions.mjs";
|
import { actionCreators as ac } from "common/Actions.mjs";
|
||||||
|
import { SectionsMgmtPanel } from "../SectionsMgmtPanel/SectionsMgmtPanel";
|
||||||
import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
|
import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
|
||||||
import { WallpapersSection } from "../../WallpapersSection/WallpapersSection";
|
import { WallpapersSection } from "../../WallpapersSection/WallpapersSection";
|
||||||
import { WallpaperCategories } from "../../WallpapersSection/WallpaperCategories";
|
import { WallpaperCategories } from "../../WallpapersSection/WallpaperCategories";
|
||||||
@@ -103,6 +104,8 @@ export class ContentSection extends React.PureComponent {
|
|||||||
wallpapersV2Enabled,
|
wallpapersV2Enabled,
|
||||||
activeWallpaper,
|
activeWallpaper,
|
||||||
setPref,
|
setPref,
|
||||||
|
mayHaveTopicSections,
|
||||||
|
exitEventFired,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
topSitesEnabled,
|
topSitesEnabled,
|
||||||
@@ -242,6 +245,9 @@ export class ContentSection extends React.PureComponent {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{mayHaveTopicSections && (
|
||||||
|
<SectionsMgmtPanel exitEventFired={exitEventFired} />
|
||||||
|
)}
|
||||||
{mayHaveRecentSaves && (
|
{mayHaveRecentSaves && (
|
||||||
<div className="check-wrapper" role="presentation">
|
<div className="check-wrapper" role="presentation">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -13,15 +13,20 @@ export class _CustomizeMenu extends React.PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
this.onEntered = this.onEntered.bind(this);
|
this.onEntered = this.onEntered.bind(this);
|
||||||
this.onExited = this.onExited.bind(this);
|
this.onExited = this.onExited.bind(this);
|
||||||
|
this.state = {
|
||||||
|
exitEventFired: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onEntered() {
|
onEntered() {
|
||||||
|
this.setState({ exitEventFired: false });
|
||||||
if (this.closeButton) {
|
if (this.closeButton) {
|
||||||
this.closeButton.focus();
|
this.closeButton.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onExited() {
|
onExited() {
|
||||||
|
this.setState({ exitEventFired: true });
|
||||||
if (this.openButton) {
|
if (this.openButton) {
|
||||||
this.openButton.focus();
|
this.openButton.focus();
|
||||||
}
|
}
|
||||||
@@ -77,12 +82,14 @@ export class _CustomizeMenu extends React.PureComponent {
|
|||||||
wallpapersV2Enabled={this.props.wallpapersV2Enabled}
|
wallpapersV2Enabled={this.props.wallpapersV2Enabled}
|
||||||
activeWallpaper={this.props.activeWallpaper}
|
activeWallpaper={this.props.activeWallpaper}
|
||||||
pocketRegion={this.props.pocketRegion}
|
pocketRegion={this.props.pocketRegion}
|
||||||
|
mayHaveTopicSections={this.props.mayHaveTopicSections}
|
||||||
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
|
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
|
||||||
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}
|
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}
|
||||||
mayHaveRecentSaves={this.props.DiscoveryStream.recentSavesEnabled}
|
mayHaveRecentSaves={this.props.DiscoveryStream.recentSavesEnabled}
|
||||||
mayHaveWeather={this.props.mayHaveWeather}
|
mayHaveWeather={this.props.mayHaveWeather}
|
||||||
spocMessageVariant={this.props.spocMessageVariant}
|
spocMessageVariant={this.props.spocMessageVariant}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
exitEventFired={this.state.exitEventFired}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/* 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, useCallback, useEffect } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { actionCreators as ac } from "common/Actions.mjs";
|
||||||
|
// eslint-disable-next-line no-shadow
|
||||||
|
import { CSSTransition } from "react-transition-group";
|
||||||
|
|
||||||
|
const PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following";
|
||||||
|
const PREF_BLOCKED_SECTIONS = "discoverystream.sections.blocked";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a comma-separated string of topics in user preferences
|
||||||
|
* into a cleaned-up array.
|
||||||
|
*
|
||||||
|
* @param pref
|
||||||
|
* @returns string[]
|
||||||
|
*/
|
||||||
|
// TODO: DRY Issue: Import function from CardSections.jsx?
|
||||||
|
const getTopics = pref => {
|
||||||
|
return pref
|
||||||
|
.split(",")
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(item => item);
|
||||||
|
};
|
||||||
|
|
||||||
|
function SectionsMgmtPanel({ exitEventFired }) {
|
||||||
|
const [showPanel, setShowPanel] = useState(false); // State management with useState
|
||||||
|
const prefs = useSelector(state => state.Prefs.values);
|
||||||
|
const layoutComponents = useSelector(
|
||||||
|
state => state.DiscoveryStream.layout[0].components
|
||||||
|
);
|
||||||
|
const sections = useSelector(state => state.DiscoveryStream.feeds.data);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
// TODO: Wrap sectionsFeedName -> sectionsList logic in try...catch?
|
||||||
|
let sectionsFeedName;
|
||||||
|
|
||||||
|
const cardGridEntry = layoutComponents.find(item => item.type === "CardGrid");
|
||||||
|
|
||||||
|
if (cardGridEntry) {
|
||||||
|
sectionsFeedName = cardGridEntry.feed.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sectionsList;
|
||||||
|
|
||||||
|
if (sectionsFeedName) {
|
||||||
|
sectionsList = sections[sectionsFeedName].data.sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
const followedSectionsPref = prefs[PREF_FOLLOWED_SECTIONS] || "";
|
||||||
|
const blockedSectionsPref = prefs[PREF_BLOCKED_SECTIONS] || "";
|
||||||
|
const followedSections = getTopics(followedSectionsPref);
|
||||||
|
const blockedSections = getTopics(blockedSectionsPref);
|
||||||
|
|
||||||
|
const [followedSectionsState, setFollowedSectionsState] =
|
||||||
|
useState(followedSectionsPref); // State management with useState
|
||||||
|
const [blockedSectionsState, setBlockedSectionsState] =
|
||||||
|
useState(blockedSectionsPref); // State management with useState
|
||||||
|
|
||||||
|
let followedSectionsData = sectionsList.filter(item =>
|
||||||
|
followedSectionsState.includes(item.sectionKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
let blockedSectionsData = sectionsList.filter(item =>
|
||||||
|
blockedSectionsState.includes(item.sectionKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
function updateCachedData() {
|
||||||
|
// Reset cached followed/blocked list data while panel is open
|
||||||
|
setFollowedSectionsState(followedSectionsPref);
|
||||||
|
setBlockedSectionsState(blockedSectionsPref);
|
||||||
|
|
||||||
|
followedSectionsData = sectionsList.filter(item =>
|
||||||
|
followedSectionsState.includes(item.sectionKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
blockedSectionsData = sectionsList.filter(item =>
|
||||||
|
blockedSectionsState.includes(item.sectionKey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFollowClick = useCallback(
|
||||||
|
(sectionKey, receivedRank) => {
|
||||||
|
dispatch(
|
||||||
|
ac.SetPref(
|
||||||
|
PREF_FOLLOWED_SECTIONS,
|
||||||
|
[...followedSections, sectionKey].join(", ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(
|
||||||
|
ac.OnlyToMain({
|
||||||
|
type: "FOLLOW_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, followedSections]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onBlockClick = useCallback(
|
||||||
|
(sectionKey, receivedRank) => {
|
||||||
|
dispatch(
|
||||||
|
ac.SetPref(
|
||||||
|
PREF_BLOCKED_SECTIONS,
|
||||||
|
[...blockedSections, sectionKey].join(", ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(
|
||||||
|
ac.OnlyToMain({
|
||||||
|
type: "BLOCK_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, blockedSections]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onUnblockClick = useCallback(
|
||||||
|
(sectionKey, receivedRank) => {
|
||||||
|
dispatch(
|
||||||
|
ac.SetPref(
|
||||||
|
PREF_BLOCKED_SECTIONS,
|
||||||
|
[...blockedSections.filter(item => item !== sectionKey)].join(", ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(
|
||||||
|
ac.OnlyToMain({
|
||||||
|
type: "UNBLOCK_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, blockedSections]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onUnfollowClick = useCallback(
|
||||||
|
(sectionKey, receivedRank) => {
|
||||||
|
dispatch(
|
||||||
|
ac.SetPref(
|
||||||
|
PREF_FOLLOWED_SECTIONS,
|
||||||
|
[...followedSections.filter(item => item !== sectionKey)].join(", ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(
|
||||||
|
ac.OnlyToMain({
|
||||||
|
type: "UNFOLLOW_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, followedSections]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Close followed/blocked topic subpanel when parent menu is closed
|
||||||
|
useEffect(() => {
|
||||||
|
if (exitEventFired) {
|
||||||
|
setShowPanel(false);
|
||||||
|
}
|
||||||
|
}, [exitEventFired]);
|
||||||
|
|
||||||
|
const togglePanel = () => {
|
||||||
|
setShowPanel(prevShowPanel => !prevShowPanel);
|
||||||
|
|
||||||
|
// Fire when the panel is open
|
||||||
|
if (!showPanel) {
|
||||||
|
updateCachedData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const followedSectionsList = followedSectionsData.map(
|
||||||
|
({ sectionKey, title, receivedRank }) => {
|
||||||
|
const following = followedSections.includes(sectionKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={sectionKey}>
|
||||||
|
<label htmlFor={`follow-topic-${sectionKey}`}>{title}</label>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
following ? "section-follow following" : "section-follow"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<moz-button
|
||||||
|
onClick={() =>
|
||||||
|
following
|
||||||
|
? onUnfollowClick(sectionKey, receivedRank)
|
||||||
|
: onFollowClick(sectionKey, receivedRank)
|
||||||
|
}
|
||||||
|
type={following ? "destructive" : "default"}
|
||||||
|
index={receivedRank}
|
||||||
|
section={sectionKey}
|
||||||
|
id={`follow-topic-${sectionKey}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="section-button-follow-text"
|
||||||
|
data-l10n-id="newtab-section-follow-button"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="section-button-following-text"
|
||||||
|
data-l10n-id="newtab-section-following-button"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="section-button-unfollow-text"
|
||||||
|
data-l10n-id="newtab-section-unfollow-button"
|
||||||
|
/>
|
||||||
|
</moz-button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const blockedSectionsList = blockedSectionsData.map(
|
||||||
|
({ sectionKey, title, receivedRank }) => {
|
||||||
|
const blocked = blockedSections.includes(sectionKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={sectionKey}>
|
||||||
|
<label htmlFor={`blocked-topic-${sectionKey}`}>{title}</label>
|
||||||
|
<div className={blocked ? "section-block blocked" : "section-block"}>
|
||||||
|
<moz-button
|
||||||
|
onClick={() =>
|
||||||
|
blocked
|
||||||
|
? onUnblockClick(sectionKey, receivedRank)
|
||||||
|
: onBlockClick(sectionKey, receivedRank)
|
||||||
|
}
|
||||||
|
type="default"
|
||||||
|
index={receivedRank}
|
||||||
|
section={sectionKey}
|
||||||
|
id={`blocked-topic-${sectionKey}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="section-button-block-text"
|
||||||
|
data-l10n-id="newtab-section-block-button"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="section-button-blocked-text"
|
||||||
|
data-l10n-id="newtab-section-blocked-button"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="section-button-unblock-text"
|
||||||
|
data-l10n-id="newtab-section-unblock-button"
|
||||||
|
/>
|
||||||
|
</moz-button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<moz-box-button
|
||||||
|
onClick={togglePanel}
|
||||||
|
data-l10n-id="newtab-section-mangage-topics-button"
|
||||||
|
></moz-box-button>
|
||||||
|
<CSSTransition
|
||||||
|
in={showPanel}
|
||||||
|
timeout={300}
|
||||||
|
classNames="sections-mgmt-panel"
|
||||||
|
unmountOnExit={true}
|
||||||
|
>
|
||||||
|
<div className="sections-mgmt-panel">
|
||||||
|
<button className="arrow-button" onClick={togglePanel}>
|
||||||
|
<h1 data-l10n-id="newtab-section-mangage-topics-title"></h1>
|
||||||
|
</button>
|
||||||
|
<h3 data-l10n-id="newtab-section-mangage-topics-followed-topics-subtitle"></h3>
|
||||||
|
{followedSectionsData.length ? (
|
||||||
|
<ul className="topic-list">{followedSectionsList}</ul>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className="topic-list-empty-state"
|
||||||
|
data-l10n-id="newtab-section-mangage-topics-followed-topics-empty-state"
|
||||||
|
></span>
|
||||||
|
)}
|
||||||
|
<h3 data-l10n-id="newtab-section-mangage-topics-blocked-topics-subtitle"></h3>
|
||||||
|
{blockedSectionsData.length ? (
|
||||||
|
<ul className="topic-list">{blockedSectionsList}</ul>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className="topic-list-empty-state"
|
||||||
|
data-l10n-id="newtab-section-mangage-topics-blocked-topics-empty-state"
|
||||||
|
></span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SectionsMgmtPanel };
|
||||||
@@ -252,6 +252,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.more-info-pocket-wrapper {
|
||||||
|
.more-information {
|
||||||
|
// Note: This is necessary so the follow/block topics panel can
|
||||||
|
// be positioned absolutely across the entire Customize menu
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-wrapper {
|
||||||
|
margin-block-end: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.more-info-top-wrapper {
|
.more-info-top-wrapper {
|
||||||
.check-wrapper {
|
.check-wrapper {
|
||||||
margin-block-start: var(--space-large);
|
margin-block-start: var(--space-large);
|
||||||
@@ -349,3 +361,157 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sections-mgmt-panel {
|
||||||
|
/* XXXdholbert The 32px subtraction here is to account for our 16px of
|
||||||
|
* margin on top and bottom. Ideally this should change to use
|
||||||
|
* 'height: stretch' when bug 1789477 lands. */
|
||||||
|
height: calc(100% - var(--space-xxlarge));
|
||||||
|
// Width of panel minus the margins
|
||||||
|
inset-inline-start: var(--space-xlarge);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 380px;
|
||||||
|
z-index: 2;
|
||||||
|
transform: translateX(100%);
|
||||||
|
margin-block: var(--space-large) 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: var(--newtab-background-color-secondary);
|
||||||
|
|
||||||
|
&:dir(rtl) {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
&.sections-mgmt-panel-enter,
|
||||||
|
&.sections-mgmt-panel-exit,
|
||||||
|
&.sections-mgmt-panel-enter-done,
|
||||||
|
&.sections-mgmt-panel-exit-done {
|
||||||
|
transition: transform 300ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sections-mgmt-panel-enter-done,
|
||||||
|
&.sections-mgmt-panel-enter-active {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typography
|
||||||
|
h3 {
|
||||||
|
margin-block: 0 var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
// List
|
||||||
|
.topic-list {
|
||||||
|
@include wallpaper-contrast-fix;
|
||||||
|
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-small);
|
||||||
|
margin-block: 0 var(--space-xxlarge);
|
||||||
|
padding-inline: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-list-empty-state {
|
||||||
|
display: block;
|
||||||
|
margin-block-end: var(--space-xxlarge);
|
||||||
|
color: var(--text-color-deemphasized);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
.arrow-button {
|
||||||
|
background: url('chrome://global/skin/icons/arrow-left.svg') no-repeat left center;
|
||||||
|
|
||||||
|
&:dir(rtl) {
|
||||||
|
background: url('chrome://global/skin/icons/arrow-right.svg') no-repeat right center;
|
||||||
|
}
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-root);
|
||||||
|
margin-block-end: var(--space-xlarge);
|
||||||
|
padding-inline-start: var(--space-xlarge);
|
||||||
|
-moz-context-properties: fill;
|
||||||
|
fill: currentColor;
|
||||||
|
min-height: var(--space-large);
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: var(--font-size-root);
|
||||||
|
margin-block: 0;
|
||||||
|
font-weight: var(--button-font-weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow / Unfollow and Block / Unblock Buttons
|
||||||
|
.section-block,
|
||||||
|
.section-follow {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.section-button-blocked-text,
|
||||||
|
.section-button-following-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-button-unblock-text,
|
||||||
|
.section-button-unfollow-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.following {
|
||||||
|
.section-button-follow-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-button-following-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.following:not(:hover) {
|
||||||
|
moz-button {
|
||||||
|
--button-background-color-destructive: var(--button-background-color);
|
||||||
|
--button-text-color-destructive: var(--button-text-color);
|
||||||
|
--button-border-color-destructive: var(--button-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.following:hover {
|
||||||
|
.section-button-following-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-button-unfollow-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.blocked {
|
||||||
|
.section-button-block-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-button-blocked-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.blocked:hover {
|
||||||
|
.section-button-blocked-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-button-unblock-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -2209,6 +2209,12 @@ main section {
|
|||||||
position: relative;
|
position: relative;
|
||||||
transition: margin-top 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
|
transition: margin-top 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
|
||||||
}
|
}
|
||||||
|
.home-section .section .more-info-pocket-wrapper .more-information {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
.home-section .section .more-info-pocket-wrapper .check-wrapper {
|
||||||
|
margin-block-end: var(--space-small);
|
||||||
|
}
|
||||||
.home-section .section .more-info-top-wrapper .check-wrapper {
|
.home-section .section .more-info-top-wrapper .check-wrapper {
|
||||||
margin-block-start: var(--space-large);
|
margin-block-start: var(--space-large);
|
||||||
}
|
}
|
||||||
@@ -2283,6 +2289,133 @@ main section {
|
|||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sections-mgmt-panel {
|
||||||
|
/* XXXdholbert The 32px subtraction here is to account for our 16px of
|
||||||
|
* margin on top and bottom. Ideally this should change to use
|
||||||
|
* 'height: stretch' when bug 1789477 lands. */
|
||||||
|
height: calc(100% - var(--space-xxlarge));
|
||||||
|
inset-inline-start: var(--space-xlarge);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 380px;
|
||||||
|
z-index: 2;
|
||||||
|
transform: translateX(100%);
|
||||||
|
margin-block: var(--space-large) 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: var(--newtab-background-color-secondary);
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel:dir(rtl) {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.sections-mgmt-panel.sections-mgmt-panel-enter, .sections-mgmt-panel.sections-mgmt-panel-exit, .sections-mgmt-panel.sections-mgmt-panel-enter-done, .sections-mgmt-panel.sections-mgmt-panel-exit-done {
|
||||||
|
transition: transform 300ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel.sections-mgmt-panel-enter-done, .sections-mgmt-panel.sections-mgmt-panel-enter-active {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel h3 {
|
||||||
|
margin-block: 0 var(--space-small);
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .topic-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-small);
|
||||||
|
margin-block: 0 var(--space-xxlarge);
|
||||||
|
padding-inline: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.lightWallpaper .sections-mgmt-panel .topic-list {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
.darkWallpaper .sections-mgmt-panel .topic-list {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .topic-list li {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .topic-list-empty-state {
|
||||||
|
display: block;
|
||||||
|
margin-block-end: var(--space-xxlarge);
|
||||||
|
color: var(--text-color-deemphasized);
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .arrow-button {
|
||||||
|
background: url("chrome://global/skin/icons/arrow-left.svg") no-repeat left center;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: var(--font-size-root);
|
||||||
|
margin-block-end: var(--space-xlarge);
|
||||||
|
padding-inline-start: var(--space-xlarge);
|
||||||
|
-moz-context-properties: fill;
|
||||||
|
fill: currentColor;
|
||||||
|
min-height: var(--space-large);
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .arrow-button:dir(rtl) {
|
||||||
|
background: url("chrome://global/skin/icons/arrow-right.svg") no-repeat right center;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .arrow-button h1 {
|
||||||
|
font-size: var(--font-size-root);
|
||||||
|
margin-block: 0;
|
||||||
|
font-weight: var(--button-font-weight);
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block,
|
||||||
|
.sections-mgmt-panel .section-follow {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block .section-button-blocked-text,
|
||||||
|
.sections-mgmt-panel .section-block .section-button-following-text,
|
||||||
|
.sections-mgmt-panel .section-follow .section-button-blocked-text,
|
||||||
|
.sections-mgmt-panel .section-follow .section-button-following-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block .section-button-unblock-text,
|
||||||
|
.sections-mgmt-panel .section-block .section-button-unfollow-text,
|
||||||
|
.sections-mgmt-panel .section-follow .section-button-unblock-text,
|
||||||
|
.sections-mgmt-panel .section-follow .section-button-unfollow-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.following .section-button-follow-text,
|
||||||
|
.sections-mgmt-panel .section-follow.following .section-button-follow-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.following .section-button-following-text,
|
||||||
|
.sections-mgmt-panel .section-follow.following .section-button-following-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.following:not(:hover) moz-button,
|
||||||
|
.sections-mgmt-panel .section-follow.following:not(:hover) moz-button {
|
||||||
|
--button-background-color-destructive: var(--button-background-color);
|
||||||
|
--button-text-color-destructive: var(--button-text-color);
|
||||||
|
--button-border-color-destructive: var(--button-border-color);
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.following:hover .section-button-following-text,
|
||||||
|
.sections-mgmt-panel .section-follow.following:hover .section-button-following-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.following:hover .section-button-unfollow-text,
|
||||||
|
.sections-mgmt-panel .section-follow.following:hover .section-button-unfollow-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.blocked .section-button-block-text,
|
||||||
|
.sections-mgmt-panel .section-follow.blocked .section-button-block-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.blocked .section-button-blocked-text,
|
||||||
|
.sections-mgmt-panel .section-follow.blocked .section-button-blocked-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.blocked:hover .section-button-blocked-text,
|
||||||
|
.sections-mgmt-panel .section-follow.blocked:hover .section-button-blocked-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sections-mgmt-panel .section-block.blocked:hover .section-button-unblock-text,
|
||||||
|
.sections-mgmt-panel .section-follow.blocked:hover .section-button-unblock-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.category-list {
|
.category-list {
|
||||||
border: none;
|
border: none;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -45,5 +45,6 @@
|
|||||||
<script async type="module" src="chrome://global/content/elements/moz-toggle.mjs"></script>
|
<script async type="module" src="chrome://global/content/elements/moz-toggle.mjs"></script>
|
||||||
<script async type="module" src="chrome://global/content/elements/moz-button.mjs"></script>
|
<script async type="module" src="chrome://global/content/elements/moz-button.mjs"></script>
|
||||||
<script async type="module" src="chrome://global/content/elements/moz-button-group.mjs"></script>
|
<script async type="module" src="chrome://global/content/elements/moz-button-group.mjs"></script>
|
||||||
|
<script async type="module" src="chrome://global/content/elements/moz-box-button.mjs"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ for (const type of [
|
|||||||
"TOP_SITES_UPDATED",
|
"TOP_SITES_UPDATED",
|
||||||
"TOTAL_BOOKMARKS_REQUEST",
|
"TOTAL_BOOKMARKS_REQUEST",
|
||||||
"TOTAL_BOOKMARKS_RESPONSE",
|
"TOTAL_BOOKMARKS_RESPONSE",
|
||||||
|
"UNBLOCK_SECTION",
|
||||||
"UNFOLLOW_SECTION",
|
"UNFOLLOW_SECTION",
|
||||||
"UNINIT",
|
"UNINIT",
|
||||||
"UPDATE_PINNED_SEARCH_SHORTCUTS",
|
"UPDATE_PINNED_SEARCH_SHORTCUTS",
|
||||||
@@ -10695,6 +10696,219 @@ const DiscoveryStreamBase = (0,external_ReactRedux_namespaceObject.connect)(stat
|
|||||||
document: globalThis.document,
|
document: globalThis.document,
|
||||||
App: state.App
|
App: state.App
|
||||||
}))(_DiscoveryStreamBase);
|
}))(_DiscoveryStreamBase);
|
||||||
|
;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/SectionsMgmtPanel/SectionsMgmtPanel.jsx
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-shadow
|
||||||
|
|
||||||
|
const SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS = "discoverystream.sections.following";
|
||||||
|
const SectionsMgmtPanel_PREF_BLOCKED_SECTIONS = "discoverystream.sections.blocked";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a comma-separated string of topics in user preferences
|
||||||
|
* into a cleaned-up array.
|
||||||
|
*
|
||||||
|
* @param pref
|
||||||
|
* @returns string[]
|
||||||
|
*/
|
||||||
|
// TODO: DRY Issue: Import function from CardSections.jsx?
|
||||||
|
const SectionsMgmtPanel_getTopics = pref => {
|
||||||
|
return pref.split(",").map(item => item.trim()).filter(item => item);
|
||||||
|
};
|
||||||
|
function SectionsMgmtPanel({
|
||||||
|
exitEventFired
|
||||||
|
}) {
|
||||||
|
const [showPanel, setShowPanel] = (0,external_React_namespaceObject.useState)(false); // State management with useState
|
||||||
|
const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
|
||||||
|
const layoutComponents = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.layout[0].components);
|
||||||
|
const sections = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.feeds.data);
|
||||||
|
const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
|
||||||
|
|
||||||
|
// TODO: Wrap sectionsFeedName -> sectionsList logic in try...catch?
|
||||||
|
let sectionsFeedName;
|
||||||
|
const cardGridEntry = layoutComponents.find(item => item.type === "CardGrid");
|
||||||
|
if (cardGridEntry) {
|
||||||
|
sectionsFeedName = cardGridEntry.feed.url;
|
||||||
|
}
|
||||||
|
let sectionsList;
|
||||||
|
if (sectionsFeedName) {
|
||||||
|
sectionsList = sections[sectionsFeedName].data.sections;
|
||||||
|
}
|
||||||
|
const followedSectionsPref = prefs[SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS] || "";
|
||||||
|
const blockedSectionsPref = prefs[SectionsMgmtPanel_PREF_BLOCKED_SECTIONS] || "";
|
||||||
|
const followedSections = SectionsMgmtPanel_getTopics(followedSectionsPref);
|
||||||
|
const blockedSections = SectionsMgmtPanel_getTopics(blockedSectionsPref);
|
||||||
|
const [followedSectionsState, setFollowedSectionsState] = (0,external_React_namespaceObject.useState)(followedSectionsPref); // State management with useState
|
||||||
|
const [blockedSectionsState, setBlockedSectionsState] = (0,external_React_namespaceObject.useState)(blockedSectionsPref); // State management with useState
|
||||||
|
|
||||||
|
let followedSectionsData = sectionsList.filter(item => followedSectionsState.includes(item.sectionKey));
|
||||||
|
let blockedSectionsData = sectionsList.filter(item => blockedSectionsState.includes(item.sectionKey));
|
||||||
|
function updateCachedData() {
|
||||||
|
// Reset cached followed/blocked list data while panel is open
|
||||||
|
setFollowedSectionsState(followedSectionsPref);
|
||||||
|
setBlockedSectionsState(blockedSectionsPref);
|
||||||
|
followedSectionsData = sectionsList.filter(item => followedSectionsState.includes(item.sectionKey));
|
||||||
|
blockedSectionsData = sectionsList.filter(item => blockedSectionsState.includes(item.sectionKey));
|
||||||
|
}
|
||||||
|
const onFollowClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
|
||||||
|
dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS, [...followedSections, sectionKey].join(", ")));
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(actionCreators.OnlyToMain({
|
||||||
|
type: "FOLLOW_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, [dispatch, followedSections]);
|
||||||
|
const onBlockClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
|
||||||
|
dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_BLOCKED_SECTIONS, [...blockedSections, sectionKey].join(", ")));
|
||||||
|
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(actionCreators.OnlyToMain({
|
||||||
|
type: "BLOCK_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, [dispatch, blockedSections]);
|
||||||
|
const onUnblockClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
|
||||||
|
dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_BLOCKED_SECTIONS, [...blockedSections.filter(item => item !== sectionKey)].join(", ")));
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(actionCreators.OnlyToMain({
|
||||||
|
type: "UNBLOCK_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, [dispatch, blockedSections]);
|
||||||
|
const onUnfollowClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
|
||||||
|
dispatch(actionCreators.SetPref(SectionsMgmtPanel_PREF_FOLLOWED_SECTIONS, [...followedSections.filter(item => item !== sectionKey)].join(", ")));
|
||||||
|
// Telemetry Event Dispatch
|
||||||
|
dispatch(actionCreators.OnlyToMain({
|
||||||
|
type: "UNFOLLOW_SECTION",
|
||||||
|
data: {
|
||||||
|
section: sectionKey,
|
||||||
|
section_position: receivedRank,
|
||||||
|
event_source: "CUSTOMIZE_PANEL"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, [dispatch, followedSections]);
|
||||||
|
|
||||||
|
// Close followed/blocked topic subpanel when parent menu is closed
|
||||||
|
(0,external_React_namespaceObject.useEffect)(() => {
|
||||||
|
if (exitEventFired) {
|
||||||
|
setShowPanel(false);
|
||||||
|
}
|
||||||
|
}, [exitEventFired]);
|
||||||
|
const togglePanel = () => {
|
||||||
|
setShowPanel(prevShowPanel => !prevShowPanel);
|
||||||
|
|
||||||
|
// Fire when the panel is open
|
||||||
|
if (!showPanel) {
|
||||||
|
updateCachedData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const followedSectionsList = followedSectionsData.map(({
|
||||||
|
sectionKey,
|
||||||
|
title,
|
||||||
|
receivedRank
|
||||||
|
}) => {
|
||||||
|
const following = followedSections.includes(sectionKey);
|
||||||
|
return /*#__PURE__*/external_React_default().createElement("li", {
|
||||||
|
key: sectionKey
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("label", {
|
||||||
|
htmlFor: `follow-topic-${sectionKey}`
|
||||||
|
}, title), /*#__PURE__*/external_React_default().createElement("div", {
|
||||||
|
className: following ? "section-follow following" : "section-follow"
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("moz-button", {
|
||||||
|
onClick: () => following ? onUnfollowClick(sectionKey, receivedRank) : onFollowClick(sectionKey, receivedRank),
|
||||||
|
type: following ? "destructive" : "default",
|
||||||
|
index: receivedRank,
|
||||||
|
section: sectionKey,
|
||||||
|
id: `follow-topic-${sectionKey}`
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "section-button-follow-text",
|
||||||
|
"data-l10n-id": "newtab-section-follow-button"
|
||||||
|
}), /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "section-button-following-text",
|
||||||
|
"data-l10n-id": "newtab-section-following-button"
|
||||||
|
}), /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "section-button-unfollow-text",
|
||||||
|
"data-l10n-id": "newtab-section-unfollow-button"
|
||||||
|
}))));
|
||||||
|
});
|
||||||
|
const blockedSectionsList = blockedSectionsData.map(({
|
||||||
|
sectionKey,
|
||||||
|
title,
|
||||||
|
receivedRank
|
||||||
|
}) => {
|
||||||
|
const blocked = blockedSections.includes(sectionKey);
|
||||||
|
return /*#__PURE__*/external_React_default().createElement("li", {
|
||||||
|
key: sectionKey
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("label", {
|
||||||
|
htmlFor: `blocked-topic-${sectionKey}`
|
||||||
|
}, title), /*#__PURE__*/external_React_default().createElement("div", {
|
||||||
|
className: blocked ? "section-block blocked" : "section-block"
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("moz-button", {
|
||||||
|
onClick: () => blocked ? onUnblockClick(sectionKey, receivedRank) : onBlockClick(sectionKey, receivedRank),
|
||||||
|
type: "default",
|
||||||
|
index: receivedRank,
|
||||||
|
section: sectionKey,
|
||||||
|
id: `blocked-topic-${sectionKey}`
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "section-button-block-text",
|
||||||
|
"data-l10n-id": "newtab-section-block-button"
|
||||||
|
}), /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "section-button-blocked-text",
|
||||||
|
"data-l10n-id": "newtab-section-blocked-button"
|
||||||
|
}), /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "section-button-unblock-text",
|
||||||
|
"data-l10n-id": "newtab-section-unblock-button"
|
||||||
|
}))));
|
||||||
|
});
|
||||||
|
return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("moz-box-button", {
|
||||||
|
onClick: togglePanel,
|
||||||
|
"data-l10n-id": "newtab-section-mangage-topics-button"
|
||||||
|
}), /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, {
|
||||||
|
in: showPanel,
|
||||||
|
timeout: 300,
|
||||||
|
classNames: "sections-mgmt-panel",
|
||||||
|
unmountOnExit: true
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("div", {
|
||||||
|
className: "sections-mgmt-panel"
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("button", {
|
||||||
|
className: "arrow-button",
|
||||||
|
onClick: togglePanel
|
||||||
|
}, /*#__PURE__*/external_React_default().createElement("h1", {
|
||||||
|
"data-l10n-id": "newtab-section-mangage-topics-title"
|
||||||
|
})), /*#__PURE__*/external_React_default().createElement("h3", {
|
||||||
|
"data-l10n-id": "newtab-section-mangage-topics-followed-topics-subtitle"
|
||||||
|
}), followedSectionsData.length ? /*#__PURE__*/external_React_default().createElement("ul", {
|
||||||
|
className: "topic-list"
|
||||||
|
}, followedSectionsList) : /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "topic-list-empty-state",
|
||||||
|
"data-l10n-id": "newtab-section-mangage-topics-followed-topics-empty-state"
|
||||||
|
}), /*#__PURE__*/external_React_default().createElement("h3", {
|
||||||
|
"data-l10n-id": "newtab-section-mangage-topics-blocked-topics-subtitle"
|
||||||
|
}), blockedSectionsData.length ? /*#__PURE__*/external_React_default().createElement("ul", {
|
||||||
|
className: "topic-list"
|
||||||
|
}, blockedSectionsList) : /*#__PURE__*/external_React_default().createElement("span", {
|
||||||
|
className: "topic-list-empty-state",
|
||||||
|
"data-l10n-id": "newtab-section-mangage-topics-blocked-topics-empty-state"
|
||||||
|
}))));
|
||||||
|
}
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./content-src/components/WallpapersSection/WallpapersSection.jsx
|
;// CONCATENATED MODULE: ./content-src/components/WallpapersSection/WallpapersSection.jsx
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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,
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
@@ -11080,6 +11294,7 @@ const WallpaperCategories = (0,external_ReactRedux_namespaceObject.connect)(stat
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ContentSection extends (external_React_default()).PureComponent {
|
class ContentSection extends (external_React_default()).PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -11162,7 +11377,9 @@ class ContentSection extends (external_React_default()).PureComponent {
|
|||||||
wallpapersEnabled,
|
wallpapersEnabled,
|
||||||
wallpapersV2Enabled,
|
wallpapersV2Enabled,
|
||||||
activeWallpaper,
|
activeWallpaper,
|
||||||
setPref
|
setPref,
|
||||||
|
mayHaveTopicSections,
|
||||||
|
exitEventFired
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
topSitesEnabled,
|
topSitesEnabled,
|
||||||
@@ -11280,7 +11497,9 @@ class ContentSection extends (external_React_default()).PureComponent {
|
|||||||
className: "sponsored",
|
className: "sponsored",
|
||||||
htmlFor: "sponsored-pocket",
|
htmlFor: "sponsored-pocket",
|
||||||
"data-l10n-id": "newtab-custom-pocket-sponsored"
|
"data-l10n-id": "newtab-custom-pocket-sponsored"
|
||||||
})), mayHaveRecentSaves && /*#__PURE__*/external_React_default().createElement("div", {
|
})), mayHaveTopicSections && /*#__PURE__*/external_React_default().createElement(SectionsMgmtPanel, {
|
||||||
|
exitEventFired: exitEventFired
|
||||||
|
}), mayHaveRecentSaves && /*#__PURE__*/external_React_default().createElement("div", {
|
||||||
className: "check-wrapper",
|
className: "check-wrapper",
|
||||||
role: "presentation"
|
role: "presentation"
|
||||||
}, /*#__PURE__*/external_React_default().createElement("input", {
|
}, /*#__PURE__*/external_React_default().createElement("input", {
|
||||||
@@ -11349,13 +11568,22 @@ class _CustomizeMenu extends (external_React_default()).PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
this.onEntered = this.onEntered.bind(this);
|
this.onEntered = this.onEntered.bind(this);
|
||||||
this.onExited = this.onExited.bind(this);
|
this.onExited = this.onExited.bind(this);
|
||||||
|
this.state = {
|
||||||
|
exitEventFired: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
onEntered() {
|
onEntered() {
|
||||||
|
this.setState({
|
||||||
|
exitEventFired: false
|
||||||
|
});
|
||||||
if (this.closeButton) {
|
if (this.closeButton) {
|
||||||
this.closeButton.focus();
|
this.closeButton.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onExited() {
|
onExited() {
|
||||||
|
this.setState({
|
||||||
|
exitEventFired: true
|
||||||
|
});
|
||||||
if (this.openButton) {
|
if (this.openButton) {
|
||||||
this.openButton.focus();
|
this.openButton.focus();
|
||||||
}
|
}
|
||||||
@@ -11402,12 +11630,14 @@ class _CustomizeMenu extends (external_React_default()).PureComponent {
|
|||||||
wallpapersV2Enabled: this.props.wallpapersV2Enabled,
|
wallpapersV2Enabled: this.props.wallpapersV2Enabled,
|
||||||
activeWallpaper: this.props.activeWallpaper,
|
activeWallpaper: this.props.activeWallpaper,
|
||||||
pocketRegion: this.props.pocketRegion,
|
pocketRegion: this.props.pocketRegion,
|
||||||
|
mayHaveTopicSections: this.props.mayHaveTopicSections,
|
||||||
mayHaveSponsoredTopSites: this.props.mayHaveSponsoredTopSites,
|
mayHaveSponsoredTopSites: this.props.mayHaveSponsoredTopSites,
|
||||||
mayHaveSponsoredStories: this.props.mayHaveSponsoredStories,
|
mayHaveSponsoredStories: this.props.mayHaveSponsoredStories,
|
||||||
mayHaveRecentSaves: this.props.DiscoveryStream.recentSavesEnabled,
|
mayHaveRecentSaves: this.props.DiscoveryStream.recentSavesEnabled,
|
||||||
mayHaveWeather: this.props.mayHaveWeather,
|
mayHaveWeather: this.props.mayHaveWeather,
|
||||||
spocMessageVariant: this.props.spocMessageVariant,
|
spocMessageVariant: this.props.spocMessageVariant,
|
||||||
dispatch: this.props.dispatch
|
dispatch: this.props.dispatch,
|
||||||
|
exitEventFired: this.state.exitEventFired
|
||||||
}))));
|
}))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13005,6 +13235,10 @@ class BaseContent extends (external_React_default()).PureComponent {
|
|||||||
const hasThumbsUpDownLayout = prefs["discoverystream.thumbsUpDown.searchTopsitesCompact"];
|
const hasThumbsUpDownLayout = prefs["discoverystream.thumbsUpDown.searchTopsitesCompact"];
|
||||||
const hasThumbsUpDown = prefs["discoverystream.thumbsUpDown.enabled"];
|
const hasThumbsUpDown = prefs["discoverystream.thumbsUpDown.enabled"];
|
||||||
const sectionsEnabled = prefs["discoverystream.sections.enabled"];
|
const sectionsEnabled = prefs["discoverystream.sections.enabled"];
|
||||||
|
const topicLabelsEnabled = prefs["discoverystream.topicLabels.enabled"];
|
||||||
|
const sectionsCustomizeMenuPanelEnabled = prefs["discoverystream.sections.customizeMenuPanel.enabled"];
|
||||||
|
// Logic to show follow/block topic mgmt panel in Customize panel
|
||||||
|
const mayHaveTopicSections = topicLabelsEnabled && sectionsEnabled && sectionsCustomizeMenuPanelEnabled;
|
||||||
const featureClassName = [weatherEnabled && mayHaveWeather && "has-weather",
|
const featureClassName = [weatherEnabled && mayHaveWeather && "has-weather",
|
||||||
// Show is weather is enabled/visible
|
// Show is weather is enabled/visible
|
||||||
prefs.showSearch ? "has-search" : "no-search", layoutsVariantAEnabled ? "layout-variant-a" : "",
|
prefs.showSearch ? "has-search" : "no-search", layoutsVariantAEnabled ? "layout-variant-a" : "",
|
||||||
@@ -13030,6 +13264,7 @@ class BaseContent extends (external_React_default()).PureComponent {
|
|||||||
wallpapersV2Enabled: wallpapersV2Enabled,
|
wallpapersV2Enabled: wallpapersV2Enabled,
|
||||||
activeWallpaper: activeWallpaper,
|
activeWallpaper: activeWallpaper,
|
||||||
pocketRegion: pocketRegion,
|
pocketRegion: pocketRegion,
|
||||||
|
mayHaveTopicSections: mayHaveTopicSections,
|
||||||
mayHaveSponsoredTopSites: mayHaveSponsoredTopSites,
|
mayHaveSponsoredTopSites: mayHaveSponsoredTopSites,
|
||||||
mayHaveSponsoredStories: mayHaveSponsoredStories,
|
mayHaveSponsoredStories: mayHaveSponsoredStories,
|
||||||
mayHaveWeather: mayHaveWeather,
|
mayHaveWeather: mayHaveWeather,
|
||||||
|
|||||||
@@ -520,6 +520,14 @@ export const PREFS_CONFIG = new Map([
|
|||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"discoverystream.sections.customizeMenuPanel.enabled",
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
"Boolean flag to enable the setions management panel in Customize menu",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"discoverystream.sections.cards.enabled",
|
"discoverystream.sections.cards.enabled",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1195,6 +1195,8 @@ export class TelemetryFeed {
|
|||||||
// Intentional fall-through
|
// Intentional fall-through
|
||||||
case at.FOLLOW_SECTION:
|
case at.FOLLOW_SECTION:
|
||||||
// Intentional fall-through
|
// Intentional fall-through
|
||||||
|
case at.UNBLOCK_SECTION:
|
||||||
|
// Intentional fall-through
|
||||||
case at.UNFOLLOW_SECTION: {
|
case at.UNFOLLOW_SECTION: {
|
||||||
this.handleCardSectionUserEvent(action);
|
this.handleCardSectionUserEvent(action);
|
||||||
break;
|
break;
|
||||||
@@ -1238,6 +1240,14 @@ export class TelemetryFeed {
|
|||||||
event_source,
|
event_source,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "UNBLOCK_SECTION":
|
||||||
|
Glean.newtab.sectionsUnblockSection.record({
|
||||||
|
newtab_visit_id: session.session_id,
|
||||||
|
section,
|
||||||
|
section_position,
|
||||||
|
event_source,
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "CARD_SECTION_IMPRESSION":
|
case "CARD_SECTION_IMPRESSION":
|
||||||
Glean.newtab.sectionsImpression.record({
|
Glean.newtab.sectionsImpression.record({
|
||||||
newtab_visit_id: session.session_id,
|
newtab_visit_id: session.session_id,
|
||||||
|
|||||||
@@ -783,6 +783,36 @@ newtab:
|
|||||||
send_in_pings:
|
send_in_pings:
|
||||||
- newtab
|
- newtab
|
||||||
|
|
||||||
|
sections_unblock_section:
|
||||||
|
type: event
|
||||||
|
description: >
|
||||||
|
Recorded when a section is unblocked
|
||||||
|
bugs:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1940566
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1940566
|
||||||
|
data_sensitivity:
|
||||||
|
- interaction
|
||||||
|
notification_emails:
|
||||||
|
- mcrawford@mozilla.com
|
||||||
|
expires: never
|
||||||
|
extra_keys:
|
||||||
|
newtab_visit_id: *newtab_visit_id
|
||||||
|
section:
|
||||||
|
description: >
|
||||||
|
section that had unblock event
|
||||||
|
type: string
|
||||||
|
section_position:
|
||||||
|
description: >
|
||||||
|
position of section on newtab
|
||||||
|
type: string
|
||||||
|
event_source:
|
||||||
|
description: >
|
||||||
|
Where the source of the event originated ("button", "context menu", etc.)
|
||||||
|
type: string
|
||||||
|
send_in_pings:
|
||||||
|
- newtab
|
||||||
|
|
||||||
newtab.search:
|
newtab.search:
|
||||||
enabled:
|
enabled:
|
||||||
lifetime: application
|
lifetime: application
|
||||||
|
|||||||
@@ -62,5 +62,10 @@
|
|||||||
type="module"
|
type="module"
|
||||||
src="chrome://global/content/elements/moz-button-group.mjs"
|
src="chrome://global/content/elements/moz-button-group.mjs"
|
||||||
></script>
|
></script>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
type="module"
|
||||||
|
src="chrome://global/content/elements/moz-box-button.mjs"
|
||||||
|
></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -51,5 +51,10 @@
|
|||||||
type="module"
|
type="module"
|
||||||
src="chrome://global/content/elements/moz-button-group.mjs"
|
src="chrome://global/content/elements/moz-button-group.mjs"
|
||||||
></script>
|
></script>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
type="module"
|
||||||
|
src="chrome://global/content/elements/moz-box-button.mjs"
|
||||||
|
></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -62,5 +62,10 @@
|
|||||||
type="module"
|
type="module"
|
||||||
src="chrome://global/content/elements/moz-button-group.mjs"
|
src="chrome://global/content/elements/moz-button-group.mjs"
|
||||||
></script>
|
></script>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
type="module"
|
||||||
|
src="chrome://global/content/elements/moz-box-button.mjs"
|
||||||
|
></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -445,9 +445,27 @@ newtab-section-follow-button = Follow
|
|||||||
newtab-section-following-button = Following
|
newtab-section-following-button = Following
|
||||||
newtab-section-unfollow-button = Unfollow
|
newtab-section-unfollow-button = Unfollow
|
||||||
|
|
||||||
|
## Button to block/unblock listed topics
|
||||||
|
## "Block", "unblocked", and "blocked" are social media terms that refer to hiding a section of stories.
|
||||||
|
## e.g. Blocked the politics section of stories.
|
||||||
|
|
||||||
|
newtab-section-block-button = Block
|
||||||
|
newtab-section-blocked-button = Blocked
|
||||||
|
newtab-section-unblock-button = Unblock
|
||||||
|
|
||||||
## Confirmation modal for blocking a section
|
## Confirmation modal for blocking a section
|
||||||
|
|
||||||
newtab-section-confirm-block-section-p1 = Are you sure you want to block this section?
|
newtab-section-confirm-block-section-p1 = Are you sure you want to block this section?
|
||||||
newtab-section-confirm-block-section-p2 = Blocked section will no longer appear in your feed.
|
newtab-section-confirm-block-section-p2 = Blocked section will no longer appear in your feed.
|
||||||
newtab-section-block-section-button = Block this section
|
newtab-section-block-section-button = Block this section
|
||||||
newtab-section-cancel-button = Not now
|
newtab-section-cancel-button = Not now
|
||||||
|
|
||||||
|
## Panel in the Customize menu section to manage followed and blocked topics
|
||||||
|
|
||||||
|
newtab-section-mangage-topics-title = Topics
|
||||||
|
newtab-section-mangage-topics-button =
|
||||||
|
.label = Followed and blocked topics
|
||||||
|
newtab-section-mangage-topics-followed-topics-subtitle = Followed Topics
|
||||||
|
newtab-section-mangage-topics-followed-topics-empty-state = You have not followed any topics yet.
|
||||||
|
newtab-section-mangage-topics-blocked-topics-subtitle = Blocked Topics
|
||||||
|
newtab-section-mangage-topics-blocked-topics-empty-state = You have not blocked any topics yet.
|
||||||
|
|||||||
Reference in New Issue
Block a user