зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1549863 - Add 3-card layout, card adjustments and bug fixes to Activity Stream r=r1cky
Differential Revision: https://phabricator.services.mozilla.com/D30286 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
5730f5fc1c
Коммит
5fda4b07d2
|
@ -100,9 +100,9 @@ function templateHTML(options, html) {
|
|||
<link rel="stylesheet" href="${options.baseUrl}css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root">${isPrerendered ? html : "<!-- Regular React Rendering -->"}</div>
|
||||
<div id="footer-asrouter-container"></div>${options.noscripts ? "" : scriptRender}
|
||||
<div id="footer-asrouter-container" role="presentation"></div>${options.noscripts ? "" : scriptRender}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
|
|
@ -43,6 +43,7 @@ for (const type of [
|
|||
"DISCOVERY_STREAM_CONFIG_SETUP",
|
||||
"DISCOVERY_STREAM_CONFIG_SET_VALUE",
|
||||
"DISCOVERY_STREAM_FEEDS_UPDATE",
|
||||
"DISCOVERY_STREAM_FEED_UPDATE",
|
||||
"DISCOVERY_STREAM_IMPRESSION_STATS",
|
||||
"DISCOVERY_STREAM_LAYOUT_RESET",
|
||||
"DISCOVERY_STREAM_LAYOUT_UPDATE",
|
||||
|
|
|
@ -495,10 +495,22 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
|
|||
...prevState,
|
||||
feeds: {
|
||||
...prevState.feeds,
|
||||
data: action.data || prevState.feeds.data,
|
||||
loaded: true,
|
||||
},
|
||||
};
|
||||
case at.DISCOVERY_STREAM_FEED_UPDATE:
|
||||
const newData = {};
|
||||
newData[action.data.url] = action.data.feed;
|
||||
return {
|
||||
...prevState,
|
||||
feeds: {
|
||||
...prevState.feeds,
|
||||
data: {
|
||||
...prevState.feeds.data,
|
||||
...newData,
|
||||
},
|
||||
},
|
||||
};
|
||||
case at.DISCOVERY_STREAM_SPOCS_CAPS:
|
||||
return {
|
||||
...prevState,
|
||||
|
|
|
@ -296,6 +296,7 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
const {message} = this.state;
|
||||
if (message.template === "trailhead") {
|
||||
return (<Trailhead
|
||||
document={this.props.document}
|
||||
message={message}
|
||||
onAction={ASRouterUtils.executeAction}
|
||||
onDoneButton={this.dismissBundle(this.state.bundle.bundle)}
|
||||
|
|
|
@ -26,7 +26,10 @@ export class ModalOverlayWrapper extends React.PureComponent {
|
|||
const {props} = this;
|
||||
return (<React.Fragment>
|
||||
<div className="modalOverlayOuter active" onClick={props.onClose} role="presentation" />
|
||||
<div className={`modalOverlayInner active ${props.innerClassName || ""}`}>
|
||||
<div className={`modalOverlayInner active ${props.innerClassName || ""}`}
|
||||
aria-labelledby={props.headerId}
|
||||
id={props.id}
|
||||
role="dialog">
|
||||
{props.children}
|
||||
</div>
|
||||
</React.Fragment>);
|
||||
|
|
|
@ -13,6 +13,8 @@ export class _StartupOverlay extends React.PureComponent {
|
|||
this.removeOverlay = this.removeOverlay.bind(this);
|
||||
this.onInputInvalid = this.onInputInvalid.bind(this);
|
||||
|
||||
this.utmParams = "utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-control";
|
||||
|
||||
this.state = {
|
||||
emailInput: "",
|
||||
overlayRemoved: false,
|
||||
|
@ -26,8 +28,8 @@ export class _StartupOverlay extends React.PureComponent {
|
|||
if (this.props.fxa_endpoint && !this.didFetch) {
|
||||
try {
|
||||
this.didFetch = true;
|
||||
const fxaParams = "entrypoint=activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun&form_type=email";
|
||||
const response = await fetch(`${this.props.fxa_endpoint}/metrics-flow?${fxaParams}`, {credentials: "omit"});
|
||||
const fxaParams = "entrypoint=activity-stream-firstrun&form_type=email";
|
||||
const response = await fetch(`${this.props.fxa_endpoint}/metrics-flow?${fxaParams}&${this.utmParams}`, {credentials: "omit"});
|
||||
if (response.status === 200) {
|
||||
const {flowId, flowBeginTime} = await response.json();
|
||||
this.setState({flowId, flowBeginTime});
|
||||
|
@ -106,8 +108,8 @@ export class _StartupOverlay extends React.PureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
let termsLink = (<a href={`${this.props.fxa_endpoint}/legal/terms`} target="_blank" rel="noopener noreferrer"><FormattedMessage id="firstrun_terms_of_service" /></a>);
|
||||
let privacyLink = (<a href={`${this.props.fxa_endpoint}/legal/privacy`} target="_blank" rel="noopener noreferrer"><FormattedMessage id="firstrun_privacy_notice" /></a>);
|
||||
let termsLink = (<a href={`${this.props.fxa_endpoint}/legal/terms?${this.utmParams}`} target="_blank" rel="noopener noreferrer"><FormattedMessage id="firstrun_terms_of_service" /></a>);
|
||||
let privacyLink = (<a href={`${this.props.fxa_endpoint}/legal/privacy?${this.utmParams}`} target="_blank" rel="noopener noreferrer"><FormattedMessage id="firstrun_privacy_notice" /></a>);
|
||||
|
||||
return (
|
||||
<div className={`overlay-wrapper ${this.state.show ? "show" : ""}`}>
|
||||
|
@ -117,7 +119,7 @@ export class _StartupOverlay extends React.PureComponent {
|
|||
<div className="firstrun-left-divider">
|
||||
<h1 className="firstrun-title"><FormattedMessage id="firstrun_title" /></h1>
|
||||
<p className="firstrun-content"><FormattedMessage id="firstrun_content" /></p>
|
||||
<a className="firstrun-link" href="https://www.mozilla.org/firefox/features/sync/" target="_blank" rel="noopener noreferrer"><FormattedMessage id="firstrun_learn_more_link" /></a>
|
||||
<a className="firstrun-link" href={`https://www.mozilla.org/firefox/features/sync/?${this.utmParams}`} target="_blank" rel="noopener noreferrer"><FormattedMessage id="firstrun_learn_more_link" /></a>
|
||||
</div>
|
||||
<div className="firstrun-sign-in">
|
||||
<p className="form-header"><FormattedMessage id="firstrun_form_header" /><span className="sub-header"><FormattedMessage id="firstrun_form_sub_header" /></span></p>
|
||||
|
@ -128,6 +130,8 @@ export class _StartupOverlay extends React.PureComponent {
|
|||
<input name="entrypoint" type="hidden" value="activity-stream-firstrun" />
|
||||
<input name="utm_source" type="hidden" value="activity-stream" />
|
||||
<input name="utm_campaign" type="hidden" value="firstrun" />
|
||||
<input name="utm_medium" type="hidden" value="referral" />
|
||||
<input name="utm_term" type="hidden" value="trailhead-control" />
|
||||
<input name="flow_id" type="hidden" value={this.state.flowId} />
|
||||
<input name="flow_begin_time" type="hidden" value={this.state.flowBeginTime} />
|
||||
<span className="error">{this.props.intl.formatMessage({id: "firstrun_invalid_input"})}</span>
|
||||
|
|
|
@ -6,22 +6,32 @@ import React from "react";
|
|||
|
||||
const FLUENT_FILES = [
|
||||
"branding/brand.ftl",
|
||||
"browser/branding/brandings.ftl",
|
||||
"browser/branding/sync-brand.ftl",
|
||||
// These are finalized strings exposed to localizers
|
||||
"browser/newtab/onboarding.ftl",
|
||||
// These are WIP/in-development strings that only get used if the string
|
||||
// doesn't already exist in onboarding.ftl above
|
||||
"trailhead.ftl",
|
||||
];
|
||||
|
||||
// From resource://devtools/client/shared/focus.js
|
||||
const FOCUSABLE_SELECTOR = [
|
||||
"a[href]:not([tabindex='-1'])",
|
||||
"button:not([disabled]):not([tabindex='-1'])",
|
||||
"iframe:not([tabindex='-1'])",
|
||||
"input:not([disabled]):not([tabindex='-1'])",
|
||||
"select:not([disabled]):not([tabindex='-1'])",
|
||||
"textarea:not([disabled]):not([tabindex='-1'])",
|
||||
"[tabindex]:not([tabindex='-1'])",
|
||||
].join(", ");
|
||||
|
||||
export class _Trailhead extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.closeModal = this.closeModal.bind(this);
|
||||
this.hideCardPanel = this.hideCardPanel.bind(this);
|
||||
this.onInputChange = this.onInputChange.bind(this);
|
||||
this.onStartBlur = this.onStartBlur.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onInputInvalid = this.onInputInvalid.bind(this);
|
||||
this.onCardAction = this.onCardAction.bind(this);
|
||||
|
||||
this.state = {
|
||||
emailInput: "",
|
||||
|
@ -34,6 +44,10 @@ export class _Trailhead extends React.PureComponent {
|
|||
this.didFetch = false;
|
||||
}
|
||||
|
||||
get dialog() {
|
||||
return this.props.document.getElementById("trailheadDialog");
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
FLUENT_FILES.forEach(file => {
|
||||
const link = document.head.appendChild(document.createElement("link"));
|
||||
|
@ -44,8 +58,9 @@ export class _Trailhead extends React.PureComponent {
|
|||
if (this.props.fxaEndpoint && !this.didFetch) {
|
||||
try {
|
||||
this.didFetch = true;
|
||||
const fxaParams = "entrypoint=activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun&utm_term=trailhead&form_type=email";
|
||||
const response = await fetch(`${this.props.fxaEndpoint}/metrics-flow?${fxaParams}`, {credentials: "omit"});
|
||||
const url = new URL(`${this.props.fxaEndpoint}/metrics-flow?entrypoint=activity-stream-firstrun&form_type=email`);
|
||||
this.addUtmParams(url);
|
||||
const response = await fetch(url, {credentials: "omit"});
|
||||
if (response.status === 200) {
|
||||
const {flowId, flowBeginTime} = await response.json();
|
||||
this.setState({flowId, flowBeginTime});
|
||||
|
@ -60,14 +75,20 @@ export class _Trailhead extends React.PureComponent {
|
|||
|
||||
componentDidMount() {
|
||||
// We need to remove hide-main since we should show it underneath everything that has rendered
|
||||
global.document.body.classList.remove("hide-main");
|
||||
this.props.document.body.classList.remove("hide-main");
|
||||
|
||||
// Add inline-onboarding class to disable fixed search header and fixed positioned settings icon
|
||||
global.document.body.classList.add("inline-onboarding");
|
||||
this.props.document.body.classList.add("inline-onboarding");
|
||||
|
||||
if (!this.props.message.content) {
|
||||
// The rest of the page is "hidden" when the modal is open
|
||||
if (this.props.message.content) {
|
||||
this.props.document.getElementById("root").setAttribute("aria-hidden", "true");
|
||||
|
||||
// Start with focus in the email input box
|
||||
this.dialog.querySelector("input[name=email]").focus();
|
||||
} else {
|
||||
// No modal overlay, let the user scroll and deal them some cards.
|
||||
global.document.body.classList.remove("welcome");
|
||||
this.props.document.body.classList.remove("welcome");
|
||||
|
||||
if (this.props.message.includeBundle || this.props.message.cards) {
|
||||
this.revealCards();
|
||||
|
@ -75,8 +96,8 @@ export class _Trailhead extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUnmount() {
|
||||
global.document.body.classList.remove("inline-onboarding");
|
||||
componentWillUnmount() {
|
||||
this.props.document.body.classList.remove("inline-onboarding");
|
||||
}
|
||||
|
||||
onInputChange(e) {
|
||||
|
@ -86,6 +107,16 @@ export class _Trailhead extends React.PureComponent {
|
|||
e.target.classList.remove("invalid");
|
||||
}
|
||||
|
||||
onStartBlur(event) {
|
||||
// Make sure focus stays within the dialog when tabbing from the button
|
||||
const {dialog} = this;
|
||||
if (event.relatedTarget &&
|
||||
!(dialog.compareDocumentPosition(event.relatedTarget) &
|
||||
dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
|
||||
dialog.querySelector(FOCUSABLE_SELECTOR).focus();
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.props.dispatch(ac.UserEvent({event: "SUBMIT_EMAIL", ...this._getFormInfo()}));
|
||||
|
||||
|
@ -94,7 +125,8 @@ export class _Trailhead extends React.PureComponent {
|
|||
|
||||
closeModal() {
|
||||
global.removeEventListener("visibilitychange", this.closeModal);
|
||||
global.document.body.classList.remove("welcome");
|
||||
this.props.document.body.classList.remove("welcome");
|
||||
this.props.document.getElementById("root").removeAttribute("aria-hidden");
|
||||
this.setState({isModalOpen: false});
|
||||
this.revealCards();
|
||||
this.props.dispatch(ac.UserEvent({event: "SKIPPED_SIGNIN", ...this._getFormInfo()}));
|
||||
|
@ -131,18 +163,55 @@ export class _Trailhead extends React.PureComponent {
|
|||
return str.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a url as a string or URL object and returns a URL object with the
|
||||
* utm_* parameters added to it. If a URL object is passed in, the paraemeters
|
||||
* are added to it (the return value can be ignored in that case as it's the
|
||||
* same object).
|
||||
*/
|
||||
addUtmParams(url, isCard = false) {
|
||||
let returnUrl = url;
|
||||
if (typeof returnUrl === "string") {
|
||||
returnUrl = new URL(url);
|
||||
}
|
||||
returnUrl.searchParams.append("utm_source", "activity-stream");
|
||||
returnUrl.searchParams.append("utm_campaign", "firstrun");
|
||||
returnUrl.searchParams.append("utm_medium", "referral");
|
||||
returnUrl.searchParams.append("utm_term", `${this.props.message.utm_term}${isCard ? "-card" : ""}`);
|
||||
return returnUrl;
|
||||
}
|
||||
|
||||
onCardAction(action) {
|
||||
let actionUpdates = {};
|
||||
|
||||
if (action.type === "OPEN_URL") {
|
||||
let url = new URL(action.data.args);
|
||||
this.addUtmParams(url, true);
|
||||
|
||||
if (action.addFlowParams) {
|
||||
url.searchParams.append("flow_id", this.state.flowId);
|
||||
url.searchParams.append("flow_begin_time", this.state.flowBeginTime);
|
||||
}
|
||||
|
||||
actionUpdates = {data: {...action.data, args: url}};
|
||||
}
|
||||
|
||||
this.props.onAction({...action, ...actionUpdates});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {props} = this;
|
||||
const {bundle: cards, content} = props.message;
|
||||
const {bundle: cards, content, utm_term} = props.message;
|
||||
const innerClassName = [
|
||||
"trailhead",
|
||||
content && content.className,
|
||||
].filter(v => v).join(" ");
|
||||
return (<>
|
||||
{this.state.isModalOpen && content ? <ModalOverlayWrapper innerClassName={innerClassName} onClose={this.closeModal}>
|
||||
{this.state.isModalOpen && content ? <ModalOverlayWrapper innerClassName={innerClassName} onClose={this.closeModal} id="trailheadDialog" headerId="trailheadHeader">
|
||||
<div className="trailheadInner">
|
||||
<div className="trailheadContent">
|
||||
<h1 data-l10n-id={content.title.string_id}>{this.getStringValue(content.title)}</h1>
|
||||
<h1 data-l10n-id={content.title.string_id}
|
||||
id="trailheadHeader">{this.getStringValue(content.title)}</h1>
|
||||
{content.subtitle &&
|
||||
<p data-l10n-id={content.subtitle.string_id}>{this.getStringValue(content.subtitle)}</p>
|
||||
}
|
||||
|
@ -154,7 +223,7 @@ export class _Trailhead extends React.PureComponent {
|
|||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<a className="trailheadLearn" data-l10n-id={content.learn.text.string_id} href={content.learn.url}>
|
||||
<a className="trailheadLearn" data-l10n-id={content.learn.text.string_id} href={this.addUtmParams(content.learn.url)}>
|
||||
{this.getStringValue(content.learn.text)}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -168,9 +237,10 @@ export class _Trailhead extends React.PureComponent {
|
|||
<input name="entrypoint" type="hidden" value="activity-stream-firstrun" />
|
||||
<input name="utm_source" type="hidden" value="activity-stream" />
|
||||
<input name="utm_campaign" type="hidden" value="firstrun" />
|
||||
<input name="utm_term" type="hidden" value="trailhead" />
|
||||
<input name="utm_term" type="hidden" value={utm_term} />
|
||||
<input name="flow_id" type="hidden" value={this.state.flowId} />
|
||||
<input name="flow_begin_time" type="hidden" value={this.state.flowBeginTime} />
|
||||
<input name="style" type="hidden" value="trailhead" />
|
||||
<p data-l10n-id="onboarding-join-form-email-error" className="error" />
|
||||
<input
|
||||
data-l10n-id={content.form.email.string_id}
|
||||
|
@ -182,9 +252,9 @@ export class _Trailhead extends React.PureComponent {
|
|||
onChange={this.onInputChange} />
|
||||
<p className="trailheadTerms" data-l10n-id="onboarding-join-form-legal">
|
||||
<a data-l10n-name="terms"
|
||||
href="https://accounts.firefox.com/legal/terms" />
|
||||
href={this.addUtmParams("https://accounts.firefox.com/legal/terms")} />
|
||||
<a data-l10n-name="privacy"
|
||||
href="https://accounts.firefox.com/legal/privacy" />
|
||||
href={this.addUtmParams("https://accounts.firefox.com/legal/privacy")} />
|
||||
</p>
|
||||
<button data-l10n-id={content.form.button.string_id} type="submit">
|
||||
{this.getStringValue(content.form.button)}
|
||||
|
@ -195,17 +265,19 @@ export class _Trailhead extends React.PureComponent {
|
|||
|
||||
<button className="trailheadStart"
|
||||
data-l10n-id={content.skipButton.string_id}
|
||||
onBlur={this.onStartBlur}
|
||||
onClick={this.closeModal}>{this.getStringValue(content.skipButton)}</button>
|
||||
</ModalOverlayWrapper> : null}
|
||||
{(cards && cards.length) ? <div className={`trailheadCards ${this.state.showCardPanel ? "expanded" : "collapsed"}`}>
|
||||
<div className="trailheadCardsInner">
|
||||
<div className="trailheadCardsInner"
|
||||
aria-hidden={!this.state.showCards}>
|
||||
<h1 data-l10n-id="onboarding-welcome-header" />
|
||||
<div className={`trailheadCardGrid${this.state.showCards ? " show" : ""}`}>
|
||||
{cards.map(card => (
|
||||
<OnboardingCard key={card.id}
|
||||
className="trailheadCard"
|
||||
sendUserActionTelemetry={props.sendUserActionTelemetry}
|
||||
onAction={props.onAction}
|
||||
onAction={this.onCardAction}
|
||||
UISurface="TRAILHEAD"
|
||||
{...card} />
|
||||
))}
|
||||
|
|
|
@ -10,11 +10,6 @@
|
|||
height: auto;
|
||||
top: 100px;
|
||||
|
||||
@media (max-height: 700px) {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $white;
|
||||
text-decoration: underline;
|
||||
|
@ -183,18 +178,38 @@
|
|||
|
||||
button,
|
||||
input {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: $white;
|
||||
border: 1px solid $grey-50;
|
||||
box-shadow: none;
|
||||
color: $grey-70;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms;
|
||||
|
||||
&:hover {
|
||||
border-color: $grey-90;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $blue-50;
|
||||
box-shadow: 0 0 0 3px $email-input-focus;
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
border-color: $red-60;
|
||||
}
|
||||
|
||||
&.invalid:focus {
|
||||
box-shadow: 0 0 0 3px $email-input-invalid;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $blue-60;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
|
@ -401,9 +416,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
// If the window is too short, we need to allow scrolling so user can get to Start Browsing button.
|
||||
@media (max-height: 700px) {
|
||||
// If the window is too short or narrow, we need to allow scrolling so user can get to Start Browsing button.
|
||||
@media (max-height: 760px), (max-width: 924px) {
|
||||
.activity-stream.welcome.inline-onboarding {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.trailhead {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,14 +170,11 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
|||
render() {
|
||||
// Select layout render data by adding spocs and position to recommendations
|
||||
const {layoutRender, spocsFill} = selectLayoutRender(this.props.DiscoveryStream, this.props.Prefs.values, rickRollCache);
|
||||
const {config, feeds, spocs} = this.props.DiscoveryStream;
|
||||
if (!spocs.loaded || !feeds.loaded) {
|
||||
return null;
|
||||
}
|
||||
const {config, spocs, feeds} = this.props.DiscoveryStream;
|
||||
|
||||
// Send SPOCS Fill if any. Note that it should not send it again if the same
|
||||
// page gets re-rendered by state changes.
|
||||
if (spocsFill.length && !this._spocsFillSent) {
|
||||
if (spocs.loaded && feeds.loaded && spocsFill.length && !this._spocsFillSent) {
|
||||
this.props.dispatch(ac.DiscoveryStreamSpocsFill({spoc_fills: spocsFill}));
|
||||
this._spocsFillSent = true;
|
||||
}
|
||||
|
@ -208,6 +205,10 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
|||
// Get "topstories" Section state for default values
|
||||
const topStories = this.props.Sections.find(s => s.id === "topstories");
|
||||
|
||||
if (!topStories) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract TopSites to render before the rest and Message to use for header
|
||||
const topSites = extractComponent("TopSites");
|
||||
const message = extractComponent("Message") || {
|
||||
|
@ -255,6 +256,9 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
|||
<div key={`row-${rowIndex}`} className={`ds-column ds-column-${row.width}`}>
|
||||
<div className="ds-column-grid">
|
||||
{row.components.map((component, componentIndex) => {
|
||||
if (!component) {
|
||||
return null;
|
||||
}
|
||||
styles[rowIndex] = [...styles[rowIndex] || [], component.styles];
|
||||
return (<div key={`component-${componentIndex}`}>
|
||||
{this.renderComponent(component, row.width)}
|
||||
|
|
|
@ -49,7 +49,7 @@ $col4-header-font-size: 14;
|
|||
background: none;
|
||||
|
||||
.meta {
|
||||
padding: 16px 0;
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ $col4-header-font-size: 14;
|
|||
}
|
||||
|
||||
&.ds-card-grid-divisible-by-4 .title {
|
||||
@include limit-visibile-lines(3, 20, 14);
|
||||
@include limit-visibile-lines(3, 20, 15);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,10 +72,11 @@ $excerpt-line-height: 20;
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding: 16px;
|
||||
padding: 12px;
|
||||
|
||||
.info-wrap {
|
||||
flex-grow: 1;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -92,11 +93,11 @@ $excerpt-line-height: 20;
|
|||
.context,
|
||||
.source {
|
||||
@include dark-theme-only {
|
||||
color: $teal-10;
|
||||
color: $grey-40;
|
||||
}
|
||||
|
||||
font-size: 13px;
|
||||
color: $teal-80;
|
||||
color: $grey-50;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,12 +113,12 @@ $excerpt-line-height: 20;
|
|||
|
||||
p {
|
||||
@include dark-theme-only {
|
||||
color: $grey-30;
|
||||
color: $grey-10;
|
||||
}
|
||||
|
||||
font-size: $excerpt-font-size * 1px;
|
||||
line-height: $excerpt-line-height * 1px;
|
||||
color: $grey-50;
|
||||
margin: 8px 0 0;
|
||||
color: $grey-90;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,12 @@ $card-header-in-hero-line-height: 20;
|
|||
|
||||
.excerpt {
|
||||
@include limit-visibile-lines(3, 20, 14);
|
||||
margin: 4px 0 8px;
|
||||
@include dark-theme-only {
|
||||
color: $grey-10;
|
||||
}
|
||||
|
||||
color: $grey-90;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.ds-card:not(.placeholder) {
|
||||
|
@ -119,7 +124,7 @@ $card-header-in-hero-line-height: 20;
|
|||
|
||||
@include limit-visibile-lines(4, 28, 22);
|
||||
color: $grey-90;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.context {
|
||||
|
@ -132,11 +137,11 @@ $card-header-in-hero-line-height: 20;
|
|||
|
||||
.source {
|
||||
@include dark-theme-only {
|
||||
color: $teal-10;
|
||||
color: $grey-40;
|
||||
}
|
||||
|
||||
font-size: 13px;
|
||||
color: $teal-80;
|
||||
color: $grey-50;
|
||||
margin-bottom: 0;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -212,7 +217,7 @@ $card-header-in-hero-line-height: 20;
|
|||
}
|
||||
|
||||
.img {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
height: 0;
|
||||
padding-top: 50%; // 2:1 aspect ratio
|
||||
}
|
||||
|
|
|
@ -220,10 +220,10 @@ $item-line-height: 20;
|
|||
.ds-list-item-context {
|
||||
@include limit-visibile-lines(1, $item-line-height, $item-font-size);
|
||||
@include dark-theme-only {
|
||||
color: $teal-10;
|
||||
color: $grey-40;
|
||||
}
|
||||
|
||||
color: $teal-80;
|
||||
color: $grey-50;
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
|
|
@ -6,44 +6,49 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
|
|||
let chosenSpocs = new Set();
|
||||
let unchosenSpocs = new Set();
|
||||
|
||||
// rickRollCache stores random probability values for each spoc position. This cache is empty
|
||||
// on page refresh and gets filled with random values on first render inside maybeInjectSpocs.
|
||||
const isFirstRun = !rickRollCache.length;
|
||||
|
||||
function maybeInjectSpocs(data, spocsConfig) {
|
||||
if (data &&
|
||||
spocsConfig && spocsConfig.positions && spocsConfig.positions.length &&
|
||||
spocs.data.spocs && spocs.data.spocs.length) {
|
||||
const recommendations = [...data.recommendations];
|
||||
for (let position of spocsConfig.positions) {
|
||||
const spoc = spocs.data.spocs[spocIndex];
|
||||
if (!spoc) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Cache random number for a position
|
||||
let rickRoll;
|
||||
if (isFirstRun) {
|
||||
rickRoll = Math.random();
|
||||
rickRollCache.push(rickRoll);
|
||||
} else {
|
||||
rickRoll = rickRollCache.shift();
|
||||
bufferRollCache.push(rickRoll);
|
||||
}
|
||||
|
||||
if (rickRoll <= spocsConfig.probability) {
|
||||
spocIndex++;
|
||||
recommendations.splice(position.index, 0, spoc);
|
||||
chosenSpocs.add(spoc);
|
||||
} else {
|
||||
unchosenSpocs.add(spoc);
|
||||
}
|
||||
function rollForSpocs(data, spocsConfig) {
|
||||
const recommendations = [...data.recommendations];
|
||||
for (let position of spocsConfig.positions) {
|
||||
const spoc = spocs.data.spocs[spocIndex];
|
||||
if (!spoc) {
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
...data,
|
||||
recommendations,
|
||||
};
|
||||
// Cache random number for a position
|
||||
let rickRoll;
|
||||
if (!rickRollCache.length) {
|
||||
rickRoll = Math.random();
|
||||
bufferRollCache.push(rickRoll);
|
||||
} else {
|
||||
rickRoll = rickRollCache.shift();
|
||||
bufferRollCache.push(rickRoll);
|
||||
}
|
||||
|
||||
if (rickRoll <= spocsConfig.probability) {
|
||||
spocIndex++;
|
||||
recommendations.splice(position.index, 0, spoc);
|
||||
chosenSpocs.add(spoc);
|
||||
} else {
|
||||
unchosenSpocs.add(spoc);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...data,
|
||||
recommendations,
|
||||
};
|
||||
}
|
||||
|
||||
function maybeInjectSpocs(data, spocsConfig) {
|
||||
// Do we ever expect to possibly have a spoc.
|
||||
if (data && spocsConfig && spocsConfig.positions && spocsConfig.positions.length) {
|
||||
// We expect a spoc, spocs are loaded, but the server returned no spocs.
|
||||
if (!spocs.data.spocs || !spocs.data.spocs.length) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// We expect a spoc, spocs are loaded, and we have spocs available.
|
||||
return rollForSpocs(data, spocsConfig);
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -63,56 +68,73 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
|
|||
filterArray.push(...DS_COMPONENTS);
|
||||
}
|
||||
|
||||
const layoutRender = layout.map(row => ({
|
||||
...row,
|
||||
const handleComponent = component => {
|
||||
positions[component.type] = positions[component.type] || 0;
|
||||
|
||||
// Loops through desired components and adds a .data property
|
||||
// containing data from feeds
|
||||
components: row.components.filter(c => !filterArray.includes(c.type)).map(component => {
|
||||
if (!component.feed || !feeds.data[component.feed.url]) {
|
||||
return component;
|
||||
let {data} = feeds.data[component.feed.url];
|
||||
|
||||
if (component && component.properties && component.properties.offset) {
|
||||
data = {
|
||||
...data,
|
||||
recommendations: data.recommendations.slice(component.properties.offset),
|
||||
};
|
||||
}
|
||||
|
||||
data = maybeInjectSpocs(data, component.spocs);
|
||||
|
||||
let items = 0;
|
||||
if (component.properties && component.properties.items) {
|
||||
items = Math.min(component.properties.items, data.recommendations.length);
|
||||
}
|
||||
|
||||
// loop through a component items
|
||||
// Store the items position sequentially for multiple components of the same type.
|
||||
// Example: A second card grid starts pos offset from the last card grid.
|
||||
for (let i = 0; i < items; i++) {
|
||||
data.recommendations[i].pos = positions[component.type]++;
|
||||
}
|
||||
|
||||
return {...component, data};
|
||||
};
|
||||
|
||||
const renderLayout = () => {
|
||||
const renderedLayoutArray = [];
|
||||
for (const row of layout.filter(r => r.components.length)) {
|
||||
let components = [];
|
||||
renderedLayoutArray.push({
|
||||
...row,
|
||||
components,
|
||||
});
|
||||
for (const component of row.components.filter(c => !filterArray.includes(c.type))) {
|
||||
if (component.feed) {
|
||||
const spocsConfig = component.spocs;
|
||||
// Are we still waiting on a feed/spocs, render what we have, and bail out early.
|
||||
if (!feeds.data[component.feed.url] ||
|
||||
(spocsConfig && spocsConfig.positions && spocsConfig.positions.length && !spocs.loaded)) {
|
||||
return renderedLayoutArray;
|
||||
}
|
||||
components.push(handleComponent(component));
|
||||
} else {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
return renderedLayoutArray;
|
||||
};
|
||||
|
||||
positions[component.type] = positions[component.type] || 0;
|
||||
const layoutRender = renderLayout(layout);
|
||||
|
||||
let {data} = feeds.data[component.feed.url];
|
||||
|
||||
if (component && component.properties && component.properties.offset) {
|
||||
data = {
|
||||
...data,
|
||||
recommendations: data.recommendations.slice(component.properties.offset),
|
||||
};
|
||||
}
|
||||
|
||||
data = maybeInjectSpocs(data, component.spocs);
|
||||
|
||||
// If empty, fill rickRollCache with random probability values from bufferRollCache
|
||||
if (!rickRollCache.length) {
|
||||
rickRollCache.push(...bufferRollCache);
|
||||
}
|
||||
|
||||
let items = 0;
|
||||
if (component.properties && component.properties.items) {
|
||||
items = Math.min(component.properties.items, data.recommendations.length);
|
||||
}
|
||||
|
||||
// loop through a component items
|
||||
// Store the items position sequentially for multiple components of the same type.
|
||||
// Example: A second card grid starts pos offset from the last card grid.
|
||||
for (let i = 0; i < items; i++) {
|
||||
data.recommendations[i].pos = positions[component.type]++;
|
||||
}
|
||||
|
||||
return {...component, data};
|
||||
}),
|
||||
})).filter(row => row.components.length);
|
||||
// If empty, fill rickRollCache with random probability values from bufferRollCache
|
||||
if (!rickRollCache.length) {
|
||||
rickRollCache.push(...bufferRollCache);
|
||||
}
|
||||
|
||||
// Generate the payload for the SPOCS Fill ping. Note that a SPOC could be rejected
|
||||
// by the `probability_selection` first, then gets chosen for the next position. For
|
||||
// all other SPOCS that never went through the probabilistic selection, its reason will
|
||||
// be "out_of_position".
|
||||
let spocsFill = [];
|
||||
if (spocs.data.spocs) {
|
||||
if (spocs.loaded && feeds.loaded && spocs.data.spocs) {
|
||||
const chosenSpocsFill = [...chosenSpocs]
|
||||
.map(spoc => ({id: spoc.id, reason: "n/a", displayed: 1, full_recalc: 0}));
|
||||
const unchosenSpocsFill = [...unchosenSpocs]
|
||||
|
|
|
@ -148,7 +148,7 @@ body {
|
|||
--trailhead-header-text-color: #{$white-60};
|
||||
--trailhead-cards-background-color: #{$grey-90-10};
|
||||
--trailhead-card-button-background-color: #{$grey-90-30};
|
||||
--trailhead-card-button-background-hover-color: #{$grey-90-40};
|
||||
--trailhead-card-button-background-active-color: #{$grey-90-50};
|
||||
--trailhead-card-button-background-hover-color: #{$grey-90-50};
|
||||
--trailhead-card-button-background-active-color: #{$grey-90-70};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,8 +120,8 @@ body {
|
|||
--trailhead-header-text-color: rgba(255, 255, 255, 0.6);
|
||||
--trailhead-cards-background-color: rgba(12, 12, 13, 0.1);
|
||||
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.3);
|
||||
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.4);
|
||||
--trailhead-card-button-background-active-color: rgba(12, 12, 13, 0.5); }
|
||||
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.5);
|
||||
--trailhead-card-button-background-active-color: rgba(12, 12, 13, 0.7); }
|
||||
|
||||
.icon {
|
||||
background-position: center center;
|
||||
|
@ -1920,7 +1920,7 @@ main {
|
|||
.ds-card-grid.ds-card-grid-no-border .ds-card {
|
||||
background: none; }
|
||||
.ds-card-grid.ds-card-grid-no-border .ds-card .meta {
|
||||
padding: 16px 0; }
|
||||
padding: 12px 0; }
|
||||
.ds-column-5 .ds-card-grid,
|
||||
.ds-column-6 .ds-card-grid,
|
||||
.ds-column-7 .ds-card-grid,
|
||||
|
@ -1946,9 +1946,9 @@ main {
|
|||
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
|
||||
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
|
||||
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
max-height: 4.28571em;
|
||||
max-height: 4em;
|
||||
overflow: hidden; }
|
||||
.ds-card-grid.empty {
|
||||
grid-template-columns: auto; }
|
||||
|
@ -1965,7 +1965,10 @@ main {
|
|||
line-height: 20px;
|
||||
max-height: 4.28571em;
|
||||
overflow: hidden;
|
||||
margin: 4px 0 8px; }
|
||||
color: #0C0C0D;
|
||||
margin: 0 0 10px; }
|
||||
[lwt-newtab-brighttext] .ds-hero .excerpt {
|
||||
color: #F9F9FA; }
|
||||
.ds-hero .ds-card:not(.placeholder) {
|
||||
border: 0;
|
||||
padding-bottom: 20px; }
|
||||
|
@ -2032,7 +2035,7 @@ main {
|
|||
max-height: 5.09091em;
|
||||
overflow: hidden;
|
||||
color: #0C0C0D;
|
||||
margin-bottom: 8px; }
|
||||
margin-bottom: 0; }
|
||||
[lwt-newtab-brighttext] .ds-hero .wrapper .meta header {
|
||||
color: #FFF; }
|
||||
.ds-hero .wrapper .meta .context {
|
||||
|
@ -2041,12 +2044,12 @@ main {
|
|||
color: #A7FFFE; }
|
||||
.ds-hero .wrapper .meta .source {
|
||||
font-size: 13px;
|
||||
color: #005A71;
|
||||
color: #737373;
|
||||
margin-bottom: 0;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
[lwt-newtab-brighttext] .ds-hero .wrapper .meta .source {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-column-5 .ds-hero .wrapper,
|
||||
.ds-column-6 .ds-hero .wrapper,
|
||||
.ds-column-7 .ds-hero .wrapper,
|
||||
|
@ -2130,7 +2133,7 @@ main {
|
|||
.ds-column-10 .ds-hero .wrapper .img,
|
||||
.ds-column-11 .ds-hero .wrapper .img,
|
||||
.ds-column-12 .ds-hero .wrapper .img {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
height: 0;
|
||||
padding-top: 50%; }
|
||||
.ds-column-9 .ds-hero .wrapper .meta,
|
||||
|
@ -2366,12 +2369,12 @@ main {
|
|||
line-height: 20px;
|
||||
max-height: 1.42857em;
|
||||
overflow: hidden;
|
||||
color: #005A71;
|
||||
color: #737373;
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis; }
|
||||
[lwt-newtab-brighttext] .ds-list-item .ds-list-item-info, [lwt-newtab-brighttext]
|
||||
.ds-list-item .ds-list-item-context {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-list-item .ds-list-item-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px; }
|
||||
|
@ -2625,9 +2628,10 @@ main {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding: 16px; }
|
||||
padding: 12px; }
|
||||
.ds-card .meta .info-wrap {
|
||||
flex-grow: 1; }
|
||||
flex-grow: 1;
|
||||
margin: 0 0 12px; }
|
||||
.ds-card .meta .title {
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
|
@ -2642,10 +2646,10 @@ main {
|
|||
.ds-card .meta .context,
|
||||
.ds-card .meta .source {
|
||||
font-size: 13px;
|
||||
color: #005A71; }
|
||||
color: #737373; }
|
||||
[lwt-newtab-brighttext] .ds-card .meta .context, [lwt-newtab-brighttext]
|
||||
.ds-card .meta .source {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-card header {
|
||||
line-height: 24px;
|
||||
font-size: 17px;
|
||||
|
@ -2655,10 +2659,10 @@ main {
|
|||
.ds-card p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #737373;
|
||||
margin: 8px 0 0; }
|
||||
color: #0C0C0D;
|
||||
margin: 0; }
|
||||
[lwt-newtab-brighttext] .ds-card p {
|
||||
color: #D7D7DB; }
|
||||
color: #F9F9FA; }
|
||||
|
||||
.ds-image {
|
||||
display: block;
|
||||
|
@ -3709,10 +3713,6 @@ a.firstrun-link {
|
|||
color: #FFF;
|
||||
height: auto;
|
||||
top: 100px; }
|
||||
@media (max-height: 700px) {
|
||||
.trailhead {
|
||||
position: absolute;
|
||||
top: 20px; } }
|
||||
.trailhead a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
|
@ -3820,14 +3820,26 @@ a.firstrun-link {
|
|||
z-index: 0; }
|
||||
.trailhead .trailheadForm button,
|
||||
.trailhead .trailheadForm input {
|
||||
border: 0;
|
||||
width: 100%; }
|
||||
.trailhead .trailheadForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px; }
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.trailhead .trailheadForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.trailhead .trailheadForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.trailhead .trailheadForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.trailhead .trailheadForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.trailhead .trailheadForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
|
@ -3966,6 +3978,9 @@ a.firstrun-link {
|
|||
.inline-onboarding .asrouter-toggle {
|
||||
position: absolute; }
|
||||
|
||||
@media (max-height: 700px) {
|
||||
@media (max-height: 760px), (max-width: 924px) {
|
||||
.activity-stream.welcome.inline-onboarding {
|
||||
overflow: auto; } }
|
||||
overflow: auto; }
|
||||
.trailhead {
|
||||
position: absolute;
|
||||
top: 20px; } }
|
||||
|
|
|
@ -123,8 +123,8 @@ body {
|
|||
--trailhead-header-text-color: rgba(255, 255, 255, 0.6);
|
||||
--trailhead-cards-background-color: rgba(12, 12, 13, 0.1);
|
||||
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.3);
|
||||
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.4);
|
||||
--trailhead-card-button-background-active-color: rgba(12, 12, 13, 0.5); }
|
||||
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.5);
|
||||
--trailhead-card-button-background-active-color: rgba(12, 12, 13, 0.7); }
|
||||
|
||||
.icon {
|
||||
background-position: center center;
|
||||
|
@ -1923,7 +1923,7 @@ main {
|
|||
.ds-card-grid.ds-card-grid-no-border .ds-card {
|
||||
background: none; }
|
||||
.ds-card-grid.ds-card-grid-no-border .ds-card .meta {
|
||||
padding: 16px 0; }
|
||||
padding: 12px 0; }
|
||||
.ds-column-5 .ds-card-grid,
|
||||
.ds-column-6 .ds-card-grid,
|
||||
.ds-column-7 .ds-card-grid,
|
||||
|
@ -1949,9 +1949,9 @@ main {
|
|||
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
|
||||
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
|
||||
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
max-height: 4.28571em;
|
||||
max-height: 4em;
|
||||
overflow: hidden; }
|
||||
.ds-card-grid.empty {
|
||||
grid-template-columns: auto; }
|
||||
|
@ -1968,7 +1968,10 @@ main {
|
|||
line-height: 20px;
|
||||
max-height: 4.28571em;
|
||||
overflow: hidden;
|
||||
margin: 4px 0 8px; }
|
||||
color: #0C0C0D;
|
||||
margin: 0 0 10px; }
|
||||
[lwt-newtab-brighttext] .ds-hero .excerpt {
|
||||
color: #F9F9FA; }
|
||||
.ds-hero .ds-card:not(.placeholder) {
|
||||
border: 0;
|
||||
padding-bottom: 20px; }
|
||||
|
@ -2035,7 +2038,7 @@ main {
|
|||
max-height: 5.09091em;
|
||||
overflow: hidden;
|
||||
color: #0C0C0D;
|
||||
margin-bottom: 8px; }
|
||||
margin-bottom: 0; }
|
||||
[lwt-newtab-brighttext] .ds-hero .wrapper .meta header {
|
||||
color: #FFF; }
|
||||
.ds-hero .wrapper .meta .context {
|
||||
|
@ -2044,12 +2047,12 @@ main {
|
|||
color: #A7FFFE; }
|
||||
.ds-hero .wrapper .meta .source {
|
||||
font-size: 13px;
|
||||
color: #005A71;
|
||||
color: #737373;
|
||||
margin-bottom: 0;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
[lwt-newtab-brighttext] .ds-hero .wrapper .meta .source {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-column-5 .ds-hero .wrapper,
|
||||
.ds-column-6 .ds-hero .wrapper,
|
||||
.ds-column-7 .ds-hero .wrapper,
|
||||
|
@ -2133,7 +2136,7 @@ main {
|
|||
.ds-column-10 .ds-hero .wrapper .img,
|
||||
.ds-column-11 .ds-hero .wrapper .img,
|
||||
.ds-column-12 .ds-hero .wrapper .img {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
height: 0;
|
||||
padding-top: 50%; }
|
||||
.ds-column-9 .ds-hero .wrapper .meta,
|
||||
|
@ -2369,12 +2372,12 @@ main {
|
|||
line-height: 20px;
|
||||
max-height: 1.42857em;
|
||||
overflow: hidden;
|
||||
color: #005A71;
|
||||
color: #737373;
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis; }
|
||||
[lwt-newtab-brighttext] .ds-list-item .ds-list-item-info, [lwt-newtab-brighttext]
|
||||
.ds-list-item .ds-list-item-context {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-list-item .ds-list-item-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px; }
|
||||
|
@ -2628,9 +2631,10 @@ main {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding: 16px; }
|
||||
padding: 12px; }
|
||||
.ds-card .meta .info-wrap {
|
||||
flex-grow: 1; }
|
||||
flex-grow: 1;
|
||||
margin: 0 0 12px; }
|
||||
.ds-card .meta .title {
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
|
@ -2645,10 +2649,10 @@ main {
|
|||
.ds-card .meta .context,
|
||||
.ds-card .meta .source {
|
||||
font-size: 13px;
|
||||
color: #005A71; }
|
||||
color: #737373; }
|
||||
[lwt-newtab-brighttext] .ds-card .meta .context, [lwt-newtab-brighttext]
|
||||
.ds-card .meta .source {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-card header {
|
||||
line-height: 24px;
|
||||
font-size: 17px;
|
||||
|
@ -2658,10 +2662,10 @@ main {
|
|||
.ds-card p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #737373;
|
||||
margin: 8px 0 0; }
|
||||
color: #0C0C0D;
|
||||
margin: 0; }
|
||||
[lwt-newtab-brighttext] .ds-card p {
|
||||
color: #D7D7DB; }
|
||||
color: #F9F9FA; }
|
||||
|
||||
.ds-image {
|
||||
display: block;
|
||||
|
@ -3712,10 +3716,6 @@ a.firstrun-link {
|
|||
color: #FFF;
|
||||
height: auto;
|
||||
top: 100px; }
|
||||
@media (max-height: 700px) {
|
||||
.trailhead {
|
||||
position: absolute;
|
||||
top: 20px; } }
|
||||
.trailhead a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
|
@ -3823,14 +3823,26 @@ a.firstrun-link {
|
|||
z-index: 0; }
|
||||
.trailhead .trailheadForm button,
|
||||
.trailhead .trailheadForm input {
|
||||
border: 0;
|
||||
width: 100%; }
|
||||
.trailhead .trailheadForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px; }
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.trailhead .trailheadForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.trailhead .trailheadForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.trailhead .trailheadForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.trailhead .trailheadForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.trailhead .trailheadForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
|
@ -3969,6 +3981,9 @@ a.firstrun-link {
|
|||
.inline-onboarding .asrouter-toggle {
|
||||
position: absolute; }
|
||||
|
||||
@media (max-height: 700px) {
|
||||
@media (max-height: 760px), (max-width: 924px) {
|
||||
.activity-stream.welcome.inline-onboarding {
|
||||
overflow: auto; } }
|
||||
overflow: auto; }
|
||||
.trailhead {
|
||||
position: absolute;
|
||||
top: 20px; } }
|
||||
|
|
|
@ -120,8 +120,8 @@ body {
|
|||
--trailhead-header-text-color: rgba(255, 255, 255, 0.6);
|
||||
--trailhead-cards-background-color: rgba(12, 12, 13, 0.1);
|
||||
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.3);
|
||||
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.4);
|
||||
--trailhead-card-button-background-active-color: rgba(12, 12, 13, 0.5); }
|
||||
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.5);
|
||||
--trailhead-card-button-background-active-color: rgba(12, 12, 13, 0.7); }
|
||||
|
||||
.icon {
|
||||
background-position: center center;
|
||||
|
@ -1920,7 +1920,7 @@ main {
|
|||
.ds-card-grid.ds-card-grid-no-border .ds-card {
|
||||
background: none; }
|
||||
.ds-card-grid.ds-card-grid-no-border .ds-card .meta {
|
||||
padding: 16px 0; }
|
||||
padding: 12px 0; }
|
||||
.ds-column-5 .ds-card-grid,
|
||||
.ds-column-6 .ds-card-grid,
|
||||
.ds-column-7 .ds-card-grid,
|
||||
|
@ -1946,9 +1946,9 @@ main {
|
|||
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
|
||||
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
|
||||
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
max-height: 4.28571em;
|
||||
max-height: 4em;
|
||||
overflow: hidden; }
|
||||
.ds-card-grid.empty {
|
||||
grid-template-columns: auto; }
|
||||
|
@ -1965,7 +1965,10 @@ main {
|
|||
line-height: 20px;
|
||||
max-height: 4.28571em;
|
||||
overflow: hidden;
|
||||
margin: 4px 0 8px; }
|
||||
color: #0C0C0D;
|
||||
margin: 0 0 10px; }
|
||||
[lwt-newtab-brighttext] .ds-hero .excerpt {
|
||||
color: #F9F9FA; }
|
||||
.ds-hero .ds-card:not(.placeholder) {
|
||||
border: 0;
|
||||
padding-bottom: 20px; }
|
||||
|
@ -2032,7 +2035,7 @@ main {
|
|||
max-height: 5.09091em;
|
||||
overflow: hidden;
|
||||
color: #0C0C0D;
|
||||
margin-bottom: 8px; }
|
||||
margin-bottom: 0; }
|
||||
[lwt-newtab-brighttext] .ds-hero .wrapper .meta header {
|
||||
color: #FFF; }
|
||||
.ds-hero .wrapper .meta .context {
|
||||
|
@ -2041,12 +2044,12 @@ main {
|
|||
color: #A7FFFE; }
|
||||
.ds-hero .wrapper .meta .source {
|
||||
font-size: 13px;
|
||||
color: #005A71;
|
||||
color: #737373;
|
||||
margin-bottom: 0;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
[lwt-newtab-brighttext] .ds-hero .wrapper .meta .source {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-column-5 .ds-hero .wrapper,
|
||||
.ds-column-6 .ds-hero .wrapper,
|
||||
.ds-column-7 .ds-hero .wrapper,
|
||||
|
@ -2130,7 +2133,7 @@ main {
|
|||
.ds-column-10 .ds-hero .wrapper .img,
|
||||
.ds-column-11 .ds-hero .wrapper .img,
|
||||
.ds-column-12 .ds-hero .wrapper .img {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
height: 0;
|
||||
padding-top: 50%; }
|
||||
.ds-column-9 .ds-hero .wrapper .meta,
|
||||
|
@ -2366,12 +2369,12 @@ main {
|
|||
line-height: 20px;
|
||||
max-height: 1.42857em;
|
||||
overflow: hidden;
|
||||
color: #005A71;
|
||||
color: #737373;
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis; }
|
||||
[lwt-newtab-brighttext] .ds-list-item .ds-list-item-info, [lwt-newtab-brighttext]
|
||||
.ds-list-item .ds-list-item-context {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-list-item .ds-list-item-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px; }
|
||||
|
@ -2625,9 +2628,10 @@ main {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding: 16px; }
|
||||
padding: 12px; }
|
||||
.ds-card .meta .info-wrap {
|
||||
flex-grow: 1; }
|
||||
flex-grow: 1;
|
||||
margin: 0 0 12px; }
|
||||
.ds-card .meta .title {
|
||||
font-size: 17px;
|
||||
line-height: 24px;
|
||||
|
@ -2642,10 +2646,10 @@ main {
|
|||
.ds-card .meta .context,
|
||||
.ds-card .meta .source {
|
||||
font-size: 13px;
|
||||
color: #005A71; }
|
||||
color: #737373; }
|
||||
[lwt-newtab-brighttext] .ds-card .meta .context, [lwt-newtab-brighttext]
|
||||
.ds-card .meta .source {
|
||||
color: #A7FFFE; }
|
||||
color: #B1B1B3; }
|
||||
.ds-card header {
|
||||
line-height: 24px;
|
||||
font-size: 17px;
|
||||
|
@ -2655,10 +2659,10 @@ main {
|
|||
.ds-card p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #737373;
|
||||
margin: 8px 0 0; }
|
||||
color: #0C0C0D;
|
||||
margin: 0; }
|
||||
[lwt-newtab-brighttext] .ds-card p {
|
||||
color: #D7D7DB; }
|
||||
color: #F9F9FA; }
|
||||
|
||||
.ds-image {
|
||||
display: block;
|
||||
|
@ -3709,10 +3713,6 @@ a.firstrun-link {
|
|||
color: #FFF;
|
||||
height: auto;
|
||||
top: 100px; }
|
||||
@media (max-height: 700px) {
|
||||
.trailhead {
|
||||
position: absolute;
|
||||
top: 20px; } }
|
||||
.trailhead a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
|
@ -3820,14 +3820,26 @@ a.firstrun-link {
|
|||
z-index: 0; }
|
||||
.trailhead .trailheadForm button,
|
||||
.trailhead .trailheadForm input {
|
||||
border: 0;
|
||||
width: 100%; }
|
||||
.trailhead .trailheadForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px; }
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.trailhead .trailheadForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.trailhead .trailheadForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.trailhead .trailheadForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.trailhead .trailheadForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.trailhead .trailheadForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
|
@ -3966,6 +3978,9 @@ a.firstrun-link {
|
|||
.inline-onboarding .asrouter-toggle {
|
||||
position: absolute; }
|
||||
|
||||
@media (max-height: 700px) {
|
||||
@media (max-height: 760px), (max-width: 924px) {
|
||||
.activity-stream.welcome.inline-onboarding {
|
||||
overflow: auto; } }
|
||||
overflow: auto; }
|
||||
.trailhead {
|
||||
position: absolute;
|
||||
top: 20px; } }
|
||||
|
|
|
@ -197,7 +197,7 @@ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS :
|
|||
// }
|
||||
const actionTypes = {};
|
||||
|
||||
for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_SETUP", "DISCOVERY_STREAM_CONFIG_SET_VALUE", "DISCOVERY_STREAM_FEEDS_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_OPT_OUT", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_SEARCH", "INIT", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "POCKET_CTA", "POCKET_LINK_DELETED_OR_ARCHIVED", "POCKET_LOGGED_IN", "POCKET_WAITING_FOR_SPOC", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
|
||||
for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_SETUP", "DISCOVERY_STREAM_CONFIG_SET_VALUE", "DISCOVERY_STREAM_FEEDS_UPDATE", "DISCOVERY_STREAM_FEED_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_OPT_OUT", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_SEARCH", "INIT", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "POCKET_CTA", "POCKET_LINK_DELETED_OR_ARCHIVED", "POCKET_LOGGED_IN", "POCKET_WAITING_FOR_SPOC", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
|
||||
actionTypes[type] = type;
|
||||
} // These are acceptable actions for AS Router messages to have. They can show up
|
||||
// as call-to-action buttons in snippets, onboarding tour, etc.
|
||||
|
@ -2201,6 +2201,7 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_8___default.a.Pur
|
|||
|
||||
if (message.template === "trailhead") {
|
||||
return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(_templates_Trailhead_Trailhead__WEBPACK_IMPORTED_MODULE_13__["Trailhead"], {
|
||||
document: this.props.document,
|
||||
message: message,
|
||||
onAction: ASRouterUtils.executeAction,
|
||||
onDoneButton: this.dismissBundle(this.state.bundle.bundle),
|
||||
|
@ -2729,7 +2730,10 @@ class ModalOverlayWrapper extends react__WEBPACK_IMPORTED_MODULE_0___default.a.P
|
|||
onClick: props.onClose,
|
||||
role: "presentation"
|
||||
}), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
||||
className: `modalOverlayInner active ${props.innerClassName || ""}`
|
||||
className: `modalOverlayInner active ${props.innerClassName || ""}`,
|
||||
"aria-labelledby": props.headerId,
|
||||
id: props.id,
|
||||
role: "dialog"
|
||||
}, props.children));
|
||||
}
|
||||
|
||||
|
@ -2988,6 +2992,7 @@ class _StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureC
|
|||
this.initScene = this.initScene.bind(this);
|
||||
this.removeOverlay = this.removeOverlay.bind(this);
|
||||
this.onInputInvalid = this.onInputInvalid.bind(this);
|
||||
this.utmParams = "utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-control";
|
||||
this.state = {
|
||||
emailInput: "",
|
||||
overlayRemoved: false,
|
||||
|
@ -3001,8 +3006,8 @@ class _StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureC
|
|||
if (this.props.fxa_endpoint && !this.didFetch) {
|
||||
try {
|
||||
this.didFetch = true;
|
||||
const fxaParams = "entrypoint=activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun&form_type=email";
|
||||
const response = await fetch(`${this.props.fxa_endpoint}/metrics-flow?${fxaParams}`, {
|
||||
const fxaParams = "entrypoint=activity-stream-firstrun&form_type=email";
|
||||
const response = await fetch(`${this.props.fxa_endpoint}/metrics-flow?${fxaParams}&${this.utmParams}`, {
|
||||
credentials: "omit"
|
||||
});
|
||||
|
||||
|
@ -3121,14 +3126,14 @@ class _StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureC
|
|||
}
|
||||
|
||||
let termsLink = react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
|
||||
href: `${this.props.fxa_endpoint}/legal/terms`,
|
||||
href: `${this.props.fxa_endpoint}/legal/terms?${this.utmParams}`,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__["FormattedMessage"], {
|
||||
id: "firstrun_terms_of_service"
|
||||
}));
|
||||
let privacyLink = react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
|
||||
href: `${this.props.fxa_endpoint}/legal/privacy`,
|
||||
href: `${this.props.fxa_endpoint}/legal/privacy?${this.utmParams}`,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__["FormattedMessage"], {
|
||||
|
@ -3154,7 +3159,7 @@ class _StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureC
|
|||
id: "firstrun_content"
|
||||
})), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
|
||||
className: "firstrun-link",
|
||||
href: "https://www.mozilla.org/firefox/features/sync/",
|
||||
href: `https://www.mozilla.org/firefox/features/sync/?${this.utmParams}`,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__["FormattedMessage"], {
|
||||
|
@ -3199,6 +3204,14 @@ class _StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureC
|
|||
name: "utm_campaign",
|
||||
type: "hidden",
|
||||
value: "firstrun"
|
||||
}), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
|
||||
name: "utm_medium",
|
||||
type: "hidden",
|
||||
value: "referral"
|
||||
}), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
|
||||
name: "utm_term",
|
||||
type: "hidden",
|
||||
value: "trailhead-control"
|
||||
}), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
|
||||
name: "flow_id",
|
||||
type: "hidden",
|
||||
|
@ -3279,18 +3292,19 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
|
|||
|
||||
|
||||
|
||||
const FLUENT_FILES = ["branding/brand.ftl", "browser/branding/sync-brand.ftl", // These are finalized strings exposed to localizers
|
||||
"browser/newtab/onboarding.ftl", // These are WIP/in-development strings that only get used if the string
|
||||
// doesn't already exist in onboarding.ftl above
|
||||
"trailhead.ftl"];
|
||||
const FLUENT_FILES = ["branding/brand.ftl", "browser/branding/brandings.ftl", "browser/branding/sync-brand.ftl", "browser/newtab/onboarding.ftl"]; // From resource://devtools/client/shared/focus.js
|
||||
|
||||
const FOCUSABLE_SELECTOR = ["a[href]:not([tabindex='-1'])", "button:not([disabled]):not([tabindex='-1'])", "iframe:not([tabindex='-1'])", "input:not([disabled]):not([tabindex='-1'])", "select:not([disabled]):not([tabindex='-1'])", "textarea:not([disabled]):not([tabindex='-1'])", "[tabindex]:not([tabindex='-1'])"].join(", ");
|
||||
class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.closeModal = this.closeModal.bind(this);
|
||||
this.hideCardPanel = this.hideCardPanel.bind(this);
|
||||
this.onInputChange = this.onInputChange.bind(this);
|
||||
this.onStartBlur = this.onStartBlur.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onInputInvalid = this.onInputInvalid.bind(this);
|
||||
this.onCardAction = this.onCardAction.bind(this);
|
||||
this.state = {
|
||||
emailInput: "",
|
||||
isModalOpen: true,
|
||||
|
@ -3302,6 +3316,10 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
this.didFetch = false;
|
||||
}
|
||||
|
||||
get dialog() {
|
||||
return this.props.document.getElementById("trailheadDialog");
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
FLUENT_FILES.forEach(file => {
|
||||
const link = document.head.appendChild(document.createElement("link"));
|
||||
|
@ -3312,8 +3330,9 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
if (this.props.fxaEndpoint && !this.didFetch) {
|
||||
try {
|
||||
this.didFetch = true;
|
||||
const fxaParams = "entrypoint=activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun&utm_term=trailhead&form_type=email";
|
||||
const response = await fetch(`${this.props.fxaEndpoint}/metrics-flow?${fxaParams}`, {
|
||||
const url = new URL(`${this.props.fxaEndpoint}/metrics-flow?entrypoint=activity-stream-firstrun&form_type=email`);
|
||||
this.addUtmParams(url);
|
||||
const response = await fetch(url, {
|
||||
credentials: "omit"
|
||||
});
|
||||
|
||||
|
@ -3348,13 +3367,17 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
|
||||
componentDidMount() {
|
||||
// We need to remove hide-main since we should show it underneath everything that has rendered
|
||||
global.document.body.classList.remove("hide-main"); // Add inline-onboarding class to disable fixed search header and fixed positioned settings icon
|
||||
this.props.document.body.classList.remove("hide-main"); // Add inline-onboarding class to disable fixed search header and fixed positioned settings icon
|
||||
|
||||
global.document.body.classList.add("inline-onboarding");
|
||||
this.props.document.body.classList.add("inline-onboarding"); // The rest of the page is "hidden" when the modal is open
|
||||
|
||||
if (!this.props.message.content) {
|
||||
if (this.props.message.content) {
|
||||
this.props.document.getElementById("root").setAttribute("aria-hidden", "true"); // Start with focus in the email input box
|
||||
|
||||
this.dialog.querySelector("input[name=email]").focus();
|
||||
} else {
|
||||
// No modal overlay, let the user scroll and deal them some cards.
|
||||
global.document.body.classList.remove("welcome");
|
||||
this.props.document.body.classList.remove("welcome");
|
||||
|
||||
if (this.props.message.includeBundle || this.props.message.cards) {
|
||||
this.revealCards();
|
||||
|
@ -3362,8 +3385,8 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUnmount() {
|
||||
global.document.body.classList.remove("inline-onboarding");
|
||||
componentWillUnmount() {
|
||||
this.props.document.body.classList.remove("inline-onboarding");
|
||||
}
|
||||
|
||||
onInputChange(e) {
|
||||
|
@ -3375,6 +3398,17 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
e.target.classList.remove("invalid");
|
||||
}
|
||||
|
||||
onStartBlur(event) {
|
||||
// Make sure focus stays within the dialog when tabbing from the button
|
||||
const {
|
||||
dialog
|
||||
} = this;
|
||||
|
||||
if (event.relatedTarget && !(dialog.compareDocumentPosition(event.relatedTarget) & dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
|
||||
dialog.querySelector(FOCUSABLE_SELECTOR).focus();
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
|
||||
event: "SUBMIT_EMAIL",
|
||||
|
@ -3385,7 +3419,8 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
|
||||
closeModal() {
|
||||
global.removeEventListener("visibilitychange", this.closeModal);
|
||||
global.document.body.classList.remove("welcome");
|
||||
this.props.document.body.classList.remove("welcome");
|
||||
this.props.document.getElementById("root").removeAttribute("aria-hidden");
|
||||
this.setState({
|
||||
isModalOpen: false
|
||||
});
|
||||
|
@ -3439,6 +3474,51 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
|
||||
return str.value;
|
||||
}
|
||||
/**
|
||||
* Takes in a url as a string or URL object and returns a URL object with the
|
||||
* utm_* parameters added to it. If a URL object is passed in, the paraemeters
|
||||
* are added to it (the return value can be ignored in that case as it's the
|
||||
* same object).
|
||||
*/
|
||||
|
||||
|
||||
addUtmParams(url, isCard = false) {
|
||||
let returnUrl = url;
|
||||
|
||||
if (typeof returnUrl === "string") {
|
||||
returnUrl = new URL(url);
|
||||
}
|
||||
|
||||
returnUrl.searchParams.append("utm_source", "activity-stream");
|
||||
returnUrl.searchParams.append("utm_campaign", "firstrun");
|
||||
returnUrl.searchParams.append("utm_medium", "referral");
|
||||
returnUrl.searchParams.append("utm_term", `${this.props.message.utm_term}${isCard ? "-card" : ""}`);
|
||||
return returnUrl;
|
||||
}
|
||||
|
||||
onCardAction(action) {
|
||||
let actionUpdates = {};
|
||||
|
||||
if (action.type === "OPEN_URL") {
|
||||
let url = new URL(action.data.args);
|
||||
this.addUtmParams(url, true);
|
||||
|
||||
if (action.addFlowParams) {
|
||||
url.searchParams.append("flow_id", this.state.flowId);
|
||||
url.searchParams.append("flow_begin_time", this.state.flowBeginTime);
|
||||
}
|
||||
|
||||
actionUpdates = {
|
||||
data: { ...action.data,
|
||||
args: url
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.props.onAction({ ...action,
|
||||
...actionUpdates
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@ -3446,18 +3526,22 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
} = this;
|
||||
const {
|
||||
bundle: cards,
|
||||
content
|
||||
content,
|
||||
utm_term
|
||||
} = props.message;
|
||||
const innerClassName = ["trailhead", content && content.className].filter(v => v).join(" ");
|
||||
return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_4___default.a.Fragment, null, this.state.isModalOpen && content ? react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_2__["ModalOverlayWrapper"], {
|
||||
innerClassName: innerClassName,
|
||||
onClose: this.closeModal
|
||||
onClose: this.closeModal,
|
||||
id: "trailheadDialog",
|
||||
headerId: "trailheadHeader"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
|
||||
className: "trailheadInner"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
|
||||
className: "trailheadContent"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h1", {
|
||||
"data-l10n-id": content.title.string_id
|
||||
"data-l10n-id": content.title.string_id,
|
||||
id: "trailheadHeader"
|
||||
}, this.getStringValue(content.title)), content.subtitle && react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
|
||||
"data-l10n-id": content.subtitle.string_id
|
||||
}, this.getStringValue(content.subtitle)), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("ul", {
|
||||
|
@ -3472,7 +3556,7 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
}, this.getStringValue(item.text))))), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("a", {
|
||||
className: "trailheadLearn",
|
||||
"data-l10n-id": content.learn.text.string_id,
|
||||
href: content.learn.url
|
||||
href: this.addUtmParams(content.learn.url)
|
||||
}, this.getStringValue(content.learn.text))), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
|
||||
className: "trailheadForm"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h3", {
|
||||
|
@ -3512,7 +3596,7 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
}), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
|
||||
name: "utm_term",
|
||||
type: "hidden",
|
||||
value: "trailhead"
|
||||
value: utm_term
|
||||
}), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
|
||||
name: "flow_id",
|
||||
type: "hidden",
|
||||
|
@ -3521,6 +3605,10 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
name: "flow_begin_time",
|
||||
type: "hidden",
|
||||
value: this.state.flowBeginTime
|
||||
}), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
|
||||
name: "style",
|
||||
type: "hidden",
|
||||
value: "trailhead"
|
||||
}), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
|
||||
"data-l10n-id": "onboarding-join-form-email-error",
|
||||
className: "error"
|
||||
|
@ -3537,21 +3625,23 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
"data-l10n-id": "onboarding-join-form-legal"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("a", {
|
||||
"data-l10n-name": "terms",
|
||||
href: "https://accounts.firefox.com/legal/terms"
|
||||
href: this.addUtmParams("https://accounts.firefox.com/legal/terms")
|
||||
}), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("a", {
|
||||
"data-l10n-name": "privacy",
|
||||
href: "https://accounts.firefox.com/legal/privacy"
|
||||
href: this.addUtmParams("https://accounts.firefox.com/legal/privacy")
|
||||
})), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("button", {
|
||||
"data-l10n-id": content.form.button.string_id,
|
||||
type: "submit"
|
||||
}, this.getStringValue(content.form.button))))), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("button", {
|
||||
className: "trailheadStart",
|
||||
"data-l10n-id": content.skipButton.string_id,
|
||||
onBlur: this.onStartBlur,
|
||||
onClick: this.closeModal
|
||||
}, this.getStringValue(content.skipButton))) : null, cards && cards.length ? react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
|
||||
className: `trailheadCards ${this.state.showCardPanel ? "expanded" : "collapsed"}`
|
||||
}, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
|
||||
className: "trailheadCardsInner"
|
||||
className: "trailheadCardsInner",
|
||||
"aria-hidden": !this.state.showCards
|
||||
}, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h1", {
|
||||
"data-l10n-id": "onboarding-welcome-header"
|
||||
}), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
|
||||
|
@ -3560,7 +3650,7 @@ class _Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureCompon
|
|||
key: card.id,
|
||||
className: "trailheadCard",
|
||||
sendUserActionTelemetry: props.sendUserActionTelemetry,
|
||||
onAction: props.onAction,
|
||||
onAction: this.onCardAction,
|
||||
UISurface: "TRAILHEAD"
|
||||
}, card)))), this.state.showCardPanel && react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("button", {
|
||||
className: "icon icon-dismiss",
|
||||
|
@ -8338,45 +8428,53 @@ const selectLayoutRender = (state, prefs, rickRollCache) => {
|
|||
let bufferRollCache = []; // Records the chosen and unchosen spocs by the probability selection.
|
||||
|
||||
let chosenSpocs = new Set();
|
||||
let unchosenSpocs = new Set(); // rickRollCache stores random probability values for each spoc position. This cache is empty
|
||||
// on page refresh and gets filled with random values on first render inside maybeInjectSpocs.
|
||||
let unchosenSpocs = new Set();
|
||||
|
||||
const isFirstRun = !rickRollCache.length;
|
||||
function rollForSpocs(data, spocsConfig) {
|
||||
const recommendations = [...data.recommendations];
|
||||
|
||||
function maybeInjectSpocs(data, spocsConfig) {
|
||||
if (data && spocsConfig && spocsConfig.positions && spocsConfig.positions.length && spocs.data.spocs && spocs.data.spocs.length) {
|
||||
const recommendations = [...data.recommendations];
|
||||
for (let position of spocsConfig.positions) {
|
||||
const spoc = spocs.data.spocs[spocIndex];
|
||||
|
||||
for (let position of spocsConfig.positions) {
|
||||
const spoc = spocs.data.spocs[spocIndex];
|
||||
|
||||
if (!spoc) {
|
||||
break;
|
||||
} // Cache random number for a position
|
||||
if (!spoc) {
|
||||
break;
|
||||
} // Cache random number for a position
|
||||
|
||||
|
||||
let rickRoll;
|
||||
let rickRoll;
|
||||
|
||||
if (isFirstRun) {
|
||||
rickRoll = Math.random();
|
||||
rickRollCache.push(rickRoll);
|
||||
} else {
|
||||
rickRoll = rickRollCache.shift();
|
||||
bufferRollCache.push(rickRoll);
|
||||
}
|
||||
|
||||
if (rickRoll <= spocsConfig.probability) {
|
||||
spocIndex++;
|
||||
recommendations.splice(position.index, 0, spoc);
|
||||
chosenSpocs.add(spoc);
|
||||
} else {
|
||||
unchosenSpocs.add(spoc);
|
||||
}
|
||||
if (!rickRollCache.length) {
|
||||
rickRoll = Math.random();
|
||||
bufferRollCache.push(rickRoll);
|
||||
} else {
|
||||
rickRoll = rickRollCache.shift();
|
||||
bufferRollCache.push(rickRoll);
|
||||
}
|
||||
|
||||
return { ...data,
|
||||
recommendations
|
||||
};
|
||||
if (rickRoll <= spocsConfig.probability) {
|
||||
spocIndex++;
|
||||
recommendations.splice(position.index, 0, spoc);
|
||||
chosenSpocs.add(spoc);
|
||||
} else {
|
||||
unchosenSpocs.add(spoc);
|
||||
}
|
||||
}
|
||||
|
||||
return { ...data,
|
||||
recommendations
|
||||
};
|
||||
}
|
||||
|
||||
function maybeInjectSpocs(data, spocsConfig) {
|
||||
// Do we ever expect to possibly have a spoc.
|
||||
if (data && spocsConfig && spocsConfig.positions && spocsConfig.positions.length) {
|
||||
// We expect a spoc, spocs are loaded, but the server returned no spocs.
|
||||
if (!spocs.data.spocs || !spocs.data.spocs.length) {
|
||||
return data;
|
||||
} // We expect a spoc, spocs are loaded, and we have spocs available.
|
||||
|
||||
|
||||
return rollForSpocs(data, spocsConfig);
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -8394,56 +8492,77 @@ const selectLayoutRender = (state, prefs, rickRollCache) => {
|
|||
filterArray.push(...DS_COMPONENTS);
|
||||
}
|
||||
|
||||
const layoutRender = layout.map(row => ({ ...row,
|
||||
// Loops through desired components and adds a .data property
|
||||
// containing data from feeds
|
||||
components: row.components.filter(c => !filterArray.includes(c.type)).map(component => {
|
||||
if (!component.feed || !feeds.data[component.feed.url]) {
|
||||
return component;
|
||||
}
|
||||
const handleComponent = component => {
|
||||
positions[component.type] = positions[component.type] || 0;
|
||||
let {
|
||||
data
|
||||
} = feeds.data[component.feed.url];
|
||||
|
||||
positions[component.type] = positions[component.type] || 0;
|
||||
let {
|
||||
data
|
||||
} = feeds.data[component.feed.url];
|
||||
|
||||
if (component && component.properties && component.properties.offset) {
|
||||
data = { ...data,
|
||||
recommendations: data.recommendations.slice(component.properties.offset)
|
||||
};
|
||||
}
|
||||
|
||||
data = maybeInjectSpocs(data, component.spocs); // If empty, fill rickRollCache with random probability values from bufferRollCache
|
||||
|
||||
if (!rickRollCache.length) {
|
||||
rickRollCache.push(...bufferRollCache);
|
||||
}
|
||||
|
||||
let items = 0;
|
||||
|
||||
if (component.properties && component.properties.items) {
|
||||
items = Math.min(component.properties.items, data.recommendations.length);
|
||||
} // loop through a component items
|
||||
// Store the items position sequentially for multiple components of the same type.
|
||||
// Example: A second card grid starts pos offset from the last card grid.
|
||||
|
||||
|
||||
for (let i = 0; i < items; i++) {
|
||||
data.recommendations[i].pos = positions[component.type]++;
|
||||
}
|
||||
|
||||
return { ...component,
|
||||
data
|
||||
if (component && component.properties && component.properties.offset) {
|
||||
data = { ...data,
|
||||
recommendations: data.recommendations.slice(component.properties.offset)
|
||||
};
|
||||
})
|
||||
})).filter(row => row.components.length); // Generate the payload for the SPOCS Fill ping. Note that a SPOC could be rejected
|
||||
}
|
||||
|
||||
data = maybeInjectSpocs(data, component.spocs);
|
||||
let items = 0;
|
||||
|
||||
if (component.properties && component.properties.items) {
|
||||
items = Math.min(component.properties.items, data.recommendations.length);
|
||||
} // loop through a component items
|
||||
// Store the items position sequentially for multiple components of the same type.
|
||||
// Example: A second card grid starts pos offset from the last card grid.
|
||||
|
||||
|
||||
for (let i = 0; i < items; i++) {
|
||||
data.recommendations[i].pos = positions[component.type]++;
|
||||
}
|
||||
|
||||
return { ...component,
|
||||
data
|
||||
};
|
||||
};
|
||||
|
||||
const renderLayout = () => {
|
||||
const renderedLayoutArray = [];
|
||||
|
||||
for (const row of layout.filter(r => r.components.length)) {
|
||||
let components = [];
|
||||
renderedLayoutArray.push({ ...row,
|
||||
components
|
||||
});
|
||||
|
||||
for (const component of row.components.filter(c => !filterArray.includes(c.type))) {
|
||||
if (component.feed) {
|
||||
const spocsConfig = component.spocs; // Are we still waiting on a feed/spocs, render what we have, and bail out early.
|
||||
|
||||
if (!feeds.data[component.feed.url] || spocsConfig && spocsConfig.positions && spocsConfig.positions.length && !spocs.loaded) {
|
||||
return renderedLayoutArray;
|
||||
}
|
||||
|
||||
components.push(handleComponent(component));
|
||||
} else {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return renderedLayoutArray;
|
||||
};
|
||||
|
||||
const layoutRender = renderLayout(layout); // If empty, fill rickRollCache with random probability values from bufferRollCache
|
||||
|
||||
if (!rickRollCache.length) {
|
||||
rickRollCache.push(...bufferRollCache);
|
||||
} // Generate the payload for the SPOCS Fill ping. Note that a SPOC could be rejected
|
||||
// by the `probability_selection` first, then gets chosen for the next position. For
|
||||
// all other SPOCS that never went through the probabilistic selection, its reason will
|
||||
// be "out_of_position".
|
||||
|
||||
|
||||
let spocsFill = [];
|
||||
|
||||
if (spocs.data.spocs) {
|
||||
if (spocs.loaded && feeds.loaded && spocs.data.spocs) {
|
||||
const chosenSpocsFill = [...chosenSpocs].map(spoc => ({
|
||||
id: spoc.id,
|
||||
reason: "n/a",
|
||||
|
@ -8679,17 +8798,12 @@ class DiscoveryStreamBase_DiscoveryStreamBase extends external_React_default.a.P
|
|||
} = selectLayoutRender(this.props.DiscoveryStream, this.props.Prefs.values, rickRollCache);
|
||||
const {
|
||||
config,
|
||||
feeds,
|
||||
spocs
|
||||
} = this.props.DiscoveryStream;
|
||||
|
||||
if (!spocs.loaded || !feeds.loaded) {
|
||||
return null;
|
||||
} // Send SPOCS Fill if any. Note that it should not send it again if the same
|
||||
spocs,
|
||||
feeds
|
||||
} = this.props.DiscoveryStream; // Send SPOCS Fill if any. Note that it should not send it again if the same
|
||||
// page gets re-rendered by state changes.
|
||||
|
||||
|
||||
if (spocsFill.length && !this._spocsFillSent) {
|
||||
if (spocs.loaded && feeds.loaded && spocsFill.length && !this._spocsFillSent) {
|
||||
this.props.dispatch(Actions["actionCreators"].DiscoveryStreamSpocsFill({
|
||||
spoc_fills: spocsFill
|
||||
}));
|
||||
|
@ -8722,7 +8836,12 @@ class DiscoveryStreamBase_DiscoveryStreamBase extends external_React_default.a.P
|
|||
}; // Get "topstories" Section state for default values
|
||||
|
||||
|
||||
const topStories = this.props.Sections.find(s => s.id === "topstories"); // Extract TopSites to render before the rest and Message to use for header
|
||||
const topStories = this.props.Sections.find(s => s.id === "topstories");
|
||||
|
||||
if (!topStories) {
|
||||
return null;
|
||||
} // Extract TopSites to render before the rest and Message to use for header
|
||||
|
||||
|
||||
const topSites = extractComponent("TopSites");
|
||||
const message = extractComponent("Message") || {
|
||||
|
@ -8765,6 +8884,10 @@ class DiscoveryStreamBase_DiscoveryStreamBase extends external_React_default.a.P
|
|||
}, external_React_default.a.createElement("div", {
|
||||
className: "ds-column-grid"
|
||||
}, row.components.map((component, componentIndex) => {
|
||||
if (!component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
styles[rowIndex] = [...(styles[rowIndex] || []), component.styles];
|
||||
return external_React_default.a.createElement("div", {
|
||||
key: `component-${componentIndex}`
|
||||
|
@ -11786,7 +11909,7 @@ class CachedIterable {
|
|||
|
||||
}
|
||||
// CONCATENATED MODULE: ./node_modules/fluent/src/fallback.js
|
||||
function _asyncIterator(iterable) { var method; if (typeof Symbol !== "undefined") { if (Symbol.asyncIterator) { method = iterable[Symbol.asyncIterator]; if (method != null) return method.call(iterable); } if (Symbol.iterator) { method = iterable[Symbol.iterator]; if (method != null) return method.call(iterable); } } throw new TypeError("Object is not async iterable"); }
|
||||
function _asyncIterator(iterable) { var method; if (typeof Symbol === "function") { if (Symbol.asyncIterator) { method = iterable[Symbol.asyncIterator]; if (method != null) return method.call(iterable); } if (Symbol.iterator) { method = iterable[Symbol.iterator]; if (method != null) return method.call(iterable); } } throw new TypeError("Object is not async iterable"); }
|
||||
|
||||
/*
|
||||
* @overview
|
||||
|
@ -13231,11 +13354,21 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
|
|||
case Actions["actionTypes"].DISCOVERY_STREAM_FEEDS_UPDATE:
|
||||
return { ...prevState,
|
||||
feeds: { ...prevState.feeds,
|
||||
data: action.data || prevState.feeds.data,
|
||||
loaded: true
|
||||
}
|
||||
};
|
||||
|
||||
case Actions["actionTypes"].DISCOVERY_STREAM_FEED_UPDATE:
|
||||
const newData = {};
|
||||
newData[action.data.url] = action.data.feed;
|
||||
return { ...prevState,
|
||||
feeds: { ...prevState.feeds,
|
||||
data: { ...prevState.feeds.data,
|
||||
...newData
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
case Actions["actionTypes"].DISCOVERY_STREAM_SPOCS_CAPS:
|
||||
return { ...prevState,
|
||||
spocs: { ...prevState.spocs,
|
||||
|
|
|
@ -1000,7 +1000,7 @@ This reports the user's interaction with Activity Stream Router.
|
|||
"source": "CFR",
|
||||
// message_id could be the ID of the recommendation, such as "wikipedia_addon"
|
||||
"message_id": "wikipedia_addon",
|
||||
"event": "[INSTALL | PIN | BLOCK | DISMISS | RATIONALE | LEARN_MORE | CLICK_DOORHANGER | MANAGE]"
|
||||
"event": "[IMPRESSION | INSTALL | PIN | BLOCK | DISMISS | RATIONALE | LEARN_MORE | CLICK | CLICK_DOORHANGER | MANAGE]"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1016,7 +1016,7 @@ This reports the user's interaction with Activity Stream Router.
|
|||
// message_id should be a bucket ID in the release channel, we may not use the
|
||||
// individual ID, such as addon ID, per legal's request
|
||||
"message_id": "bucket_id",
|
||||
"event": "[INSTALL | PIN | BLOCK | DISMISS | RATIONALE | LEARN_MORE | CLICK_DOORHANGER | MANAGE]"
|
||||
"event": "[IMPRESSION | INSTALL | PIN | BLOCK | DISMISS | RATIONALE | LEARN_MORE | CLICK | CLICK_DOORHANGER | MANAGE]"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
# 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/.
|
||||
|
||||
[localization] en-US.jar:
|
||||
trailhead.ftl (./data/trailhead.wip)
|
||||
|
||||
browser.jar:
|
||||
% resource activity-stream %res/activity-stream/ contentaccessible=yes
|
||||
res/activity-stream/lib/ (./lib/*)
|
||||
|
|
|
@ -41,6 +41,7 @@ ChromeUtils.defineModuleGetter(this, "Sampling",
|
|||
const TRAILHEAD_CONFIG = {
|
||||
OVERRIDE_PREF: "trailhead.firstrun.branches",
|
||||
DID_SEE_ABOUT_WELCOME_PREF: "trailhead.firstrun.didSeeAboutWelcome",
|
||||
INTERRUPTS_EXPERIMENT_PREF: "trailhead.firstrun.interruptsExperiment",
|
||||
BRANCHES: {
|
||||
interrupts: [
|
||||
["control"],
|
||||
|
@ -569,7 +570,7 @@ class _ASRouter {
|
|||
|
||||
ASRouterPreferences.init();
|
||||
ASRouterPreferences.addListener(this.onPrefChange);
|
||||
BookmarkPanelHub.init(this.handleMessageRequest, this.addImpression);
|
||||
BookmarkPanelHub.init(this.handleMessageRequest, this.addImpression, this.dispatch);
|
||||
|
||||
this._loadLocalProviders();
|
||||
|
||||
|
@ -747,6 +748,13 @@ class _ASRouter {
|
|||
experiment === "interrupts" ? interrupt : triplet,
|
||||
{type: "as-firstrun"}
|
||||
);
|
||||
|
||||
// On the first time setting the interrupts experiment, expose the branch
|
||||
// for normandy to target for survey study.
|
||||
if (experiment === "interrupts" &&
|
||||
!Services.prefs.prefHasUserValue(TRAILHEAD_CONFIG.INTERRUPTS_EXPERIMENT_PREF)) {
|
||||
Services.prefs.setStringPref(TRAILHEAD_CONFIG.INTERRUPTS_EXPERIMENT_PREF, interrupt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ const PREFS_CONFIG = new Map([
|
|||
value: "topsites,topstories,highlights",
|
||||
}],
|
||||
["improvesearch.noDefaultSearchTile", {
|
||||
title: "Experiment to remove tiles that are the same as the default search",
|
||||
title: "Remove tiles that are the same as the default search",
|
||||
value: true,
|
||||
}],
|
||||
["improvesearch.topSiteSearchShortcuts.searchEngines", {
|
||||
|
@ -215,7 +215,7 @@ const PREFS_CONFIG = new Map([
|
|||
title: "Configuration for CFR FxA Messages provider",
|
||||
value: JSON.stringify({
|
||||
id: "cfr-fxa",
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
type: "remote-settings",
|
||||
bucket: "cfr-fxa",
|
||||
frequency: {custom: [{period: "daily", cap: 1}]},
|
||||
|
|
|
@ -16,22 +16,26 @@ class _BookmarkPanelHub {
|
|||
this._trigger = {id: "bookmark-panel"};
|
||||
this._handleMessageRequest = null;
|
||||
this._addImpression = null;
|
||||
this._dispatch = null;
|
||||
this._initalized = false;
|
||||
this._response = null;
|
||||
this._l10n = null;
|
||||
|
||||
this.messageRequest = this.messageRequest.bind(this);
|
||||
this.toggleRecommendation = this.toggleRecommendation.bind(this);
|
||||
this.sendUserEventTelemetry = this.sendUserEventTelemetry.bind(this);
|
||||
this.collapseMessage = this.collapseMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function} handleMessageRequest
|
||||
* @param {function} addImpression
|
||||
* @param {function} dispatch - Used for sending user telemetry information
|
||||
*/
|
||||
init(handleMessageRequest, addImpression) {
|
||||
init(handleMessageRequest, addImpression, dispatch) {
|
||||
this._handleMessageRequest = handleMessageRequest;
|
||||
this._addImpression = addImpression;
|
||||
this._dispatch = dispatch;
|
||||
this._l10n = new DOMLocalization([
|
||||
"browser/branding/sync-brand.ftl",
|
||||
"browser/newtab/asrouter.ftl",
|
||||
|
@ -44,6 +48,7 @@ class _BookmarkPanelHub {
|
|||
this._initalized = false;
|
||||
this._handleMessageRequest = null;
|
||||
this._addImpression = null;
|
||||
this._dispatch = null;
|
||||
this._response = null;
|
||||
}
|
||||
|
||||
|
@ -83,6 +88,7 @@ class _BookmarkPanelHub {
|
|||
if (response && response.content) {
|
||||
this.showMessage(response.content, target, win);
|
||||
this.sendImpression();
|
||||
this.sendUserEventTelemetry("IMPRESSION");
|
||||
} else {
|
||||
this.hideMessage(target);
|
||||
}
|
||||
|
@ -111,6 +117,7 @@ class _BookmarkPanelHub {
|
|||
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
|
||||
csp: null,
|
||||
});
|
||||
this.sendUserEventTelemetry("CLICK");
|
||||
});
|
||||
recommendation.style.color = message.color;
|
||||
recommendation.style.background = `-moz-linear-gradient(-45deg, ${message.background_color_1} 0%, ${message.background_color_2} 70%)`;
|
||||
|
@ -119,6 +126,7 @@ class _BookmarkPanelHub {
|
|||
close.setAttribute("aria-label", "close");
|
||||
this._l10n.setAttributes(close, message.close_button.tooltiptext);
|
||||
close.addEventListener("click", e => {
|
||||
this.sendUserEventTelemetry("DISMISS");
|
||||
this.collapseMessage();
|
||||
target.close(e);
|
||||
});
|
||||
|
@ -181,6 +189,17 @@ class _BookmarkPanelHub {
|
|||
sendImpression() {
|
||||
this._addImpression(this._response);
|
||||
}
|
||||
|
||||
sendUserEventTelemetry(event) {
|
||||
this._sendTelemetry({message_id: this._response.id, bucket_id: this._response.id, event});
|
||||
}
|
||||
|
||||
_sendTelemetry(ping) {
|
||||
this._dispatch({
|
||||
type: "DOORHANGER_TELEMETRY",
|
||||
data: {action: "cfr_user_event", source: "CFR", ...ping},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._BookmarkPanelHub = _BookmarkPanelHub;
|
||||
|
|
|
@ -266,7 +266,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
* the scope for isStartup and the promises object.
|
||||
* Combines feed results and promises for each component with a feed.
|
||||
*/
|
||||
buildFeedPromise({newFeedsPromises, newFeeds}, isStartup) {
|
||||
buildFeedPromise({newFeedsPromises, newFeeds}, isStartup, sendUpdate) {
|
||||
return component => {
|
||||
const {url} = component.feed;
|
||||
|
||||
|
@ -275,8 +275,16 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
// we then fill in with the proper object inside the promise.
|
||||
newFeeds[url] = {};
|
||||
const feedPromise = this.getComponentFeed(url, isStartup);
|
||||
|
||||
feedPromise.then(feed => {
|
||||
newFeeds[url] = this.filterRecommendations(feed);
|
||||
sendUpdate({
|
||||
type: at.DISCOVERY_STREAM_FEED_UPDATE,
|
||||
data: {
|
||||
feed: newFeeds[url],
|
||||
url,
|
||||
},
|
||||
});
|
||||
|
||||
// We grab affinities off the first feed for the moment.
|
||||
// Ideally this would be returned from the server on the layout,
|
||||
|
@ -293,7 +301,6 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}).catch(/* istanbul ignore next */ error => {
|
||||
Cu.reportError(`Error trying to load component feed ${url}: ${error}`);
|
||||
});
|
||||
|
||||
newFeedsPromises.push(feedPromise);
|
||||
}
|
||||
};
|
||||
|
@ -317,11 +324,11 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
* @returns {Function} We return a function so we can contain the scope for isStartup.
|
||||
* Reduces feeds into promises and feed data.
|
||||
*/
|
||||
reduceFeedComponents(isStartup) {
|
||||
reduceFeedComponents(isStartup, sendUpdate) {
|
||||
return (accumulator, row) => {
|
||||
row.components
|
||||
.filter(component => component && component.feed)
|
||||
.forEach(this.buildFeedPromise(accumulator, isStartup));
|
||||
.forEach(this.buildFeedPromise(accumulator, isStartup, sendUpdate));
|
||||
return accumulator;
|
||||
};
|
||||
}
|
||||
|
@ -334,14 +341,14 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
* @returns {Object} An object with newFeedsPromises (Array) and newFeeds (Object),
|
||||
* we can Promise.all newFeedsPromises to get completed data in newFeeds.
|
||||
*/
|
||||
buildFeedPromises(layout, isStartup) {
|
||||
buildFeedPromises(layout, isStartup, sendUpdate) {
|
||||
const initialData = {
|
||||
newFeedsPromises: [],
|
||||
newFeeds: {},
|
||||
};
|
||||
return layout
|
||||
.filter(row => row && row.components)
|
||||
.reduce(this.reduceFeedComponents(isStartup), initialData);
|
||||
.reduce(this.reduceFeedComponents(isStartup, sendUpdate), initialData);
|
||||
}
|
||||
|
||||
async loadComponentFeeds(sendUpdate, isStartup) {
|
||||
|
@ -355,7 +362,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
// was issued to fetch the component feed in `getComponentFeed()`.
|
||||
this.componentFeedFetched = false;
|
||||
const start = perfService.absNow();
|
||||
const {newFeedsPromises, newFeeds} = this.buildFeedPromises(DiscoveryStream.layout, isStartup);
|
||||
const {newFeedsPromises, newFeeds} = this.buildFeedPromises(DiscoveryStream.layout, isStartup, sendUpdate);
|
||||
|
||||
// Each promise has a catch already built in, so no need to catch here.
|
||||
await Promise.all(newFeedsPromises);
|
||||
|
@ -365,7 +372,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
this.componentFeedRequestTime = Math.round(perfService.absNow() - start);
|
||||
}
|
||||
await this.cache.set("feeds", newFeeds);
|
||||
sendUpdate({type: at.DISCOVERY_STREAM_FEEDS_UPDATE, data: newFeeds});
|
||||
sendUpdate({
|
||||
type: at.DISCOVERY_STREAM_FEEDS_UPDATE,
|
||||
});
|
||||
}
|
||||
|
||||
async loadSpocs(sendUpdate, isStartup) {
|
||||
|
@ -615,6 +624,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
async getComponentFeed(feedUrl, isStartup) {
|
||||
const cachedData = await this.cache.get() || {};
|
||||
const {feeds} = cachedData;
|
||||
|
||||
let feed = feeds ? feeds[feedUrl] : null;
|
||||
if (this.isExpired({cachedData, key: "feed", url: feedUrl, isStartup})) {
|
||||
const feedResponse = await this.fetchFromEndpoint(feedUrl);
|
||||
|
@ -626,7 +636,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
feed = {
|
||||
lastUpdated: Date.now(),
|
||||
data: {
|
||||
...feedResponse,
|
||||
settings: feedResponse.settings,
|
||||
recommendations,
|
||||
},
|
||||
};
|
||||
|
@ -673,8 +683,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
this.loadAffinityScoresCache();
|
||||
await this.loadLayout(dispatch, isStartup);
|
||||
await Promise.all([
|
||||
this.loadComponentFeeds(dispatch, isStartup).catch(error => Cu.reportError(`Error trying to load component feeds: ${error}`)),
|
||||
this.loadSpocs(dispatch, isStartup).catch(error => Cu.reportError(`Error trying to load spocs feed: ${error}`)),
|
||||
this.loadComponentFeeds(dispatch, isStartup).catch(error => Cu.reportError(`Error trying to load component feeds: ${error}`)),
|
||||
]);
|
||||
if (isStartup) {
|
||||
await this._maybeUpdateCachedData();
|
||||
|
@ -1032,7 +1042,7 @@ defaultLayoutResp = {
|
|||
{
|
||||
"type": "CardGrid",
|
||||
"properties": {
|
||||
"items": 4,
|
||||
"items": 3,
|
||||
},
|
||||
"header": {
|
||||
"title": "",
|
||||
|
@ -1045,7 +1055,7 @@ defaultLayoutResp = {
|
|||
"probability": 1,
|
||||
"positions": [
|
||||
{
|
||||
"index": 3,
|
||||
"index": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -1062,19 +1072,21 @@ defaultLayoutResp = {
|
|||
},
|
||||
"feed": {
|
||||
"embed_reference": null,
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=4&duration=2592000&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=4&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
},
|
||||
"properties": {
|
||||
"items": 4,
|
||||
"has_numbers": false,
|
||||
"has_images": true,
|
||||
"border": "no-border",
|
||||
},
|
||||
"styles": {
|
||||
".ds-header": "margin-top: 4px;",
|
||||
},
|
||||
"spocs": {
|
||||
"probability": 1,
|
||||
"positions": [
|
||||
{
|
||||
"index": 3,
|
||||
"index": 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -1091,13 +1103,15 @@ defaultLayoutResp = {
|
|||
},
|
||||
"feed": {
|
||||
"embed_reference": null,
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=5&duration=2592000&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=5&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
},
|
||||
"properties": {
|
||||
"items": 4,
|
||||
"has_numbers": false,
|
||||
"has_images": true,
|
||||
"border": "no-border",
|
||||
},
|
||||
"styles": {
|
||||
".ds-header": "margin-top: 4px;",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1112,13 +1126,15 @@ defaultLayoutResp = {
|
|||
},
|
||||
"feed": {
|
||||
"embed_reference": null,
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=8&duration=2592000&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=8&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
},
|
||||
"properties": {
|
||||
"items": 4,
|
||||
"has_numbers": false,
|
||||
"has_images": true,
|
||||
"border": "no-border",
|
||||
},
|
||||
"styles": {
|
||||
".ds-header": "margin-top: 4px;",
|
||||
},
|
||||
"spocs": {
|
||||
"probability": 1,
|
||||
|
@ -1141,13 +1157,15 @@ defaultLayoutResp = {
|
|||
},
|
||||
"feed": {
|
||||
"embed_reference": null,
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=2&duration=2592000&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=2&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
},
|
||||
"styles": {
|
||||
".ds-header": "margin-top: 4px;",
|
||||
},
|
||||
"properties": {
|
||||
"items": 4,
|
||||
"has_numbers": false,
|
||||
"has_images": true,
|
||||
"border": "no-border",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1162,13 +1180,15 @@ defaultLayoutResp = {
|
|||
},
|
||||
"feed": {
|
||||
"embed_reference": null,
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=1&duration=2592000&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=1&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
},
|
||||
"properties": {
|
||||
"items": 4,
|
||||
"has_numbers": false,
|
||||
"has_images": true,
|
||||
"border": "no-border",
|
||||
},
|
||||
"styles": {
|
||||
".ds-header": "margin-top: 4px;",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1183,13 +1203,15 @@ defaultLayoutResp = {
|
|||
},
|
||||
"feed": {
|
||||
"embed_reference": null,
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=7&duration=2592000&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
"url": "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?topic_id=7&end_time_offset=172800&version=3&consumer_key=$apiKey&locale_lang=en-US&feed_variant=OptimalCuratedLinksForLocaleFeed&model_id=external_time_live",
|
||||
},
|
||||
"properties": {
|
||||
"items": 4,
|
||||
"has_numbers": false,
|
||||
"has_images": true,
|
||||
"border": "no-border",
|
||||
},
|
||||
"styles": {
|
||||
".ds-header": "margin-top: 4px;",
|
||||
},
|
||||
"spocs": {
|
||||
"probability": 1,
|
||||
|
|
|
@ -148,6 +148,7 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
targeting: "trailheadInterrupt == 'join'",
|
||||
trigger: {id: "firstRun"},
|
||||
includeBundle: {length: 3, template: "onboarding", trigger: {id: "showOnboarding"}},
|
||||
utm_term: "trailhead-join",
|
||||
content: {
|
||||
className: "joinCohort",
|
||||
title: {string_id: "onboarding-welcome-body"},
|
||||
|
@ -177,6 +178,7 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
targeting: "trailheadInterrupt == 'sync'",
|
||||
trigger: {id: "firstRun"},
|
||||
includeBundle: {length: 3, template: "onboarding", trigger: {id: "showOnboarding"}},
|
||||
utm_term: "trailhead-sync",
|
||||
content: {
|
||||
className: "syncCohort",
|
||||
title: {property_id: "firstrun_title"},
|
||||
|
@ -201,6 +203,7 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
targeting: "trailheadInterrupt == 'cards'",
|
||||
trigger: {id: "firstRun"},
|
||||
includeBundle: {length: 3, template: "onboarding", trigger: {id: "showOnboarding"}},
|
||||
utm_term: "trailhead-cards",
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_4",
|
||||
|
@ -241,7 +244,8 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
label: {string_id: "onboarding-data-sync-button"},
|
||||
action: {
|
||||
type: "OPEN_URL",
|
||||
data: {args: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun", where: "tabshifted"},
|
||||
addFlowParams: true,
|
||||
data: {args: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&style=trailhead", where: "tabshifted"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -274,11 +278,11 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
bundled: 3,
|
||||
order: 1,
|
||||
content: {
|
||||
title: {string_id: "onboarding-private-browsing-title"},
|
||||
text: {string_id: "onboarding-private-browsing-text"},
|
||||
title: {string_id: "onboarding-browse-privately-title"},
|
||||
text: {string_id: "onboarding-browse-privately-text"},
|
||||
icon: "private",
|
||||
primary_button: {
|
||||
label: {string_id: "onboarding-private-browsing-button"},
|
||||
label: {string_id: "onboarding-browse-privately-button"},
|
||||
action: {type: "OPEN_PRIVATE_BROWSER_WINDOW"},
|
||||
},
|
||||
},
|
||||
|
@ -298,7 +302,7 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
label: {string_id: "onboarding-firefox-send-button"},
|
||||
action: {
|
||||
type: "OPEN_URL",
|
||||
data: {args: "https://send.firefox.com/?utm_source=activity-stream?utm_medium=referral?utm_campaign=firstrun", where: "tabshifted"},
|
||||
data: {args: "https://send.firefox.com/", where: "tabshifted"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -329,25 +333,6 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
id: "TRAILHEAD_CARD_7",
|
||||
template: "onboarding",
|
||||
bundled: 3,
|
||||
content: {
|
||||
title: {string_id: "onboarding-privacy-right-title"},
|
||||
text: {string_id: "onboarding-privacy-right-text"},
|
||||
icon: "pledge",
|
||||
primary_button: {
|
||||
label: {string_id: "onboarding-privacy-right-button"},
|
||||
action: {
|
||||
type: "OPEN_URL",
|
||||
data: {args: "https://www.mozilla.org/?privacy-right", where: "tabshifted"},
|
||||
},
|
||||
},
|
||||
},
|
||||
targeting: "trailheadTriplet == 'unused'",
|
||||
trigger: {id: "showOnboarding"},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_CARD_8",
|
||||
template: "onboarding",
|
||||
bundled: 3,
|
||||
order: 3,
|
||||
content: {
|
||||
title: {string_id: "onboarding-send-tabs-title"},
|
||||
|
@ -365,7 +350,7 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
trigger: {id: "showOnboarding"},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_CARD_9",
|
||||
id: "TRAILHEAD_CARD_8",
|
||||
template: "onboarding",
|
||||
bundled: 3,
|
||||
order: 2,
|
||||
|
@ -385,7 +370,7 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
trigger: {id: "showOnboarding"},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_CARD_10",
|
||||
id: "TRAILHEAD_CARD_9",
|
||||
template: "onboarding",
|
||||
bundled: 3,
|
||||
order: 3,
|
||||
|
@ -405,7 +390,7 @@ const ONBOARDING_MESSAGES = async () => ([
|
|||
trigger: {id: "showOnboarding"},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_CARD_11",
|
||||
id: "TRAILHEAD_CARD_10",
|
||||
template: "onboarding",
|
||||
bundled: 3,
|
||||
order: 4,
|
||||
|
|
|
@ -42,7 +42,7 @@ const SECTION_ID = "topsites";
|
|||
const ROWS_PREF = "topSitesRows";
|
||||
|
||||
// Search experiment stuff
|
||||
const NO_DEFAULT_SEARCH_TILE_EXP_PREF = "improvesearch.noDefaultSearchTile";
|
||||
const FILTER_DEFAULT_SEARCH_PREF = "improvesearch.noDefaultSearchTile";
|
||||
const SEARCH_FILTERS = [
|
||||
"google",
|
||||
"search.yahoo",
|
||||
|
@ -87,7 +87,7 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
observe(subj, topic, data) {
|
||||
// We should update the current top sites if the search engine has been changed since
|
||||
// the search engine that gets filtered out of top sites has changed.
|
||||
if (topic === "browser-search-engine-modified" && data === "engine-default" && this.store.getState().Prefs.values[NO_DEFAULT_SEARCH_TILE_EXP_PREF]) {
|
||||
if (topic === "browser-search-engine-modified" && data === "engine-default" && this.store.getState().Prefs.values[FILTER_DEFAULT_SEARCH_PREF]) {
|
||||
delete this._currentSearchHostname;
|
||||
this._currentSearchHostname = getShortURLForCurrentSearch();
|
||||
this.refresh({broadcast: true});
|
||||
|
@ -127,20 +127,14 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
}
|
||||
|
||||
/**
|
||||
* isExperimentOnAndLinkFilteredSearch - is the experiment on and does a given hostname match the user's default search engine?
|
||||
* shouldFilterSearchTile - is default filtering enabled and does a given hostname match the user's default search engine?
|
||||
*
|
||||
* @param {string} hostname a top site hostname, such as "amazon" or "foo"
|
||||
* @returns {bool}
|
||||
*/
|
||||
isExperimentOnAndLinkFilteredSearch(hostname) {
|
||||
if (!this.store.getState().Prefs.values[NO_DEFAULT_SEARCH_TILE_EXP_PREF]) {
|
||||
return false;
|
||||
}
|
||||
// If TopSite Search Shortcuts is enabled we don't want to filter those sites out
|
||||
if (this.store.getState().Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT] && getSearchProvider(hostname)) {
|
||||
return false;
|
||||
}
|
||||
if (SEARCH_FILTERS.includes(hostname) || hostname === this._currentSearchHostname) {
|
||||
shouldFilterSearchTile(hostname) {
|
||||
if (this.store.getState().Prefs.values[FILTER_DEFAULT_SEARCH_PREF] &&
|
||||
(SEARCH_FILTERS.includes(hostname) || hostname === this._currentSearchHostname)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -227,7 +221,7 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
});
|
||||
for (let link of cache) {
|
||||
const hostname = shortURL(link);
|
||||
if (!this.isExperimentOnAndLinkFilteredSearch(hostname)) {
|
||||
if (!this.shouldFilterSearchTile(hostname)) {
|
||||
frecent.push({
|
||||
...(searchShortcutsExperiment ? await this.topSiteToSearchTopSite(link) : link),
|
||||
hostname,
|
||||
|
@ -241,7 +235,7 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
const searchProvider = getSearchProvider(shortURL(link));
|
||||
if (NewTabUtils.blockedLinks.isBlocked({url: link.url})) {
|
||||
continue;
|
||||
} else if (this.isExperimentOnAndLinkFilteredSearch(link.hostname)) {
|
||||
} else if (this.shouldFilterSearchTile(link.hostname)) {
|
||||
continue;
|
||||
// If we've previously blocked a search shortcut, remove the default top site
|
||||
// that matches the hostname
|
||||
|
@ -670,7 +664,7 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
this.refreshDefaults(action.data.value);
|
||||
break;
|
||||
case ROWS_PREF:
|
||||
case NO_DEFAULT_SEARCH_TILE_EXP_PREF:
|
||||
case FILTER_DEFAULT_SEARCH_PREF:
|
||||
case SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF:
|
||||
this.refresh({broadcast: true});
|
||||
break;
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -9,9 +9,9 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
<script src="chrome://browser/content/contentSearchUI.js"></script>
|
||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||
<script src="resource://activity-stream/vendor/react.js"></script>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
|
||||
</head>
|
||||
<body class="activity-stream">
|
||||
<div id="header-asrouter-container"></div>
|
||||
<div id="header-asrouter-container" role="presentation"></div>
|
||||
<div id="root"><!-- Regular React Rendering --></div>
|
||||
<div id="footer-asrouter-container"></div>
|
||||
<div id="footer-asrouter-container" role="presentation"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче