forked from mirrors/gecko-dev
Bug 1715158 - Remove ASRouterUISurface, snippet templates, and associated tests. r=pdahiya
Depends on D193853 Differential Revision: https://phabricator.services.mozilla.com/D193854
This commit is contained in:
parent
d7aac5f2af
commit
da108723df
35 changed files with 1 additions and 5719 deletions
|
|
@ -1,346 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.sys.mjs";
|
||||
import { actionTypes as at } from "common/Actions.sys.mjs";
|
||||
import { ASRouterUtils } from "./asrouter-utils";
|
||||
import { generateBundles } from "./rich-text-strings";
|
||||
import { ImpressionsWrapper } from "./components/ImpressionsWrapper/ImpressionsWrapper";
|
||||
import { LocalizationProvider, ReactLocalization } from "@fluent/react";
|
||||
import { NEWTAB_DARK_THEME } from "content-src/lib/constants";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { SnippetsTemplates } from "./templates/template-manifest";
|
||||
|
||||
const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
|
||||
|
||||
// Note: nextProps/prevProps refer to props passed to <ImpressionsWrapper />, not <ASRouterUISurface />
|
||||
function shouldSendImpressionOnUpdate(nextProps, prevProps) {
|
||||
return (
|
||||
nextProps.message.id &&
|
||||
(!prevProps.message || prevProps.message.id !== nextProps.message.id)
|
||||
);
|
||||
}
|
||||
|
||||
export class ASRouterUISurface extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.sendClick = this.sendClick.bind(this);
|
||||
this.sendImpression = this.sendImpression.bind(this);
|
||||
this.sendUserActionTelemetry = this.sendUserActionTelemetry.bind(this);
|
||||
this.onUserAction = this.onUserAction.bind(this);
|
||||
this.fetchFlowParams = this.fetchFlowParams.bind(this);
|
||||
this.onBlockSelected = this.onBlockSelected.bind(this);
|
||||
this.onBlockById = this.onBlockById.bind(this);
|
||||
this.onDismiss = this.onDismiss.bind(this);
|
||||
this.onMessageFromParent = this.onMessageFromParent.bind(this);
|
||||
|
||||
this.state = { message: {} };
|
||||
if (props.document) {
|
||||
this.footerPortal = props.document.getElementById(
|
||||
"footer-asrouter-container"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchFlowParams(params = {}) {
|
||||
let result = {};
|
||||
const { fxaEndpoint } = this.props;
|
||||
if (!fxaEndpoint) {
|
||||
const err =
|
||||
"Tried to fetch flow params before fxaEndpoint pref was ready";
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
const urlObj = new URL(fxaEndpoint);
|
||||
urlObj.pathname = "metrics-flow";
|
||||
Object.keys(params).forEach(key => {
|
||||
urlObj.searchParams.append(key, params[key]);
|
||||
});
|
||||
const response = await fetch(urlObj.toString(), { credentials: "omit" });
|
||||
if (response.status === 200) {
|
||||
const { deviceId, flowId, flowBeginTime } = await response.json();
|
||||
result = { deviceId, flowId, flowBeginTime };
|
||||
} else {
|
||||
console.error("Non-200 response", response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
sendUserActionTelemetry(extraProps = {}) {
|
||||
const { message } = this.state;
|
||||
const eventType = `${message.provider}_user_event`;
|
||||
const source = extraProps.id;
|
||||
delete extraProps.id;
|
||||
ASRouterUtils.sendTelemetry({
|
||||
source,
|
||||
message_id: message.id,
|
||||
action: eventType,
|
||||
...extraProps,
|
||||
});
|
||||
}
|
||||
|
||||
sendImpression(extraProps) {
|
||||
if (this.state.message.provider === "preview") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.sendUserActionTelemetry({ event: "IMPRESSION", ...extraProps });
|
||||
return ASRouterUtils.sendMessage({
|
||||
type: msg.IMPRESSION,
|
||||
data: this.state.message,
|
||||
});
|
||||
}
|
||||
|
||||
// If link has a `metric` data attribute send it as part of the `event_context`
|
||||
// telemetry field which can have arbitrary values.
|
||||
// Used for router messages with links as part of the content.
|
||||
sendClick(event) {
|
||||
const { dataset } = event.target;
|
||||
const metric = {
|
||||
event_context: dataset.metric,
|
||||
// Used for the `source` of the event. Needed to differentiate
|
||||
// from other snippet or onboarding events that may occur.
|
||||
id: "NEWTAB_FOOTER_BAR_CONTENT",
|
||||
};
|
||||
const { entrypoint_name, entrypoint_value } = dataset;
|
||||
// Assign the snippet referral for the action
|
||||
const entrypoint = entrypoint_name
|
||||
? new URLSearchParams([[entrypoint_name, entrypoint_value]]).toString()
|
||||
: entrypoint_value;
|
||||
const action = {
|
||||
type: dataset.action,
|
||||
data: {
|
||||
args: dataset.args,
|
||||
...(entrypoint && { entrypoint }),
|
||||
},
|
||||
};
|
||||
if (action.type) {
|
||||
ASRouterUtils.executeAction(action);
|
||||
}
|
||||
if (
|
||||
!this.state.message.content.do_not_autoblock &&
|
||||
!dataset.do_not_autoblock
|
||||
) {
|
||||
this.onBlockById(this.state.message.id);
|
||||
}
|
||||
if (this.state.message.provider !== "preview") {
|
||||
this.sendUserActionTelemetry({ event: "CLICK_BUTTON", ...metric });
|
||||
}
|
||||
}
|
||||
|
||||
onBlockSelected(options) {
|
||||
return this.onBlockById(this.state.message.id, {
|
||||
...options,
|
||||
campaign: this.state.message.campaign,
|
||||
});
|
||||
}
|
||||
|
||||
onBlockById(id, options) {
|
||||
return ASRouterUtils.blockById(id, options).then(clearAll => {
|
||||
if (clearAll) {
|
||||
this.setState({ message: {} });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDismiss() {
|
||||
this.clearMessage(this.state.message.id);
|
||||
}
|
||||
|
||||
// Blocking a snippet by id blocks the entire campaign
|
||||
// so when clearing we use the two values interchangeably
|
||||
clearMessage(idOrCampaign) {
|
||||
if (
|
||||
idOrCampaign === this.state.message.id ||
|
||||
idOrCampaign === this.state.message.campaign
|
||||
) {
|
||||
this.setState({ message: {} });
|
||||
}
|
||||
}
|
||||
|
||||
clearProvider(id) {
|
||||
if (this.state.message.provider === id) {
|
||||
this.setState({ message: {} });
|
||||
}
|
||||
}
|
||||
|
||||
onMessageFromParent({ type, data }) {
|
||||
// These only exists due to onPrefChange events in ASRouter
|
||||
switch (type) {
|
||||
case "ClearMessages": {
|
||||
data.forEach(id => this.clearMessage(id));
|
||||
break;
|
||||
}
|
||||
case "ClearProviders": {
|
||||
data.forEach(id => this.clearProvider(id));
|
||||
break;
|
||||
}
|
||||
case "EnterSnippetsPreviewMode": {
|
||||
this.props.dispatch({ type: at.SNIPPETS_PREVIEW_MODE });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestMessage(endpoint) {
|
||||
ASRouterUtils.sendMessage({
|
||||
type: "NEWTAB_MESSAGE_REQUEST",
|
||||
data: { endpoint },
|
||||
}).then(state => this.setState(state));
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const endpoint = ASRouterUtils.getPreviewEndpoint();
|
||||
if (endpoint && endpoint.theme === "dark") {
|
||||
global.window.dispatchEvent(
|
||||
new CustomEvent("LightweightTheme:Set", {
|
||||
detail: { data: NEWTAB_DARK_THEME },
|
||||
})
|
||||
);
|
||||
}
|
||||
if (endpoint && endpoint.dir === "rtl") {
|
||||
//Set `dir = rtl` on the HTML
|
||||
this.props.document.dir = "rtl";
|
||||
}
|
||||
ASRouterUtils.addListener(this.onMessageFromParent);
|
||||
this.requestMessage(endpoint);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ASRouterUtils.removeListener(this.onMessageFromParent);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (
|
||||
prevProps.adminContent &&
|
||||
JSON.stringify(prevProps.adminContent) !==
|
||||
JSON.stringify(this.props.adminContent)
|
||||
) {
|
||||
this.updateContent();
|
||||
}
|
||||
if (prevState.message.id !== this.state.message.id) {
|
||||
const main = global.window.document.querySelector("main");
|
||||
if (main) {
|
||||
if (this.state.message.id) {
|
||||
main.classList.add("has-snippet");
|
||||
} else {
|
||||
main.classList.remove("has-snippet");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateContent() {
|
||||
this.setState({
|
||||
...this.props.adminContent,
|
||||
});
|
||||
}
|
||||
|
||||
async getMonitorUrl({ url, flowRequestParams = {} }) {
|
||||
const flowValues = await this.fetchFlowParams(flowRequestParams);
|
||||
|
||||
// Note that flowParams are actually added dynamically on the page
|
||||
const urlObj = new URL(url);
|
||||
["deviceId", "flowId", "flowBeginTime"].forEach(key => {
|
||||
if (key in flowValues) {
|
||||
urlObj.searchParams.append(key, flowValues[key]);
|
||||
}
|
||||
});
|
||||
|
||||
return urlObj.toString();
|
||||
}
|
||||
|
||||
async onUserAction(action) {
|
||||
switch (action.type) {
|
||||
// This needs to be handled locally because its
|
||||
case "ENABLE_FIREFOX_MONITOR":
|
||||
const url = await this.getMonitorUrl(action.data.args);
|
||||
ASRouterUtils.executeAction({ type: "OPEN_URL", data: { args: url } });
|
||||
break;
|
||||
default:
|
||||
ASRouterUtils.executeAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
renderSnippets() {
|
||||
const { message } = this.state;
|
||||
if (!SnippetsTemplates[message.template]) {
|
||||
return null;
|
||||
}
|
||||
const SnippetComponent = SnippetsTemplates[message.template];
|
||||
const { content } = message;
|
||||
|
||||
return (
|
||||
<ImpressionsWrapper
|
||||
id="NEWTAB_FOOTER_BAR"
|
||||
message={this.state.message}
|
||||
sendImpression={this.sendImpression}
|
||||
shouldSendImpressionOnUpdate={shouldSendImpressionOnUpdate}
|
||||
// This helps with testing
|
||||
document={this.props.document}
|
||||
>
|
||||
<LocalizationProvider
|
||||
l10n={new ReactLocalization(generateBundles(content))}
|
||||
>
|
||||
<SnippetComponent
|
||||
{...this.state.message}
|
||||
UISurface="NEWTAB_FOOTER_BAR"
|
||||
onBlock={this.onBlockSelected}
|
||||
onDismiss={this.onDismiss}
|
||||
onAction={this.onUserAction}
|
||||
sendClick={this.sendClick}
|
||||
sendUserActionTelemetry={this.sendUserActionTelemetry}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</ImpressionsWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
renderPreviewBanner() {
|
||||
if (this.state.message.provider !== "preview") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="snippets-preview-banner">
|
||||
<span className="icon icon-small-spacer icon-info" />
|
||||
<span>Preview Purposes Only</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message } = this.state;
|
||||
if (!message.id) {
|
||||
return null;
|
||||
}
|
||||
const shouldRenderBelowSearch = TEMPLATES_BELOW_SEARCH.includes(
|
||||
message.template
|
||||
);
|
||||
|
||||
return shouldRenderBelowSearch ? (
|
||||
// Render special below search snippets in place;
|
||||
<div className="below-search-snippet-wrapper">
|
||||
{this.renderSnippets()}
|
||||
</div>
|
||||
) : (
|
||||
// For regular snippets etc. we should render everything in our footer
|
||||
// container.
|
||||
ReactDOM.createPortal(
|
||||
<>
|
||||
{this.renderPreviewBanner()}
|
||||
{this.renderSnippets()}
|
||||
</>,
|
||||
this.footerPortal
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ASRouterUISurface.defaultProps = { document: global.document };
|
||||
|
|
@ -74,41 +74,6 @@ export const ASRouterUtils = {
|
|||
return ASRouterUtils.sendMessage(ac.ASRouterUserEvent(ping));
|
||||
},
|
||||
getPreviewEndpoint() {
|
||||
if (
|
||||
global.document &&
|
||||
global.document.location &&
|
||||
global.document.location.href.includes("endpoint")
|
||||
) {
|
||||
const params = new URLSearchParams(
|
||||
global.document.location.href.slice(
|
||||
global.document.location.href.indexOf("endpoint")
|
||||
)
|
||||
);
|
||||
try {
|
||||
const endpoint = new URL(params.get("endpoint"));
|
||||
return {
|
||||
url: endpoint.href,
|
||||
snippetId: params.get("snippetId"),
|
||||
theme: this.getPreviewTheme(),
|
||||
dir: this.getPreviewDir(),
|
||||
};
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
getPreviewTheme() {
|
||||
return new URLSearchParams(
|
||||
global.document.location.href.slice(
|
||||
global.document.location.href.indexOf("theme")
|
||||
)
|
||||
).get("theme");
|
||||
},
|
||||
getPreviewDir() {
|
||||
return new URLSearchParams(
|
||||
global.document.location.href.slice(
|
||||
global.document.location.href.indexOf("dir")
|
||||
)
|
||||
).get("dir");
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Localized } from "@fluent/react";
|
||||
import React from "react";
|
||||
import { RICH_TEXT_KEYS } from "../../rich-text-strings";
|
||||
import { safeURI } from "../../template-utils";
|
||||
|
||||
// Elements allowed in snippet content
|
||||
const ALLOWED_TAGS = {
|
||||
b: <b />,
|
||||
i: <i />,
|
||||
u: <u />,
|
||||
strong: <strong />,
|
||||
em: <em />,
|
||||
br: <br />,
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform an object (tag name: {url}) into (tag name: anchor) where the url
|
||||
* is used as href, in order to render links inside a Fluent.Localized component.
|
||||
*/
|
||||
export function convertLinks(
|
||||
links,
|
||||
sendClick,
|
||||
doNotAutoBlock,
|
||||
openNewWindow = false
|
||||
) {
|
||||
if (links) {
|
||||
return Object.keys(links).reduce((acc, linkTag) => {
|
||||
const { action } = links[linkTag];
|
||||
// Setting the value to false will not include the attribute in the anchor
|
||||
const url = action ? false : safeURI(links[linkTag].url);
|
||||
|
||||
acc[linkTag] = (
|
||||
// eslint was getting a false positive caused by the dynamic injection
|
||||
// of content.
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
<a
|
||||
href={url}
|
||||
target={openNewWindow ? "_blank" : ""}
|
||||
data-metric={links[linkTag].metric}
|
||||
data-action={action}
|
||||
data-args={links[linkTag].args}
|
||||
data-do_not_autoblock={doNotAutoBlock}
|
||||
data-entrypoint_name={links[linkTag].entrypoint_name}
|
||||
data-entrypoint_value={links[linkTag].entrypoint_value}
|
||||
rel="noreferrer"
|
||||
onClick={sendClick}
|
||||
/>
|
||||
);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message wrapper used to sanitize markup and render HTML.
|
||||
*/
|
||||
export function RichText(props) {
|
||||
if (!RICH_TEXT_KEYS.includes(props.localization_id)) {
|
||||
throw new Error(
|
||||
`ASRouter: ${props.localization_id} is not a valid rich text property. If you want it to be processed, you need to add it to asrouter/rich-text-strings.js`
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Localized
|
||||
id={props.localization_id}
|
||||
elems={{
|
||||
...ALLOWED_TAGS,
|
||||
...props.customElements,
|
||||
...convertLinks(
|
||||
props.links,
|
||||
props.sendClick,
|
||||
props.doNotAutoBlock,
|
||||
props.openNewWindow
|
||||
),
|
||||
}}
|
||||
>
|
||||
<span>{props.text}</span>
|
||||
</Localized>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from "react";
|
||||
|
||||
export class SnippetBase extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onBlockClicked = this.onBlockClicked.bind(this);
|
||||
this.onDismissClicked = this.onDismissClicked.bind(this);
|
||||
this.setBlockButtonRef = this.setBlockButtonRef.bind(this);
|
||||
this.onBlockButtonMouseEnter = this.onBlockButtonMouseEnter.bind(this);
|
||||
this.onBlockButtonMouseLeave = this.onBlockButtonMouseLeave.bind(this);
|
||||
this.state = { blockButtonHover: false };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.blockButtonRef) {
|
||||
this.blockButtonRef.addEventListener(
|
||||
"mouseenter",
|
||||
this.onBlockButtonMouseEnter
|
||||
);
|
||||
this.blockButtonRef.addEventListener(
|
||||
"mouseleave",
|
||||
this.onBlockButtonMouseLeave
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.blockButtonRef) {
|
||||
this.blockButtonRef.removeEventListener(
|
||||
"mouseenter",
|
||||
this.onBlockButtonMouseEnter
|
||||
);
|
||||
this.blockButtonRef.removeEventListener(
|
||||
"mouseleave",
|
||||
this.onBlockButtonMouseLeave
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setBlockButtonRef(element) {
|
||||
this.blockButtonRef = element;
|
||||
}
|
||||
|
||||
onBlockButtonMouseEnter() {
|
||||
this.setState({ blockButtonHover: true });
|
||||
}
|
||||
|
||||
onBlockButtonMouseLeave() {
|
||||
this.setState({ blockButtonHover: false });
|
||||
}
|
||||
|
||||
onBlockClicked() {
|
||||
if (this.props.provider !== "preview") {
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "BLOCK",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
}
|
||||
|
||||
this.props.onBlock();
|
||||
}
|
||||
|
||||
onDismissClicked() {
|
||||
if (this.props.provider !== "preview") {
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "DISMISS",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
}
|
||||
|
||||
this.props.onDismiss();
|
||||
}
|
||||
|
||||
renderDismissButton() {
|
||||
if (this.props.footerDismiss) {
|
||||
return (
|
||||
<div className="footer">
|
||||
<div className="footer-content">
|
||||
<button
|
||||
className="ASRouterButton secondary"
|
||||
onClick={this.onDismissClicked}
|
||||
>
|
||||
{this.props.content.scene2_dismiss_button_text}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const label = this.props.content.block_button_text || "Remove this";
|
||||
return (
|
||||
<button
|
||||
className="blockButton"
|
||||
title={label}
|
||||
aria-label={label}
|
||||
onClick={this.onBlockClicked}
|
||||
ref={this.setBlockButtonRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { blockButtonHover } = this.state;
|
||||
|
||||
const containerClassName = `SnippetBaseContainer${
|
||||
props.className ? ` ${props.className}` : ""
|
||||
}${blockButtonHover ? " active" : ""}`;
|
||||
|
||||
return (
|
||||
<div className={containerClassName} style={this.props.textStyle}>
|
||||
<div className="innerWrapper">{props.children}</div>
|
||||
{this.renderDismissButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
.SnippetBaseContainer {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border-top: 1px solid transparent;
|
||||
box-shadow: $shadow-secondary;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-primary-action-background);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
[lwt-newtab-brighttext] & {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
&[type='checkbox'] {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.innerWrapper {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px $section-horizontal-padding;
|
||||
// This is to account for the block button on smaller screens
|
||||
padding-inline-end: 36px;
|
||||
max-width: $wrapper-max-width-large + ($section-horizontal-padding * 2);
|
||||
|
||||
@media (min-width: $break-point-large) {
|
||||
padding-inline-end: $section-horizontal-padding;
|
||||
}
|
||||
|
||||
@media (min-width: $break-point-widest) {
|
||||
max-width: $wrapper-max-width-widest + ($section-horizontal-padding * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.blockButton {
|
||||
display: none;
|
||||
background: none;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
inset-inline-end: 12px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-image: url('chrome://global/skin/icons/close.svg');
|
||||
-moz-context-properties: fill;
|
||||
color: inherit;
|
||||
fill: currentColor;
|
||||
opacity: 0.5;
|
||||
margin-top: -8px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover .blockButton {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
margin-inline-end: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.snippets-preview-banner {
|
||||
font-size: 15px;
|
||||
line-height: 42px;
|
||||
color: var(--newtab-text-primary-color);
|
||||
background: var(--newtab-background-color-secondary);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
// We show snippet icons for both themes and conditionally hide
|
||||
// based on which theme is currently active
|
||||
body {
|
||||
&:not([lwt-newtab-brighttext]) {
|
||||
.icon-dark-theme,
|
||||
.icon.icon-dark-theme,
|
||||
.scene2Icon .icon-dark-theme {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[lwt-newtab-brighttext] {
|
||||
.icon-light-theme,
|
||||
.icon.icon-light-theme,
|
||||
.scene2Icon .icon-light-theme {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { FluentBundle, FluentResource } from "@fluent/bundle";
|
||||
|
||||
/**
|
||||
* Properties that allow rich text MUST be added to this list.
|
||||
* key: the localization_id that should be used
|
||||
* value: a property or array of properties on the message.content object
|
||||
*/
|
||||
const RICH_TEXT_CONFIG = {
|
||||
text: ["text", "scene1_text"],
|
||||
success_text: "success_text",
|
||||
error_text: "error_text",
|
||||
scene2_text: "scene2_text",
|
||||
amo_html: "amo_html",
|
||||
privacy_html: "scene2_privacy_html",
|
||||
disclaimer_html: "scene2_disclaimer_html",
|
||||
};
|
||||
|
||||
export const RICH_TEXT_KEYS = Object.keys(RICH_TEXT_CONFIG);
|
||||
|
||||
/**
|
||||
* Generates an array of messages suitable for fluent's localization provider
|
||||
* including all needed strings for rich text.
|
||||
* @param {object} content A .content object from an ASR message (i.e. message.content)
|
||||
* @returns {FluentBundle[]} A array containing the fluent message context
|
||||
*/
|
||||
export function generateBundles(content) {
|
||||
const bundle = new FluentBundle("en-US");
|
||||
|
||||
RICH_TEXT_KEYS.forEach(key => {
|
||||
const attrs = RICH_TEXT_CONFIG[key];
|
||||
const attrsToTry = Array.isArray(attrs) ? [...attrs] : [attrs];
|
||||
let string = "";
|
||||
while (!string && attrsToTry.length) {
|
||||
const attr = attrsToTry.pop();
|
||||
string = content[attr];
|
||||
}
|
||||
bundle.addResource(new FluentResource(`${key} = ${string}`));
|
||||
});
|
||||
return [bundle];
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from "react";
|
||||
import { SimpleSnippet } from "../SimpleSnippet/SimpleSnippet";
|
||||
|
||||
class EOYSnippetBase extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* setFrequencyValue - `frequency` form parameter value should be `monthly`
|
||||
* if `monthly-checkbox` is selected or `single` otherwise
|
||||
*/
|
||||
setFrequencyValue() {
|
||||
const frequencyCheckbox = this.refs.form.querySelector("#monthly-checkbox");
|
||||
if (frequencyCheckbox.checked) {
|
||||
this.refs.form.querySelector("[name='frequency']").value = "monthly";
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
this.props.sendClick(event);
|
||||
this.setFrequencyValue();
|
||||
if (!this.props.content.do_not_autoblock) {
|
||||
this.props.onBlock();
|
||||
}
|
||||
this.refs.form.submit();
|
||||
}
|
||||
|
||||
renderDonations() {
|
||||
const fieldNames = ["first", "second", "third", "fourth"];
|
||||
const numberFormat = new Intl.NumberFormat(
|
||||
this.props.content.locale || navigator.language,
|
||||
{
|
||||
style: "currency",
|
||||
currency: this.props.content.currency_code,
|
||||
minimumFractionDigits: 0,
|
||||
}
|
||||
);
|
||||
// Default to `second` button
|
||||
const { selected_button } = this.props.content;
|
||||
const btnStyle = {
|
||||
color: this.props.content.button_color,
|
||||
backgroundColor: this.props.content.button_background_color,
|
||||
};
|
||||
const donationURLParams = [];
|
||||
const paramsStartIndex = this.props.content.donation_form_url.indexOf("?");
|
||||
for (const entry of new URLSearchParams(
|
||||
this.props.content.donation_form_url.slice(paramsStartIndex)
|
||||
).entries()) {
|
||||
donationURLParams.push(entry);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
className="EOYSnippetForm"
|
||||
action={this.props.content.donation_form_url}
|
||||
method={this.props.form_method}
|
||||
onSubmit={this.handleSubmit}
|
||||
data-metric="EOYSnippetForm"
|
||||
ref="form"
|
||||
>
|
||||
{donationURLParams.map(([key, value], idx) => (
|
||||
<input type="hidden" name={key} value={value} key={idx} />
|
||||
))}
|
||||
{fieldNames.map((field, idx) => {
|
||||
const button_name = `donation_amount_${field}`;
|
||||
const amount = this.props.content[button_name];
|
||||
return (
|
||||
<React.Fragment key={idx}>
|
||||
<input
|
||||
type="radio"
|
||||
name="amount"
|
||||
value={amount}
|
||||
id={field}
|
||||
defaultChecked={button_name === selected_button}
|
||||
/>
|
||||
<label htmlFor={field} className="donation-amount">
|
||||
{numberFormat.format(amount)}
|
||||
</label>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="monthly-checkbox-container">
|
||||
<input id="monthly-checkbox" type="checkbox" />
|
||||
<label htmlFor="monthly-checkbox">
|
||||
{this.props.content.monthly_checkbox_label_text}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="frequency" value="single" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="currency"
|
||||
value={this.props.content.currency_code}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="presets"
|
||||
value={fieldNames.map(
|
||||
field => this.props.content[`donation_amount_${field}`]
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
style={btnStyle}
|
||||
type="submit"
|
||||
className="ASRouterButton primary donation-form-url"
|
||||
>
|
||||
{this.props.content.button_label}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const textStyle = {
|
||||
color: this.props.content.text_color,
|
||||
backgroundColor: this.props.content.background_color,
|
||||
};
|
||||
const customElement = (
|
||||
<em style={{ backgroundColor: this.props.content.highlight_color }} />
|
||||
);
|
||||
return (
|
||||
<SimpleSnippet
|
||||
{...this.props}
|
||||
className={this.props.content.test}
|
||||
customElements={{ em: customElement }}
|
||||
textStyle={textStyle}
|
||||
extraContent={this.renderDonations()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const EOYSnippet = props => {
|
||||
const extendedContent = {
|
||||
monthly_checkbox_label_text: "Make my donation monthly",
|
||||
locale: "en-US",
|
||||
currency_code: "usd",
|
||||
selected_button: "donation_amount_second",
|
||||
...props.content,
|
||||
};
|
||||
|
||||
return (
|
||||
<EOYSnippetBase {...props} content={extendedContent} form_method="GET" />
|
||||
);
|
||||
};
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
{
|
||||
"title": "EOYSnippet",
|
||||
"description": "Fundraising Snippet",
|
||||
"version": "1.1.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"donation_form_url": {
|
||||
"type": "string",
|
||||
"description": "Url to the donation form."
|
||||
},
|
||||
"currency_code": {
|
||||
"type": "string",
|
||||
"description": "The code for the currency. Examle gbp, cad, usd.",
|
||||
"default": "usd"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "String for the locale code.",
|
||||
"default": "en-US"
|
||||
},
|
||||
"text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text_color": {
|
||||
"type": "string",
|
||||
"description": "Modify the text message color"
|
||||
},
|
||||
"background_color": {
|
||||
"type": "string",
|
||||
"description": "Snippet background color."
|
||||
},
|
||||
"highlight_color": {
|
||||
"type": "string",
|
||||
"description": "Paragraph em highlight color."
|
||||
},
|
||||
"donation_amount_first": {
|
||||
"type": "number",
|
||||
"description": "First button amount."
|
||||
},
|
||||
"donation_amount_second": {
|
||||
"type": "number",
|
||||
"description": "Second button amount."
|
||||
},
|
||||
"donation_amount_third": {
|
||||
"type": "number",
|
||||
"description": "Third button amount."
|
||||
},
|
||||
"donation_amount_fourth": {
|
||||
"type": "number",
|
||||
"description": "Fourth button amount."
|
||||
},
|
||||
"selected_button": {
|
||||
"type": "string",
|
||||
"description": "Default donation_amount_second. Donation amount button that's selected by default.",
|
||||
"default": "donation_amount_second"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text for accessibility",
|
||||
"default": ""
|
||||
},
|
||||
"title": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{ "description": "Snippet title displayed before snippet text" }
|
||||
]
|
||||
},
|
||||
"title_icon": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
|
||||
}
|
||||
]
|
||||
},
|
||||
"button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"block_button_text": {
|
||||
"type": "string",
|
||||
"description": "Tooltip text used for dismiss button."
|
||||
},
|
||||
"monthly_checkbox_label_text": {
|
||||
"type": "string",
|
||||
"description": "Label text for monthly checkbox.",
|
||||
"default": "Make my donation monthly"
|
||||
},
|
||||
"test": {
|
||||
"type": "string",
|
||||
"description": "Different styles for the snippet. Options are bold and takeover."
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked"
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
},
|
||||
"args": {
|
||||
"type": "string",
|
||||
"description": "Additional parameters for link action, example which specific menu the button should open"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"text",
|
||||
"donation_form_url",
|
||||
"donation_amount_first",
|
||||
"donation_amount_second",
|
||||
"donation_amount_third",
|
||||
"donation_amount_fourth",
|
||||
"button_label",
|
||||
"currency_code"
|
||||
],
|
||||
"dependencies": {
|
||||
"button_color": ["button_label"],
|
||||
"button_background_color": ["button_label"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.donation-amount,
|
||||
.donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.donation-amount {
|
||||
color: var(--newtab-text-primary-color);
|
||||
margin-inline-end: 18px;
|
||||
border: $input-border;
|
||||
padding: 5px 14px;
|
||||
background: var(--newtab-background-color-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
&[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px;
|
||||
|
||||
&:checked + .donation-amount {
|
||||
// Use a text color for the background to achieve an inverted look.
|
||||
background: var(--newtab-text-secondary-color);
|
||||
color: var(--newtab-background-color-secondary);
|
||||
border: $border-secondary;
|
||||
}
|
||||
|
||||
// accessibility
|
||||
&:checked:focus + .donation-amount,
|
||||
&:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-primary-action-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.monthly-checkbox-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from "react";
|
||||
import { SubmitFormSnippet } from "../SubmitFormSnippet/SubmitFormSnippet.jsx";
|
||||
|
||||
export const FXASignupSnippet = props => {
|
||||
const userAgent = window.navigator.userAgent.match(/Firefox\/([0-9]+)\./);
|
||||
const firefox_version = userAgent ? parseInt(userAgent[1], 10) : 0;
|
||||
const extendedContent = {
|
||||
scene1_button_label: "Learn more",
|
||||
retry_button_label: "Try again",
|
||||
scene2_email_placeholder_text: "Your email here",
|
||||
scene2_button_label: "Sign me up",
|
||||
scene2_dismiss_button_text: "Dismiss",
|
||||
...props.content,
|
||||
hidden_inputs: {
|
||||
action: "email",
|
||||
context: "fx_desktop_v3",
|
||||
entrypoint: "snippets",
|
||||
utm_source: "snippet",
|
||||
utm_content: firefox_version,
|
||||
utm_campaign: props.content.utm_campaign,
|
||||
utm_term: props.content.utm_term,
|
||||
...props.content.hidden_inputs,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<SubmitFormSnippet
|
||||
{...props}
|
||||
content={extendedContent}
|
||||
form_action={"https://accounts.firefox.com/"}
|
||||
form_method="GET"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
{
|
||||
"title": "FXASignupSnippet",
|
||||
"description": "A snippet template for FxA sign up/sign in",
|
||||
"version": "1.2.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"scene1_title": {
|
||||
"allof": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{ "description": "snippet title displayed before snippet text" }
|
||||
]
|
||||
},
|
||||
"scene1_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_section_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1, dark theme variant. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_text": {
|
||||
"type": "string",
|
||||
"description": "Section title text for scene 1. scene1_section_title_icon must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, scene1_section_title_text links to this" }
|
||||
]
|
||||
},
|
||||
"scene2_title": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Title displayed before text in scene 2. Should be plain text."
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene2_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene1_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene2_email_placeholder_text": {
|
||||
"type": "string",
|
||||
"description": "Value to show while input is empty.",
|
||||
"default": "Your email here"
|
||||
},
|
||||
"scene2_button_label": {
|
||||
"type": "string",
|
||||
"description": "Label for form submit button",
|
||||
"default": "Sign me up"
|
||||
},
|
||||
"scene2_dismiss_button_text": {
|
||||
"type": "string",
|
||||
"description": "Label for the dismiss button when the sign-up form is expanded.",
|
||||
"default": "Dismiss"
|
||||
},
|
||||
"hidden_inputs": {
|
||||
"type": "object",
|
||||
"description": "Each entry represents a hidden input, key is used as value for the name property.",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["email"]
|
||||
},
|
||||
"context": {
|
||||
"type": "string",
|
||||
"enum": ["fx_desktop_v3"]
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "string",
|
||||
"enum": ["snippets"]
|
||||
},
|
||||
"utm_content": {
|
||||
"type": "number",
|
||||
"description": "Firefox version number"
|
||||
},
|
||||
"utm_source": {
|
||||
"type": "string",
|
||||
"enum": ["snippet"]
|
||||
},
|
||||
"utm_campaign": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_campaign."
|
||||
},
|
||||
"utm_term": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_term."
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"scene1_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
|
||||
}
|
||||
],
|
||||
"default": "Learn more"
|
||||
},
|
||||
"scene1_button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"scene1_button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"retry_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for the button in the event of a submission error/failure."
|
||||
}
|
||||
],
|
||||
"default": "Try again"
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked",
|
||||
"default": false
|
||||
},
|
||||
"utm_campaign": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_campaign."
|
||||
},
|
||||
"utm_term": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_term."
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["scene1_text", "scene2_text", "scene1_button_label"],
|
||||
"dependencies": {
|
||||
"scene1_button_color": ["scene1_button_label"],
|
||||
"scene1_button_background_color": ["scene1_button_label"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from "react";
|
||||
import { SubmitFormSnippet } from "../SubmitFormSnippet/SubmitFormSnippet.jsx";
|
||||
|
||||
export const NewsletterSnippet = props => {
|
||||
const extendedContent = {
|
||||
scene1_button_label: "Learn more",
|
||||
retry_button_label: "Try again",
|
||||
scene2_email_placeholder_text: "Your email here",
|
||||
scene2_button_label: "Sign me up",
|
||||
scene2_dismiss_button_text: "Dismiss",
|
||||
scene2_newsletter: "mozilla-foundation",
|
||||
...props.content,
|
||||
hidden_inputs: {
|
||||
newsletters: props.content.scene2_newsletter || "mozilla-foundation",
|
||||
fmt: "H",
|
||||
lang: props.content.locale || "en-US",
|
||||
source_url: `https://snippets.mozilla.com/show/${props.id}`,
|
||||
...props.content.hidden_inputs,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<SubmitFormSnippet
|
||||
{...props}
|
||||
content={extendedContent}
|
||||
form_action={"https://basket.mozilla.org/subscribe.json"}
|
||||
form_method="POST"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
{
|
||||
"title": "NewsletterSnippet",
|
||||
"description": "A snippet template for send to device mobile download",
|
||||
"version": "1.2.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "Two to five character string for the locale code",
|
||||
"default": "en-US"
|
||||
},
|
||||
"scene1_title": {
|
||||
"allof": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{ "description": "snippet title displayed before snippet text" }
|
||||
]
|
||||
},
|
||||
"scene1_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_section_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1, dark theme variant. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_text": {
|
||||
"type": "string",
|
||||
"description": "Section title text for scene 1. scene1_section_title_icon must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, scene1_section_title_text links to this" }
|
||||
]
|
||||
},
|
||||
"scene2_title": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Title displayed before text in scene 2. Should be plain text."
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene2_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene1_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene2_email_placeholder_text": {
|
||||
"type": "string",
|
||||
"description": "Value to show while input is empty.",
|
||||
"default": "Your email here"
|
||||
},
|
||||
"scene2_button_label": {
|
||||
"type": "string",
|
||||
"description": "Label for form submit button",
|
||||
"default": "Sign me up"
|
||||
},
|
||||
"scene2_privacy_html": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Html for disclaimer and link underneath input box."
|
||||
},
|
||||
"scene2_dismiss_button_text": {
|
||||
"type": "string",
|
||||
"description": "Label for the dismiss button when the sign-up form is expanded.",
|
||||
"default": "Dismiss"
|
||||
},
|
||||
"hidden_inputs": {
|
||||
"type": "object",
|
||||
"description": "Each entry represents a hidden input, key is used as value for the name property.",
|
||||
"properties": {
|
||||
"fmt": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"default": "H"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scene1_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
|
||||
}
|
||||
],
|
||||
"default": "Learn more"
|
||||
},
|
||||
"scene1_button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"scene1_button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"retry_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for the button in the event of a submission error/failure."
|
||||
}
|
||||
],
|
||||
"default": "Try again"
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked",
|
||||
"default": false
|
||||
},
|
||||
"success_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown on successful registration."
|
||||
},
|
||||
"error_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown if registration failed."
|
||||
},
|
||||
"scene2_newsletter": {
|
||||
"type": "string",
|
||||
"description": "Newsletter/basket id user is subscribing to.",
|
||||
"default": "mozilla-foundation"
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["scene1_text", "scene2_text", "scene1_button_label"],
|
||||
"dependencies": {
|
||||
"scene1_button_color": ["scene1_button_label"],
|
||||
"scene1_button_background_color": ["scene1_button_label"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { isEmailOrPhoneNumber } from "./isEmailOrPhoneNumber";
|
||||
import React from "react";
|
||||
import { SubmitFormSnippet } from "../SubmitFormSnippet/SubmitFormSnippet.jsx";
|
||||
|
||||
function validateInput(value, content) {
|
||||
const type = isEmailOrPhoneNumber(value, content);
|
||||
return type ? "" : "Must be an email or a phone number.";
|
||||
}
|
||||
|
||||
function processFormData(input, message) {
|
||||
const { content } = message;
|
||||
const type = content.include_sms
|
||||
? isEmailOrPhoneNumber(input.value, content)
|
||||
: "email";
|
||||
const formData = new FormData();
|
||||
let url;
|
||||
if (type === "phone") {
|
||||
url = "https://basket.mozilla.org/news/subscribe_sms/";
|
||||
formData.append("mobile_number", input.value);
|
||||
formData.append("msg_name", content.message_id_sms);
|
||||
formData.append("country", content.country);
|
||||
} else if (type === "email") {
|
||||
url = "https://basket.mozilla.org/news/subscribe/";
|
||||
formData.append("email", input.value);
|
||||
formData.append("newsletters", content.message_id_email);
|
||||
formData.append(
|
||||
"source_url",
|
||||
encodeURIComponent(`https://snippets.mozilla.com/show/${message.id}`)
|
||||
);
|
||||
}
|
||||
formData.append("lang", content.locale);
|
||||
return { formData, url };
|
||||
}
|
||||
|
||||
function addDefaultValues(props) {
|
||||
return {
|
||||
...props,
|
||||
content: {
|
||||
scene1_button_label: "Learn more",
|
||||
retry_button_label: "Try again",
|
||||
scene2_dismiss_button_text: "Dismiss",
|
||||
scene2_button_label: "Send",
|
||||
scene2_input_placeholder: "Your email here",
|
||||
locale: "en-US",
|
||||
country: "us",
|
||||
message_id_email: "",
|
||||
include_sms: false,
|
||||
...props.content,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const SendToDeviceSnippet = props => {
|
||||
const propsWithDefaults = addDefaultValues(props);
|
||||
|
||||
return (
|
||||
<SubmitFormSnippet
|
||||
{...propsWithDefaults}
|
||||
form_method="POST"
|
||||
className="send_to_device_snippet"
|
||||
inputType={propsWithDefaults.content.include_sms ? "text" : "email"}
|
||||
validateInput={
|
||||
propsWithDefaults.content.include_sms ? validateInput : null
|
||||
}
|
||||
processFormData={processFormData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SendToDeviceScene2Snippet = props => {
|
||||
return <SendToDeviceSnippet expandedAlt={true} {...props} />;
|
||||
};
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
{
|
||||
"title": "SendToDeviceSnippet",
|
||||
"description": "A snippet template for send to device mobile download",
|
||||
"version": "1.2.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "Two to five character string for the locale code",
|
||||
"default": "en-US"
|
||||
},
|
||||
"country": {
|
||||
"type": "string",
|
||||
"description": "Two character string for the country code (used for SMS)",
|
||||
"default": "us"
|
||||
},
|
||||
"scene1_title": {
|
||||
"allof": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{ "description": "snippet title displayed before snippet text" }
|
||||
]
|
||||
},
|
||||
"scene1_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_section_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1, dark theme variant. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_text": {
|
||||
"type": "string",
|
||||
"description": "Section title text for scene 1. scene1_section_title_icon must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, scene1_section_title_text links to this" }
|
||||
]
|
||||
},
|
||||
"scene2_title": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Title displayed before text in scene 2. Should be plain text."
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene2_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene2_icon": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."
|
||||
},
|
||||
"scene2_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene1_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene2_button_label": {
|
||||
"type": "string",
|
||||
"description": "Label for form submit button",
|
||||
"default": "Send"
|
||||
},
|
||||
"scene2_input_placeholder": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Value to show while input is empty.",
|
||||
"default": "Your email here"
|
||||
},
|
||||
"scene2_disclaimer_html": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Html for disclaimer and link underneath input box."
|
||||
},
|
||||
"scene2_dismiss_button_text": {
|
||||
"type": "string",
|
||||
"description": "Label for the dismiss button when the sign-up form is expanded.",
|
||||
"default": "Dismiss"
|
||||
},
|
||||
"hidden_inputs": {
|
||||
"type": "object",
|
||||
"description": "Each entry represents a hidden input, key is used as value for the name property.",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["email"]
|
||||
},
|
||||
"context": {
|
||||
"type": "string",
|
||||
"enum": ["fx_desktop_v3"]
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "string",
|
||||
"enum": ["snippets"]
|
||||
},
|
||||
"utm_content": {
|
||||
"type": "string",
|
||||
"description": "Firefox version number"
|
||||
},
|
||||
"utm_source": {
|
||||
"type": "string",
|
||||
"enum": ["snippet"]
|
||||
},
|
||||
"utm_campaign": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_campaign."
|
||||
},
|
||||
"utm_term": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_term."
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"scene1_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
|
||||
}
|
||||
],
|
||||
"default": "Learn more"
|
||||
},
|
||||
"scene1_button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"scene1_button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"retry_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for the button in the event of a submission error/failure."
|
||||
}
|
||||
],
|
||||
"default": "Try again"
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked",
|
||||
"default": false
|
||||
},
|
||||
"success_title": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Title shown before text on successful registration."
|
||||
},
|
||||
"success_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown on successful registration."
|
||||
},
|
||||
"error_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown if registration failed."
|
||||
},
|
||||
"include_sms": {
|
||||
"type": "boolean",
|
||||
"description": "(send to device) Allow users to send an SMS message with the form?",
|
||||
"default": false
|
||||
},
|
||||
"message_id_sms": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Newsletter/basket id representing the SMS message to be sent."
|
||||
},
|
||||
"message_id_email": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."
|
||||
},
|
||||
"utm_campaign": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_campaign."
|
||||
},
|
||||
"utm_term": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_term."
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["scene1_text", "scene2_text", "scene1_button_label"],
|
||||
"dependencies": {
|
||||
"scene1_button_color": ["scene1_button_label"],
|
||||
"scene1_button_background_color": ["scene1_button_label"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Checks if a given string is an email or phone number or neither
|
||||
* @param {string} val The user input
|
||||
* @param {ASRMessageContent} content .content property on ASR message
|
||||
* @returns {"email"|"phone"|""} The type of the input
|
||||
*/
|
||||
export function isEmailOrPhoneNumber(val, content) {
|
||||
const { locale } = content;
|
||||
// http://emailregex.com/
|
||||
const email_re =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
const check_email = email_re.test(val);
|
||||
let check_phone; // depends on locale
|
||||
switch (locale) {
|
||||
case "en-US":
|
||||
case "en-CA":
|
||||
// allow 10-11 digits in case user wants to enter country code
|
||||
check_phone = val.length >= 10 && val.length <= 11 && !isNaN(val);
|
||||
break;
|
||||
case "de":
|
||||
// allow between 2 and 12 digits for german phone numbers
|
||||
check_phone = val.length >= 2 && val.length <= 12 && !isNaN(val);
|
||||
break;
|
||||
// this case should never be hit, but good to have a fallback just in case
|
||||
default:
|
||||
check_phone = !isNaN(val);
|
||||
break;
|
||||
}
|
||||
if (check_email) {
|
||||
return "email";
|
||||
} else if (check_phone) {
|
||||
return "phone";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "../../components/Button/Button";
|
||||
import { RichText } from "../../components/RichText/RichText";
|
||||
import { safeURI } from "../../template-utils";
|
||||
import { SnippetBase } from "../../components/SnippetBase/SnippetBase";
|
||||
|
||||
const DEFAULT_ICON_PATH = "chrome://branding/content/icon64.png";
|
||||
// Alt text placeholder in case the prop from the server isn't available
|
||||
const ICON_ALT_TEXT = "";
|
||||
|
||||
export class SimpleBelowSearchSnippet extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onButtonClick = this.onButtonClick.bind(this);
|
||||
}
|
||||
|
||||
renderText() {
|
||||
const { props } = this;
|
||||
return props.content.text ? (
|
||||
<RichText
|
||||
text={props.content.text}
|
||||
customElements={this.props.customElements}
|
||||
localization_id="text"
|
||||
links={props.content.links}
|
||||
sendClick={props.sendClick}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
renderTitle() {
|
||||
const { title } = this.props.content;
|
||||
return title ? (
|
||||
<h3 className={"title title-inline"}>
|
||||
{title}
|
||||
<br />
|
||||
</h3>
|
||||
) : null;
|
||||
}
|
||||
|
||||
async onButtonClick() {
|
||||
if (this.props.provider !== "preview") {
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "CLICK_BUTTON",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
}
|
||||
const { button_url } = this.props.content;
|
||||
// If button_url is defined handle it as OPEN_URL action
|
||||
const type = this.props.content.button_action || (button_url && "OPEN_URL");
|
||||
await this.props.onAction({
|
||||
type,
|
||||
data: { args: this.props.content.button_action_args || button_url },
|
||||
});
|
||||
if (!this.props.content.do_not_autoblock) {
|
||||
this.props.onBlock();
|
||||
}
|
||||
}
|
||||
|
||||
_shouldRenderButton() {
|
||||
return (
|
||||
this.props.content.button_action ||
|
||||
this.props.onButtonClick ||
|
||||
this.props.content.button_url
|
||||
);
|
||||
}
|
||||
|
||||
renderButton() {
|
||||
const { props } = this;
|
||||
if (!this._shouldRenderButton()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={props.onButtonClick || this.onButtonClick}
|
||||
color={props.content.button_color}
|
||||
backgroundColor={props.content.button_background_color}
|
||||
>
|
||||
{props.content.button_label}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
let className = "SimpleBelowSearchSnippet";
|
||||
let containerName = "below-search-snippet";
|
||||
|
||||
if (props.className) {
|
||||
className += ` ${props.className}`;
|
||||
}
|
||||
if (this._shouldRenderButton()) {
|
||||
className += " withButton";
|
||||
containerName += " withButton";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={containerName}>
|
||||
<div className="snippet-hover-wrapper">
|
||||
<SnippetBase
|
||||
{...props}
|
||||
className={className}
|
||||
textStyle={this.props.textStyle}
|
||||
>
|
||||
<img
|
||||
src={safeURI(props.content.icon) || DEFAULT_ICON_PATH}
|
||||
className="icon icon-light-theme"
|
||||
alt={props.content.icon_alt_text || ICON_ALT_TEXT}
|
||||
/>
|
||||
<img
|
||||
src={
|
||||
safeURI(props.content.icon_dark_theme || props.content.icon) ||
|
||||
DEFAULT_ICON_PATH
|
||||
}
|
||||
className="icon icon-dark-theme"
|
||||
alt={props.content.icon_alt_text || ICON_ALT_TEXT}
|
||||
/>
|
||||
<div className="textContainer">
|
||||
{this.renderTitle()}
|
||||
<p className="body">{this.renderText()}</p>
|
||||
{this.props.extraContent}
|
||||
</div>
|
||||
{<div className="buttonContainer">{this.renderButton()}</div>}
|
||||
</SnippetBase>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
{
|
||||
"title": "SimpleBelowSearchSnippet",
|
||||
"description": "A simple template with an icon, rich text and an optional button. It gets inserted below the Activity Stream search box.",
|
||||
"version": "1.2.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"title": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{ "description": "Snippet title displayed before snippet text" }
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text describing icon for screen readers",
|
||||
"default": ""
|
||||
},
|
||||
"block_button_text": {
|
||||
"type": "string",
|
||||
"description": "Tooltip text used for dismiss button.",
|
||||
"default": "Remove this"
|
||||
},
|
||||
"button_action": {
|
||||
"type": "string",
|
||||
"description": "The type of action the button should trigger."
|
||||
},
|
||||
"button_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, button_label links to this" }
|
||||
]
|
||||
},
|
||||
"button_action_args": {
|
||||
"description": "Additional parameters for button action, example which specific menu the button should open"
|
||||
},
|
||||
"button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
|
||||
}
|
||||
]
|
||||
},
|
||||
"button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA link has been clicked"
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
},
|
||||
"args": {
|
||||
"type": "string",
|
||||
"description": "Additional parameters for link action, example which specific menu the button should open"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["text"],
|
||||
"dependencies": {
|
||||
"button_action": ["button_label"],
|
||||
"button_url": ["button_label"],
|
||||
"button_color": ["button_label"],
|
||||
"button_background_color": ["button_label"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
/* stylelint-disable max-nesting-depth */
|
||||
|
||||
.below-search-snippet {
|
||||
margin: 0 auto 16px;
|
||||
|
||||
&.withButton {
|
||||
margin: auto;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
|
||||
.snippet-hover-wrapper {
|
||||
min-height: 60px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
|
||||
.blockButton {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.SimpleBelowSearchSnippet {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
z-index: auto;
|
||||
|
||||
@media (min-width: $break-point-large) {
|
||||
width: 736px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.innerWrapper {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: $shadow-card;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: $break-point-medium) {
|
||||
align-items: flex-start;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
text-align: inherit;
|
||||
width: 696px;
|
||||
}
|
||||
|
||||
@media (width <= 865px) {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
// There is an off-by-one gap between breakpoints; this is to prevent weirdness at exactly 610px.
|
||||
@media (max-width: $break-point-medium - 1px) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.blockButton {
|
||||
display: block;
|
||||
inset-inline-end: 10px;
|
||||
opacity: 1;
|
||||
top: 50%;
|
||||
|
||||
&:focus {
|
||||
box-shadow: $shadow-primary;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.title-inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.textContainer {
|
||||
margin: 10px;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-end: 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-top: 8px;
|
||||
margin-inline-start: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
|
||||
@media (min-width: $break-point-medium) {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: $break-point-medium) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.withButton {
|
||||
line-height: 20px;
|
||||
margin-bottom: 10px;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
|
||||
.innerWrapper {
|
||||
// There is an off-by-one gap between breakpoints; this is to prevent weirdness at exactly 1121px.
|
||||
@media (max-width: $break-point-widest + 1px) {
|
||||
margin: 0 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.blockButton {
|
||||
display: block;
|
||||
inset-inline-end: -10%;
|
||||
opacity: 0;
|
||||
margin: auto;
|
||||
top: unset;
|
||||
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
// There is an off-by-one gap between breakpoints; this is to prevent weirdness at exactly 1121px.
|
||||
@media (max-width: $break-point-widest + 1px) {
|
||||
inset-inline-end: 2%;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
margin: auto 0;
|
||||
margin-inline-end: 10px;
|
||||
|
||||
@media (max-width: $break-point-medium) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
margin: auto;
|
||||
margin-inline-end: 0;
|
||||
|
||||
@media (max-width: $break-point-medium) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@media (max-width: $break-point-medium) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
display: inline;
|
||||
position: sticky;
|
||||
transform: translateY(-50%);
|
||||
margin: 8px 0 0;
|
||||
|
||||
@media (min-width: $break-point-medium) {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Button } from "../../components/Button/Button";
|
||||
import ConditionalWrapper from "../../components/ConditionalWrapper/ConditionalWrapper";
|
||||
import React from "react";
|
||||
import { RichText } from "../../components/RichText/RichText";
|
||||
import { safeURI } from "../../template-utils";
|
||||
import { SnippetBase } from "../../components/SnippetBase/SnippetBase";
|
||||
|
||||
const DEFAULT_ICON_PATH = "chrome://branding/content/icon64.png";
|
||||
// Alt text placeholder in case the prop from the server isn't available
|
||||
const ICON_ALT_TEXT = "";
|
||||
|
||||
export class SimpleSnippet extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onButtonClick = this.onButtonClick.bind(this);
|
||||
}
|
||||
|
||||
onButtonClick() {
|
||||
if (this.props.provider !== "preview") {
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "CLICK_BUTTON",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
}
|
||||
const { button_url, button_entrypoint_value, button_entrypoint_name } =
|
||||
this.props.content;
|
||||
// If button_url is defined handle it as OPEN_URL action
|
||||
const type = this.props.content.button_action || (button_url && "OPEN_URL");
|
||||
// Assign the snippet referral for the action
|
||||
const entrypoint = button_entrypoint_name
|
||||
? new URLSearchParams([
|
||||
[button_entrypoint_name, button_entrypoint_value],
|
||||
]).toString()
|
||||
: button_entrypoint_value;
|
||||
this.props.onAction({
|
||||
type,
|
||||
data: {
|
||||
args: this.props.content.button_action_args || button_url,
|
||||
...(entrypoint && { entrypoint }),
|
||||
},
|
||||
});
|
||||
if (!this.props.content.do_not_autoblock) {
|
||||
this.props.onBlock();
|
||||
}
|
||||
}
|
||||
|
||||
_shouldRenderButton() {
|
||||
return (
|
||||
this.props.content.button_action ||
|
||||
this.props.onButtonClick ||
|
||||
this.props.content.button_url
|
||||
);
|
||||
}
|
||||
|
||||
renderTitle() {
|
||||
const { title } = this.props.content;
|
||||
return title ? (
|
||||
<h3
|
||||
className={`title ${this._shouldRenderButton() ? "title-inline" : ""}`}
|
||||
>
|
||||
{this.renderTitleIcon()} {title}
|
||||
</h3>
|
||||
) : null;
|
||||
}
|
||||
|
||||
renderTitleIcon() {
|
||||
const titleIconLight = safeURI(this.props.content.title_icon);
|
||||
const titleIconDark = safeURI(
|
||||
this.props.content.title_icon_dark_theme || this.props.content.title_icon
|
||||
);
|
||||
if (!titleIconLight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span
|
||||
className="titleIcon icon-light-theme"
|
||||
style={{ backgroundImage: `url("${titleIconLight}")` }}
|
||||
/>
|
||||
<span
|
||||
className="titleIcon icon-dark-theme"
|
||||
style={{ backgroundImage: `url("${titleIconDark}")` }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderButton() {
|
||||
const { props } = this;
|
||||
if (!this._shouldRenderButton()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={props.onButtonClick || this.onButtonClick}
|
||||
color={props.content.button_color}
|
||||
backgroundColor={props.content.button_background_color}
|
||||
>
|
||||
{props.content.button_label}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
renderText() {
|
||||
const { props } = this;
|
||||
return (
|
||||
<RichText
|
||||
text={props.content.text}
|
||||
customElements={this.props.customElements}
|
||||
localization_id="text"
|
||||
links={props.content.links}
|
||||
sendClick={props.sendClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
wrapSectionHeader(url) {
|
||||
return function (children) {
|
||||
return <a href={url}>{children}</a>;
|
||||
};
|
||||
}
|
||||
|
||||
wrapSnippetContent(children) {
|
||||
return <div className="innerContentWrapper">{children}</div>;
|
||||
}
|
||||
|
||||
renderSectionHeader() {
|
||||
const { props } = this;
|
||||
|
||||
// an icon and text must be specified to render the section header
|
||||
if (props.content.section_title_icon && props.content.section_title_text) {
|
||||
const sectionTitleIconLight = safeURI(props.content.section_title_icon);
|
||||
const sectionTitleIconDark = safeURI(
|
||||
props.content.section_title_icon_dark_theme ||
|
||||
props.content.section_title_icon
|
||||
);
|
||||
const sectionTitleURL = props.content.section_title_url;
|
||||
|
||||
return (
|
||||
<div className="section-header">
|
||||
<h3 className="section-title">
|
||||
<ConditionalWrapper
|
||||
condition={sectionTitleURL}
|
||||
wrap={this.wrapSectionHeader(sectionTitleURL)}
|
||||
>
|
||||
<span
|
||||
className="icon icon-small-spacer icon-light-theme"
|
||||
style={{ backgroundImage: `url("${sectionTitleIconLight}")` }}
|
||||
/>
|
||||
<span
|
||||
className="icon icon-small-spacer icon-dark-theme"
|
||||
style={{ backgroundImage: `url("${sectionTitleIconDark}")` }}
|
||||
/>
|
||||
<span className="section-title-text">
|
||||
{props.content.section_title_text}
|
||||
</span>
|
||||
</ConditionalWrapper>
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const sectionHeader = this.renderSectionHeader();
|
||||
let className = "SimpleSnippet";
|
||||
|
||||
if (props.className) {
|
||||
className += ` ${props.className}`;
|
||||
}
|
||||
if (props.content.tall) {
|
||||
className += " tall";
|
||||
}
|
||||
if (sectionHeader) {
|
||||
className += " has-section-header";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="snippet-hover-wrapper">
|
||||
<SnippetBase
|
||||
{...props}
|
||||
className={className}
|
||||
textStyle={this.props.textStyle}
|
||||
>
|
||||
{sectionHeader}
|
||||
<ConditionalWrapper
|
||||
condition={sectionHeader}
|
||||
wrap={this.wrapSnippetContent}
|
||||
>
|
||||
<img
|
||||
src={safeURI(props.content.icon) || DEFAULT_ICON_PATH}
|
||||
className="icon icon-light-theme"
|
||||
alt={props.content.icon_alt_text || ICON_ALT_TEXT}
|
||||
/>
|
||||
<img
|
||||
src={
|
||||
safeURI(props.content.icon_dark_theme || props.content.icon) ||
|
||||
DEFAULT_ICON_PATH
|
||||
}
|
||||
className="icon icon-dark-theme"
|
||||
alt={props.content.icon_alt_text || ICON_ALT_TEXT}
|
||||
/>
|
||||
<div>
|
||||
{this.renderTitle()} <p className="body">{this.renderText()}</p>
|
||||
{this.props.extraContent}
|
||||
</div>
|
||||
{<div>{this.renderButton()}</div>}
|
||||
</ConditionalWrapper>
|
||||
</SnippetBase>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
{
|
||||
"title": "SimpleSnippet",
|
||||
"description": "A simple template with an icon, text, and optional button.",
|
||||
"version": "1.1.2",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"title": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{ "description": "Snippet title displayed before snippet text" }
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon, dark theme variant. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text describing icon for screen readers",
|
||||
"default": ""
|
||||
},
|
||||
"title_icon": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"title_icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text describing title icon for screen readers",
|
||||
"default": ""
|
||||
},
|
||||
"button_action": {
|
||||
"type": "string",
|
||||
"description": "The type of action the button should trigger."
|
||||
},
|
||||
"button_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, button_label links to this" }
|
||||
]
|
||||
},
|
||||
"button_action_args": {
|
||||
"description": "Additional parameters for button action, example which specific menu the button should open"
|
||||
},
|
||||
"button_entrypoint_value": {
|
||||
"description": "String used for telemetry attribution of clicks",
|
||||
"type": "string"
|
||||
},
|
||||
"button_entrypoint_name": {
|
||||
"description": "String used for telemetry attribution of clicks",
|
||||
"type": "string"
|
||||
},
|
||||
"button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
|
||||
}
|
||||
]
|
||||
},
|
||||
"button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"block_button_text": {
|
||||
"type": "string",
|
||||
"description": "Tooltip text used for dismiss button.",
|
||||
"default": "Remove this"
|
||||
},
|
||||
"tall": {
|
||||
"type": "boolean",
|
||||
"description": "To be used by fundraising only, increases height to roughly 120px. Defaults to false."
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked"
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
},
|
||||
"args": {
|
||||
"type": "string",
|
||||
"description": "Additional parameters for link action, example which specific menu the button should open"
|
||||
}
|
||||
}
|
||||
},
|
||||
"section_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
|
||||
},
|
||||
"section_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
|
||||
},
|
||||
"section_title_text": {
|
||||
"type": "string",
|
||||
"description": "Section title text. section_title_icon must also be specified to display."
|
||||
},
|
||||
"section_title_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, section_title_text links to this" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["text"],
|
||||
"dependencies": {
|
||||
"button_action": ["button_label"],
|
||||
"button_url": ["button_label"],
|
||||
"button_color": ["button_label"],
|
||||
"button_background_color": ["button_label"],
|
||||
"section_title_url": ["section_title_text"],
|
||||
"button_entrypoint_name": ["button_entrypoint_value"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
$section-header-height: 30px;
|
||||
$icon-width: 54px; // width of primary icon + margin
|
||||
|
||||
.SimpleSnippet {
|
||||
&.tall {
|
||||
padding: 27px 0;
|
||||
}
|
||||
|
||||
p em {
|
||||
color: var(--newtab-text-emphasis-text-color);
|
||||
font-style: normal;
|
||||
background: var(--newtab-text-emphasis-background);
|
||||
}
|
||||
|
||||
&.bold {
|
||||
height: 176px;
|
||||
|
||||
.body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 71px;
|
||||
height: 71px;
|
||||
}
|
||||
}
|
||||
|
||||
&.takeover {
|
||||
height: 344px;
|
||||
|
||||
.body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 79px;
|
||||
height: 79px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.title-inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.titleIcon {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 2px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.tall .icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
|
||||
&.bold,
|
||||
&.takeover {
|
||||
.donation-form-url,
|
||||
.donation-amount {
|
||||
padding-block: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
&.has-section-header .innerWrapper {
|
||||
// account for section header being 100% width
|
||||
flex-wrap: wrap;
|
||||
padding-top: 7px;
|
||||
}
|
||||
|
||||
// wrapper div added if section-header is displayed that allows icon/text/button
|
||||
// to squish instead of wrapping. this is effectively replicating layout behavior
|
||||
// when section-header is *not* present.
|
||||
.innerContentWrapper {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
// color should match that of 'Recommended by Pocket' and 'Highlights' in newtab page
|
||||
color: var(--newtab-text-primary-color);
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 16px;
|
||||
margin-inline-end: 6px;
|
||||
margin-top: -2px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
{
|
||||
"title": "SubmitFormSnippet",
|
||||
"description": "A template with two states: a SimpleSnippet and another that contains a form",
|
||||
"version": "1.2.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "Two to five character string for the locale code"
|
||||
},
|
||||
"country": {
|
||||
"type": "string",
|
||||
"description": "Two character string for the country code (used for SMS)"
|
||||
},
|
||||
"section_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
|
||||
},
|
||||
"section_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
|
||||
},
|
||||
"section_title_text": {
|
||||
"type": "string",
|
||||
"description": "Section title text. section_title_icon must also be specified to display."
|
||||
},
|
||||
"section_title_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, section_title_text links to this" }
|
||||
]
|
||||
},
|
||||
"scene2_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"form_action": {
|
||||
"type": "string",
|
||||
"description": "Endpoint to submit form data."
|
||||
},
|
||||
"success_title": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Title shown before text on successful registration."
|
||||
},
|
||||
"success_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown on successful registration."
|
||||
},
|
||||
"error_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown if registration failed."
|
||||
},
|
||||
"scene2_email_placeholder_text": {
|
||||
"type": "string",
|
||||
"description": "Value to show while input is empty."
|
||||
},
|
||||
"scene2_input_placeholder": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Value to show while input is empty."
|
||||
},
|
||||
"scene2_button_label": {
|
||||
"type": "string",
|
||||
"description": "Label for form submit button"
|
||||
},
|
||||
"scene2_privacy_html": {
|
||||
"type": "string",
|
||||
"description": "Information about how the form data is used."
|
||||
},
|
||||
"scene2_disclaimer_html": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Html for disclaimer and link underneath input box."
|
||||
},
|
||||
"scene2_icon": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
|
||||
},
|
||||
"scene2_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."
|
||||
},
|
||||
"scene2_icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text describing scene2 icon for screen readers",
|
||||
"default": ""
|
||||
},
|
||||
"scene2_newsletter": {
|
||||
"type": "string",
|
||||
"description": "Newsletter/basket id user is subscribing to. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/. Default 'mozilla-foundation'."
|
||||
},
|
||||
"hidden_inputs": {
|
||||
"type": "object",
|
||||
"description": "Each entry represents a hidden input, key is used as value for the name property."
|
||||
},
|
||||
"retry_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for the button in the event of a submission error/failure."
|
||||
}
|
||||
],
|
||||
"default": "Try again"
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked"
|
||||
},
|
||||
"include_sms": {
|
||||
"type": "boolean",
|
||||
"description": "(send to device) Allow users to send an SMS message with the form?"
|
||||
},
|
||||
"message_id_sms": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Newsletter/basket id representing the SMS message to be sent."
|
||||
},
|
||||
"message_id_email": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."
|
||||
},
|
||||
"utm_campaign": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_campaign."
|
||||
},
|
||||
"utm_term": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_term."
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["scene2_text"],
|
||||
"dependencies": {
|
||||
"section_title_icon": ["section_title_text"],
|
||||
"section_title_icon_dark_theme": ["section_title_text"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,408 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Button } from "../../components/Button/Button";
|
||||
import React from "react";
|
||||
import { RichText } from "../../components/RichText/RichText";
|
||||
import { safeURI } from "../../template-utils";
|
||||
import { SimpleSnippet } from "../SimpleSnippet/SimpleSnippet";
|
||||
import { SnippetBase } from "../../components/SnippetBase/SnippetBase";
|
||||
import ConditionalWrapper from "../../components/ConditionalWrapper/ConditionalWrapper";
|
||||
|
||||
// Alt text placeholder in case the prop from the server isn't available
|
||||
const ICON_ALT_TEXT = "";
|
||||
|
||||
export class SubmitFormSnippet extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.expandSnippet = this.expandSnippet.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleSubmitAttempt = this.handleSubmitAttempt.bind(this);
|
||||
this.onInputChange = this.onInputChange.bind(this);
|
||||
this.state = {
|
||||
expanded: false,
|
||||
submitAttempted: false,
|
||||
signupSubmitted: false,
|
||||
signupSuccess: false,
|
||||
disableForm: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleSubmitAttempt() {
|
||||
if (!this.state.submitAttempted) {
|
||||
this.setState({ submitAttempted: true });
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit(event) {
|
||||
let json;
|
||||
|
||||
if (this.state.disableForm) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
this.setState({ disableForm: true });
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "CLICK_BUTTON",
|
||||
event_context: "conversion-subscribe-activation",
|
||||
id: "NEWTAB_FOOTER_BAR_CONTENT",
|
||||
});
|
||||
|
||||
if (this.props.form_method.toUpperCase() === "GET") {
|
||||
this.props.onBlock({ preventDismiss: true });
|
||||
this.refs.form.submit();
|
||||
return;
|
||||
}
|
||||
|
||||
const { url, formData } = this.props.processFormData
|
||||
? this.props.processFormData(this.refs.mainInput, this.props)
|
||||
: { url: this.refs.form.action, formData: new FormData(this.refs.form) };
|
||||
|
||||
try {
|
||||
const fetchRequest = new Request(url, {
|
||||
body: formData,
|
||||
method: "POST",
|
||||
credentials: "omit",
|
||||
});
|
||||
const response = await fetch(fetchRequest); // eslint-disable-line fetch-options/no-fetch-credentials
|
||||
json = await response.json();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (json && json.status === "ok") {
|
||||
this.setState({ signupSuccess: true, signupSubmitted: true });
|
||||
if (!this.props.content.do_not_autoblock) {
|
||||
this.props.onBlock({ preventDismiss: true });
|
||||
}
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "CLICK_BUTTON",
|
||||
event_context: "subscribe-success",
|
||||
id: "NEWTAB_FOOTER_BAR_CONTENT",
|
||||
});
|
||||
} else {
|
||||
console.error(
|
||||
"There was a problem submitting the form",
|
||||
json || "[No JSON response]"
|
||||
);
|
||||
this.setState({ signupSuccess: false, signupSubmitted: true });
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "CLICK_BUTTON",
|
||||
event_context: "subscribe-error",
|
||||
id: "NEWTAB_FOOTER_BAR_CONTENT",
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ disableForm: false });
|
||||
}
|
||||
|
||||
expandSnippet() {
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "CLICK_BUTTON",
|
||||
event_context: "scene1-button-learn-more",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
expanded: true,
|
||||
signupSuccess: false,
|
||||
signupSubmitted: false,
|
||||
});
|
||||
}
|
||||
|
||||
renderHiddenFormInputs() {
|
||||
const { hidden_inputs } = this.props.content;
|
||||
|
||||
if (!hidden_inputs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.keys(hidden_inputs).map((key, idx) => (
|
||||
<input key={idx} type="hidden" name={key} value={hidden_inputs[key]} />
|
||||
));
|
||||
}
|
||||
|
||||
renderDisclaimer() {
|
||||
const { content } = this.props;
|
||||
if (!content.scene2_disclaimer_html) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<p className="disclaimerText">
|
||||
<RichText
|
||||
text={content.scene2_disclaimer_html}
|
||||
localization_id="disclaimer_html"
|
||||
links={content.links}
|
||||
doNotAutoBlock={true}
|
||||
openNewWindow={true}
|
||||
sendClick={this.props.sendClick}
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderFormPrivacyNotice() {
|
||||
const { content } = this.props;
|
||||
if (!content.scene2_privacy_html) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<p className="privacyNotice">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="id_privacy"
|
||||
name="privacy"
|
||||
required="required"
|
||||
/>
|
||||
<label htmlFor="id_privacy">
|
||||
<RichText
|
||||
text={content.scene2_privacy_html}
|
||||
localization_id="privacy_html"
|
||||
links={content.links}
|
||||
doNotAutoBlock={true}
|
||||
openNewWindow={true}
|
||||
sendClick={this.props.sendClick}
|
||||
/>
|
||||
</label>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderSignupSubmitted() {
|
||||
const { content } = this.props;
|
||||
const isSuccess = this.state.signupSuccess;
|
||||
const successTitle = isSuccess && content.success_title;
|
||||
const bodyText = isSuccess
|
||||
? { success_text: content.success_text }
|
||||
: { error_text: content.error_text };
|
||||
const retryButtonText = content.retry_button_label;
|
||||
return (
|
||||
<SnippetBase {...this.props}>
|
||||
<div className="submissionStatus">
|
||||
{successTitle ? (
|
||||
<h2 className="submitStatusTitle">{successTitle}</h2>
|
||||
) : null}
|
||||
<p>
|
||||
<RichText
|
||||
{...bodyText}
|
||||
localization_id={isSuccess ? "success_text" : "error_text"}
|
||||
/>
|
||||
{isSuccess ? null : (
|
||||
<Button onClick={this.expandSnippet}>{retryButtonText}</Button>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</SnippetBase>
|
||||
);
|
||||
}
|
||||
|
||||
onInputChange(event) {
|
||||
if (!this.props.validateInput) {
|
||||
return;
|
||||
}
|
||||
const hasError = this.props.validateInput(
|
||||
event.target.value,
|
||||
this.props.content
|
||||
);
|
||||
event.target.setCustomValidity(hasError);
|
||||
}
|
||||
|
||||
wrapSectionHeader(url) {
|
||||
return function (children) {
|
||||
return <a href={url}>{children}</a>;
|
||||
};
|
||||
}
|
||||
|
||||
renderInput() {
|
||||
const placholder =
|
||||
this.props.content.scene2_email_placeholder_text ||
|
||||
this.props.content.scene2_input_placeholder;
|
||||
return (
|
||||
<input
|
||||
ref="mainInput"
|
||||
type={this.props.inputType || "email"}
|
||||
className={`mainInput${this.state.submitAttempted ? "" : " clean"}`}
|
||||
name="email"
|
||||
required={true}
|
||||
placeholder={placholder}
|
||||
onChange={this.props.validateInput ? this.onInputChange : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
return (
|
||||
<form
|
||||
action={this.props.form_action}
|
||||
method={this.props.form_method}
|
||||
onSubmit={this.handleSubmit}
|
||||
ref="form"
|
||||
>
|
||||
{this.renderHiddenFormInputs()}
|
||||
<div>
|
||||
{this.renderInput()}
|
||||
<button
|
||||
type="submit"
|
||||
className="ASRouterButton primary"
|
||||
onClick={this.handleSubmitAttempt}
|
||||
ref="formSubmitBtn"
|
||||
>
|
||||
{this.props.content.scene2_button_label}
|
||||
</button>
|
||||
</div>
|
||||
{this.renderFormPrivacyNotice() || this.renderDisclaimer()}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
renderScene2Icon() {
|
||||
const { content } = this.props;
|
||||
if (!content.scene2_icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="scene2Icon">
|
||||
<img
|
||||
src={safeURI(content.scene2_icon)}
|
||||
className="icon-light-theme"
|
||||
alt={content.scene2_icon_alt_text || ICON_ALT_TEXT}
|
||||
/>
|
||||
<img
|
||||
src={safeURI(content.scene2_icon_dark_theme || content.scene2_icon)}
|
||||
className="icon-dark-theme"
|
||||
alt={content.scene2_icon_alt_text || ICON_ALT_TEXT}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSignupView() {
|
||||
const { content } = this.props;
|
||||
const containerClass = `SubmitFormSnippet ${this.props.className}`;
|
||||
return (
|
||||
<SnippetBase
|
||||
{...this.props}
|
||||
className={containerClass}
|
||||
footerDismiss={true}
|
||||
>
|
||||
{this.renderScene2Icon()}
|
||||
<div className="message">
|
||||
<p>
|
||||
{content.scene2_title && (
|
||||
<h3 className="scene2Title">{content.scene2_title}</h3>
|
||||
)}{" "}
|
||||
{content.scene2_text && (
|
||||
<RichText
|
||||
scene2_text={content.scene2_text}
|
||||
localization_id="scene2_text"
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{this.renderForm()}
|
||||
</SnippetBase>
|
||||
);
|
||||
}
|
||||
|
||||
renderSectionHeader() {
|
||||
const { props } = this;
|
||||
|
||||
// an icon and text must be specified to render the section header
|
||||
if (props.content.section_title_icon && props.content.section_title_text) {
|
||||
const sectionTitleIconLight = safeURI(props.content.section_title_icon);
|
||||
const sectionTitleIconDark = safeURI(
|
||||
props.content.section_title_icon_dark_theme ||
|
||||
props.content.section_title_icon
|
||||
);
|
||||
const sectionTitleURL = props.content.section_title_url;
|
||||
|
||||
return (
|
||||
<div className="section-header">
|
||||
<h3 className="section-title">
|
||||
<ConditionalWrapper
|
||||
wrap={this.wrapSectionHeader(sectionTitleURL)}
|
||||
condition={sectionTitleURL}
|
||||
>
|
||||
<span
|
||||
className="icon icon-small-spacer icon-light-theme"
|
||||
style={{ backgroundImage: `url("${sectionTitleIconLight}")` }}
|
||||
/>
|
||||
<span
|
||||
className="icon icon-small-spacer icon-dark-theme"
|
||||
style={{ backgroundImage: `url("${sectionTitleIconDark}")` }}
|
||||
/>
|
||||
<span className="section-title-text">
|
||||
{props.content.section_title_text}
|
||||
</span>
|
||||
</ConditionalWrapper>
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderSignupViewAlt() {
|
||||
const { content } = this.props;
|
||||
const containerClass = `SubmitFormSnippet ${this.props.className} scene2Alt`;
|
||||
return (
|
||||
<SnippetBase
|
||||
{...this.props}
|
||||
className={containerClass}
|
||||
// Don't show bottom dismiss button
|
||||
footerDismiss={false}
|
||||
>
|
||||
{this.renderSectionHeader()}
|
||||
{this.renderScene2Icon()}
|
||||
<div className="message">
|
||||
<p>
|
||||
{content.scene2_text && (
|
||||
<RichText
|
||||
scene2_text={content.scene2_text}
|
||||
localization_id="scene2_text"
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
{this.renderForm()}
|
||||
</div>
|
||||
</SnippetBase>
|
||||
);
|
||||
}
|
||||
|
||||
getFirstSceneContent() {
|
||||
return Object.keys(this.props.content)
|
||||
.filter(key => key.includes("scene1"))
|
||||
.reduce((acc, key) => {
|
||||
acc[key.substr(7)] = this.props.content[key];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
render() {
|
||||
const content = { ...this.props.content, ...this.getFirstSceneContent() };
|
||||
|
||||
if (this.state.signupSubmitted) {
|
||||
return this.renderSignupSubmitted();
|
||||
}
|
||||
// Render only scene 2 (signup view). Must check before `renderSignupView`
|
||||
// to catch the Failure/Try again scenario where we want to return and render
|
||||
// the scene again.
|
||||
if (this.props.expandedAlt) {
|
||||
return this.renderSignupViewAlt();
|
||||
}
|
||||
if (this.state.expanded) {
|
||||
return this.renderSignupView();
|
||||
}
|
||||
return (
|
||||
<SimpleSnippet
|
||||
{...this.props}
|
||||
content={content}
|
||||
onButtonClick={this.expandSnippet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
{
|
||||
"title": "SubmitFormSnippet",
|
||||
"description": "A template with two states: a SimpleSnippet and another that contains a form",
|
||||
"version": "1.2.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "Two to five character string for the locale code"
|
||||
},
|
||||
"country": {
|
||||
"type": "string",
|
||||
"description": "Two character string for the country code (used for SMS)"
|
||||
},
|
||||
"scene1_title": {
|
||||
"allof": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{ "description": "snippet title displayed before snippet text" }
|
||||
]
|
||||
},
|
||||
"scene1_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_section_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Section title icon for scene 1, dark theme variant. 16x16px. SVG or PNG preferred. scene1_section_title_text must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_text": {
|
||||
"type": "string",
|
||||
"description": "Section title text for scene 1. scene1_section_title_icon must also be specified to display."
|
||||
},
|
||||
"scene1_section_title_url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "A url, scene1_section_title_text links to this" }
|
||||
]
|
||||
},
|
||||
"scene2_title": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Title displayed before text in scene 2. Should be plain text."
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene2_text": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/richText" },
|
||||
{
|
||||
"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"scene1_icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text describing scene1 icon for screen readers",
|
||||
"default": ""
|
||||
},
|
||||
"scene1_title_icon": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene1_title_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"scene1_title_icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text describing scene1 title icon for screen readers",
|
||||
"default": ""
|
||||
},
|
||||
"form_action": {
|
||||
"type": "string",
|
||||
"description": "Endpoint to submit form data."
|
||||
},
|
||||
"success_title": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Title shown before text on successful registration."
|
||||
},
|
||||
"success_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown on successful registration."
|
||||
},
|
||||
"error_text": {
|
||||
"type": "string",
|
||||
"description": "Message shown if registration failed."
|
||||
},
|
||||
"scene2_email_placeholder_text": {
|
||||
"type": "string",
|
||||
"description": "Value to show while input is empty."
|
||||
},
|
||||
"scene2_input_placeholder": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Value to show while input is empty."
|
||||
},
|
||||
"scene2_button_label": {
|
||||
"type": "string",
|
||||
"description": "Label for form submit button"
|
||||
},
|
||||
"scene2_privacy_html": {
|
||||
"type": "string",
|
||||
"description": "Information about how the form data is used."
|
||||
},
|
||||
"scene2_disclaimer_html": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Html for disclaimer and link underneath input box."
|
||||
},
|
||||
"scene2_dismiss_button_text": {
|
||||
"type": "string",
|
||||
"description": "Label for the dismiss button when the sign-up form is expanded."
|
||||
},
|
||||
"scene2_icon": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
|
||||
},
|
||||
"scene2_icon_dark_theme": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."
|
||||
},
|
||||
"scene2_icon_alt_text": {
|
||||
"type": "string",
|
||||
"description": "Alt text describing scene2 icon for screen readers",
|
||||
"default": ""
|
||||
},
|
||||
"scene2_newsletter": {
|
||||
"type": "string",
|
||||
"description": "Newsletter/basket id user is subscribing to. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/. Default 'mozilla-foundation'."
|
||||
},
|
||||
"hidden_inputs": {
|
||||
"type": "object",
|
||||
"description": "Each entry represents a hidden input, key is used as value for the name property."
|
||||
},
|
||||
"scene1_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."
|
||||
}
|
||||
]
|
||||
},
|
||||
"scene1_button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"scene1_button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"retry_button_label": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/plainText" },
|
||||
{
|
||||
"description": "Text for the button in the event of a submission error/failure."
|
||||
}
|
||||
],
|
||||
"default": "Try again"
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked"
|
||||
},
|
||||
"include_sms": {
|
||||
"type": "boolean",
|
||||
"description": "(send to device) Allow users to send an SMS message with the form?"
|
||||
},
|
||||
"message_id_sms": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Newsletter/basket id representing the SMS message to be sent."
|
||||
},
|
||||
"message_id_email": {
|
||||
"type": "string",
|
||||
"description": "(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."
|
||||
},
|
||||
"utm_campaign": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_campaign."
|
||||
},
|
||||
"utm_term": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_term."
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/link_url" },
|
||||
{ "description": "The url where the link points to." }
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["scene1_text", "scene2_text", "scene1_button_label"],
|
||||
"dependencies": {
|
||||
"scene1_button_color": ["scene1_button_label"],
|
||||
"scene1_button_background_color": ["scene1_button_label"]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
/* stylelint-disable max-nesting-depth */
|
||||
|
||||
.SubmitFormSnippet {
|
||||
flex-direction: column;
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
|
||||
.disclaimerText {
|
||||
margin: 5px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.send_to_device_snippet {
|
||||
text-align: center;
|
||||
|
||||
.message {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.scene2Title {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.ASRouterButton {
|
||||
&.primary {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
}
|
||||
|
||||
.scene2Icon {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
img {
|
||||
width: 98px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.scene2Title {
|
||||
font-size: inherit;
|
||||
margin: 0 0 10px;
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 14px;
|
||||
align-self: stretch;
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.privacyNotice {
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
.innerWrapper {
|
||||
// https://github.com/mozmeao/snippets/blob/2054899350590adcb3c0b0a341c782b0e2f81d0b/activity-stream/newsletter-subscribe.html#L46
|
||||
max-width: 736px;
|
||||
flex-wrap: wrap;
|
||||
justify-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: right;
|
||||
background-color: var(--newtab-background-color);
|
||||
padding: 10px 0;
|
||||
|
||||
.footer-content {
|
||||
margin: 0 auto;
|
||||
max-width: 768px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
|
||||
[dir='rtl'] & {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
&.mainInput {
|
||||
border-radius: 2px;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
border: $input-border;
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
width: 50%;
|
||||
|
||||
&.clean {
|
||||
&:invalid,
|
||||
&:required {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: $input-border-active;
|
||||
box-shadow: var(--newtab-textbox-focus-boxshadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.scene2Alt {
|
||||
text-align: start;
|
||||
|
||||
.scene2Icon {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message {
|
||||
flex: 5;
|
||||
margin-bottom: 0;
|
||||
|
||||
p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
width: 100%;
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.section-title a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.innerWrapper {
|
||||
padding: 0 0 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submissionStatus {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding: 20px 0;
|
||||
|
||||
.submitStatusTitle {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { EOYSnippet } from "./EOYSnippet/EOYSnippet";
|
||||
import { FXASignupSnippet } from "./FXASignupSnippet/FXASignupSnippet";
|
||||
import { NewsletterSnippet } from "./NewsletterSnippet/NewsletterSnippet";
|
||||
import {
|
||||
SendToDeviceSnippet,
|
||||
SendToDeviceScene2Snippet,
|
||||
} from "./SendToDeviceSnippet/SendToDeviceSnippet";
|
||||
import { SimpleBelowSearchSnippet } from "./SimpleBelowSearchSnippet/SimpleBelowSearchSnippet";
|
||||
import { SimpleSnippet } from "./SimpleSnippet/SimpleSnippet";
|
||||
|
||||
// Key names matching schema name of templates
|
||||
export const SnippetsTemplates = {
|
||||
simple_snippet: SimpleSnippet,
|
||||
newsletter_snippet: NewsletterSnippet,
|
||||
fxa_signup_snippet: FXASignupSnippet,
|
||||
send_to_device_snippet: SendToDeviceSnippet,
|
||||
send_to_device_scene2_snippet: SendToDeviceScene2Snippet,
|
||||
eoy_snippet: EOYSnippet,
|
||||
simple_below_search_snippet: SimpleBelowSearchSnippet,
|
||||
};
|
||||
|
|
@ -168,10 +168,5 @@ input {
|
|||
|
||||
// AS Router
|
||||
@import '../asrouter/components/Button/Button';
|
||||
@import '../asrouter/components/SnippetBase/SnippetBase';
|
||||
@import '../asrouter/components/ModalOverlay/ModalOverlay';
|
||||
@import '../asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet';
|
||||
@import '../asrouter/templates/SimpleSnippet/SimpleSnippet';
|
||||
@import '../asrouter/templates/SubmitFormSnippet/SubmitFormSnippet';
|
||||
@import '../asrouter/templates/EOYSnippet/EOYSnippet';
|
||||
// stylelint-enable no-invalid-position-at-import-rule
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ $shadow-image-inset: inset 0 0 0 0.5px $black-15;
|
|||
--newtab-status-error: #{$red-60};
|
||||
--newtab-inner-box-shadow-color: #{$black-10};
|
||||
--newtab-overlay-color: color-mix(in srgb, var(--newtab-background-color) 85%, transparent);
|
||||
--newtab-text-emphasis-background: #{$yellow-50};
|
||||
--newtab-text-emphasis-text-color: #{$grey-90};
|
||||
|
||||
@include textbox-focus(var(--newtab-primary-action-background));
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ input {
|
|||
--newtab-status-error: #D70022;
|
||||
--newtab-inner-box-shadow-color: rgba(0, 0, 0, 0.1);
|
||||
--newtab-overlay-color: color-mix(in srgb, var(--newtab-background-color) 85%, transparent);
|
||||
--newtab-text-emphasis-background: #FFE900;
|
||||
--newtab-text-emphasis-text-color: #0C0C0D;
|
||||
--newtab-textbox-focus-color: var(--newtab-primary-action-background);
|
||||
--newtab-textbox-focus-boxshadow: 0 0 0 1px var(--newtab-primary-action-background), 0 0 0 4px rgba(var(--newtab-primary-action-background), 0.3);
|
||||
--newtab-button-secondary-color: inherit;
|
||||
|
|
@ -4136,105 +4134,6 @@ main.has-snippet {
|
|||
transition: box-shadow 150ms;
|
||||
}
|
||||
|
||||
.SnippetBaseContainer {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border-top: 1px solid transparent;
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.SnippetBaseContainer a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-primary-action-background);
|
||||
}
|
||||
.SnippetBaseContainer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
[lwt-newtab-brighttext] .SnippetBaseContainer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
.SnippetBaseContainer input[type=checkbox] {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 25px;
|
||||
padding-inline-end: 36px;
|
||||
max-width: 836px;
|
||||
}
|
||||
@media (min-width: 866px) {
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
padding-inline-end: 25px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1122px) {
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
max-width: 1092px;
|
||||
}
|
||||
}
|
||||
.SnippetBaseContainer .blockButton {
|
||||
display: none;
|
||||
background: none;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
inset-inline-end: 12px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-image: url("chrome://global/skin/icons/close.svg");
|
||||
-moz-context-properties: fill;
|
||||
color: inherit;
|
||||
fill: currentColor;
|
||||
opacity: 0.5;
|
||||
margin-top: -8px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.SnippetBaseContainer:hover .blockButton {
|
||||
display: block;
|
||||
}
|
||||
.SnippetBaseContainer .icon {
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
margin-inline-end: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.snippets-preview-banner {
|
||||
font-size: 15px;
|
||||
line-height: 42px;
|
||||
color: var(--newtab-text-primary-color);
|
||||
background: var(--newtab-background-color-secondary);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.snippets-preview-banner span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
body:not([lwt-newtab-brighttext]) .icon-dark-theme,
|
||||
body:not([lwt-newtab-brighttext]) .icon.icon-dark-theme,
|
||||
body:not([lwt-newtab-brighttext]) .scene2Icon .icon-dark-theme {
|
||||
display: none;
|
||||
}
|
||||
body[lwt-newtab-brighttext] .icon-light-theme,
|
||||
body[lwt-newtab-brighttext] .icon.icon-light-theme,
|
||||
body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.activity-stream.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -4328,464 +4227,3 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||
transition: box-shadow 150ms;
|
||||
}
|
||||
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.below-search-snippet {
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
.below-search-snippet.withButton {
|
||||
margin: auto;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper {
|
||||
min-height: 60px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper:hover {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.SimpleBelowSearchSnippet {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
z-index: auto;
|
||||
}
|
||||
@media (min-width: 866px) {
|
||||
.SimpleBelowSearchSnippet {
|
||||
width: 736px;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.active {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
align-items: flex-start;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
text-align: inherit;
|
||||
width: 696px;
|
||||
}
|
||||
}
|
||||
@media (width <= 865px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: 609px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .blockButton {
|
||||
display: block;
|
||||
inset-inline-end: 10px;
|
||||
opacity: 1;
|
||||
top: 50%;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .blockButton:focus {
|
||||
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .title-inline {
|
||||
display: inline;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .textContainer {
|
||||
margin: 10px;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-end: 20px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
margin-top: 8px;
|
||||
margin-inline-start: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton {
|
||||
line-height: 20px;
|
||||
margin-bottom: 10px;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
}
|
||||
@media (max-width: 1123px) {
|
||||
.SimpleBelowSearchSnippet.withButton .innerWrapper {
|
||||
margin: 0 40px;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton {
|
||||
display: block;
|
||||
inset-inline-end: -10%;
|
||||
opacity: 0;
|
||||
margin: auto;
|
||||
top: unset;
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton:focus {
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
}
|
||||
@media (max-width: 1123px) {
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton {
|
||||
inset-inline-end: 2%;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
margin: auto 0;
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet.withButton .icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .buttonContainer {
|
||||
margin: auto;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet.withButton .buttonContainer {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet button {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .body {
|
||||
display: inline;
|
||||
position: sticky;
|
||||
transform: translateY(-50%);
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .body {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .body a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.SimpleSnippet.tall {
|
||||
padding: 27px 0;
|
||||
}
|
||||
.SimpleSnippet p em {
|
||||
color: var(--newtab-text-emphasis-text-color);
|
||||
font-style: normal;
|
||||
background: var(--newtab-text-emphasis-background);
|
||||
}
|
||||
.SimpleSnippet.bold {
|
||||
height: 176px;
|
||||
}
|
||||
.SimpleSnippet.bold .body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SimpleSnippet.bold .icon {
|
||||
width: 71px;
|
||||
height: 71px;
|
||||
}
|
||||
.SimpleSnippet.takeover {
|
||||
height: 344px;
|
||||
}
|
||||
.SimpleSnippet.takeover .body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
.SimpleSnippet.takeover .icon {
|
||||
width: 79px;
|
||||
height: 79px;
|
||||
}
|
||||
.SimpleSnippet .title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet .title-inline {
|
||||
display: inline;
|
||||
}
|
||||
.SimpleSnippet .titleIcon {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 2px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.SimpleSnippet .body {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet.tall .icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
.SimpleSnippet.bold .donation-form-url,
|
||||
.SimpleSnippet.bold .donation-amount, .SimpleSnippet.takeover .donation-form-url,
|
||||
.SimpleSnippet.takeover .donation-amount {
|
||||
padding-block: 8px;
|
||||
}
|
||||
.SimpleSnippet.bold .icon, .SimpleSnippet.takeover .icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
.SimpleSnippet .icon {
|
||||
align-self: flex-start;
|
||||
}
|
||||
.SimpleSnippet.has-section-header .innerWrapper {
|
||||
flex-wrap: wrap;
|
||||
padding-top: 7px;
|
||||
}
|
||||
.SimpleSnippet .innerContentWrapper {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.SimpleSnippet .section-header {
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SimpleSnippet .section-title {
|
||||
color: var(--newtab-text-primary-color);
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet .section-title a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.SimpleSnippet .section-title .icon {
|
||||
height: 16px;
|
||||
margin-inline-end: 6px;
|
||||
margin-top: -2px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.SubmitFormSnippet {
|
||||
flex-direction: column;
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet .disclaimerText {
|
||||
margin: 5px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
}
|
||||
.SubmitFormSnippet p {
|
||||
margin: 0;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet {
|
||||
text-align: center;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet .message {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet .scene2Title {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
}
|
||||
.SubmitFormSnippet .ASRouterButton.primary {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Icon {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Icon img {
|
||||
width: 98px;
|
||||
display: inline-block;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Title {
|
||||
font-size: inherit;
|
||||
margin: 0 0 10px;
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
}
|
||||
.SubmitFormSnippet form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet .message {
|
||||
font-size: 14px;
|
||||
align-self: stretch;
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SubmitFormSnippet .privacyNotice {
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
.SubmitFormSnippet .innerWrapper {
|
||||
max-width: 736px;
|
||||
flex-wrap: wrap;
|
||||
justify-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.SubmitFormSnippet .footer {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: right;
|
||||
background-color: var(--newtab-background-color);
|
||||
padding: 10px 0;
|
||||
}
|
||||
.SubmitFormSnippet .footer .footer-content {
|
||||
margin: 0 auto;
|
||||
max-width: 768px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
[dir=rtl] .SubmitFormSnippet .footer .footer-content {
|
||||
text-align: left;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput {
|
||||
border-radius: 2px;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
width: 50%;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput.clean:invalid, .SubmitFormSnippet input.mainInput.clean:required {
|
||||
box-shadow: none;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput:focus {
|
||||
border: 1px solid var(--newtab-textbox-focus-color);
|
||||
box-shadow: var(--newtab-textbox-focus-boxshadow);
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt {
|
||||
text-align: start;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .scene2Icon {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .message {
|
||||
flex: 5;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .message p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-header {
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-header .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-title a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .innerWrapper {
|
||||
padding: 0 0 16px;
|
||||
}
|
||||
|
||||
.submissionStatus {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.submissionStatus .submitStatusTitle {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: var(--newtab-text-primary-color);
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
padding: 5px 14px;
|
||||
background: var(--newtab-background-color-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.EOYSnippetForm input[type=radio] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px;
|
||||
}
|
||||
.EOYSnippetForm input[type=radio]:checked + .donation-amount {
|
||||
background: var(--newtab-text-secondary-color);
|
||||
color: var(--newtab-background-color-secondary);
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
}
|
||||
.EOYSnippetForm input[type=radio]:checked:focus + .donation-amount, .EOYSnippetForm input[type=radio]:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-primary-action-background);
|
||||
}
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ input {
|
|||
--newtab-status-error: #D70022;
|
||||
--newtab-inner-box-shadow-color: rgba(0, 0, 0, 0.1);
|
||||
--newtab-overlay-color: color-mix(in srgb, var(--newtab-background-color) 85%, transparent);
|
||||
--newtab-text-emphasis-background: #FFE900;
|
||||
--newtab-text-emphasis-text-color: #0C0C0D;
|
||||
--newtab-textbox-focus-color: var(--newtab-primary-action-background);
|
||||
--newtab-textbox-focus-boxshadow: 0 0 0 1px var(--newtab-primary-action-background), 0 0 0 4px rgba(var(--newtab-primary-action-background), 0.3);
|
||||
--newtab-button-secondary-color: inherit;
|
||||
|
|
@ -4140,105 +4138,6 @@ main.has-snippet {
|
|||
transition: box-shadow 150ms;
|
||||
}
|
||||
|
||||
.SnippetBaseContainer {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border-top: 1px solid transparent;
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.SnippetBaseContainer a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-primary-action-background);
|
||||
}
|
||||
.SnippetBaseContainer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
[lwt-newtab-brighttext] .SnippetBaseContainer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
.SnippetBaseContainer input[type=checkbox] {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 25px;
|
||||
padding-inline-end: 36px;
|
||||
max-width: 836px;
|
||||
}
|
||||
@media (min-width: 866px) {
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
padding-inline-end: 25px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1122px) {
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
max-width: 1092px;
|
||||
}
|
||||
}
|
||||
.SnippetBaseContainer .blockButton {
|
||||
display: none;
|
||||
background: none;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
inset-inline-end: 12px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-image: url("chrome://global/skin/icons/close.svg");
|
||||
-moz-context-properties: fill;
|
||||
color: inherit;
|
||||
fill: currentColor;
|
||||
opacity: 0.5;
|
||||
margin-top: -8px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.SnippetBaseContainer:hover .blockButton {
|
||||
display: block;
|
||||
}
|
||||
.SnippetBaseContainer .icon {
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
margin-inline-end: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.snippets-preview-banner {
|
||||
font-size: 15px;
|
||||
line-height: 42px;
|
||||
color: var(--newtab-text-primary-color);
|
||||
background: var(--newtab-background-color-secondary);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.snippets-preview-banner span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
body:not([lwt-newtab-brighttext]) .icon-dark-theme,
|
||||
body:not([lwt-newtab-brighttext]) .icon.icon-dark-theme,
|
||||
body:not([lwt-newtab-brighttext]) .scene2Icon .icon-dark-theme {
|
||||
display: none;
|
||||
}
|
||||
body[lwt-newtab-brighttext] .icon-light-theme,
|
||||
body[lwt-newtab-brighttext] .icon.icon-light-theme,
|
||||
body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.activity-stream.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -4332,464 +4231,3 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||
transition: box-shadow 150ms;
|
||||
}
|
||||
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.below-search-snippet {
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
.below-search-snippet.withButton {
|
||||
margin: auto;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper {
|
||||
min-height: 60px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper:hover {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.SimpleBelowSearchSnippet {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
z-index: auto;
|
||||
}
|
||||
@media (min-width: 866px) {
|
||||
.SimpleBelowSearchSnippet {
|
||||
width: 736px;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.active {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
align-items: flex-start;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
text-align: inherit;
|
||||
width: 696px;
|
||||
}
|
||||
}
|
||||
@media (width <= 865px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: 609px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .blockButton {
|
||||
display: block;
|
||||
inset-inline-end: 10px;
|
||||
opacity: 1;
|
||||
top: 50%;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .blockButton:focus {
|
||||
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .title-inline {
|
||||
display: inline;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .textContainer {
|
||||
margin: 10px;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-end: 20px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
margin-top: 8px;
|
||||
margin-inline-start: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton {
|
||||
line-height: 20px;
|
||||
margin-bottom: 10px;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
}
|
||||
@media (max-width: 1123px) {
|
||||
.SimpleBelowSearchSnippet.withButton .innerWrapper {
|
||||
margin: 0 40px;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton {
|
||||
display: block;
|
||||
inset-inline-end: -10%;
|
||||
opacity: 0;
|
||||
margin: auto;
|
||||
top: unset;
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton:focus {
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
}
|
||||
@media (max-width: 1123px) {
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton {
|
||||
inset-inline-end: 2%;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
margin: auto 0;
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet.withButton .icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .buttonContainer {
|
||||
margin: auto;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet.withButton .buttonContainer {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet button {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .body {
|
||||
display: inline;
|
||||
position: sticky;
|
||||
transform: translateY(-50%);
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .body {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .body a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.SimpleSnippet.tall {
|
||||
padding: 27px 0;
|
||||
}
|
||||
.SimpleSnippet p em {
|
||||
color: var(--newtab-text-emphasis-text-color);
|
||||
font-style: normal;
|
||||
background: var(--newtab-text-emphasis-background);
|
||||
}
|
||||
.SimpleSnippet.bold {
|
||||
height: 176px;
|
||||
}
|
||||
.SimpleSnippet.bold .body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SimpleSnippet.bold .icon {
|
||||
width: 71px;
|
||||
height: 71px;
|
||||
}
|
||||
.SimpleSnippet.takeover {
|
||||
height: 344px;
|
||||
}
|
||||
.SimpleSnippet.takeover .body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
.SimpleSnippet.takeover .icon {
|
||||
width: 79px;
|
||||
height: 79px;
|
||||
}
|
||||
.SimpleSnippet .title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet .title-inline {
|
||||
display: inline;
|
||||
}
|
||||
.SimpleSnippet .titleIcon {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 2px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.SimpleSnippet .body {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet.tall .icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
.SimpleSnippet.bold .donation-form-url,
|
||||
.SimpleSnippet.bold .donation-amount, .SimpleSnippet.takeover .donation-form-url,
|
||||
.SimpleSnippet.takeover .donation-amount {
|
||||
padding-block: 8px;
|
||||
}
|
||||
.SimpleSnippet.bold .icon, .SimpleSnippet.takeover .icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
.SimpleSnippet .icon {
|
||||
align-self: flex-start;
|
||||
}
|
||||
.SimpleSnippet.has-section-header .innerWrapper {
|
||||
flex-wrap: wrap;
|
||||
padding-top: 7px;
|
||||
}
|
||||
.SimpleSnippet .innerContentWrapper {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.SimpleSnippet .section-header {
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SimpleSnippet .section-title {
|
||||
color: var(--newtab-text-primary-color);
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet .section-title a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.SimpleSnippet .section-title .icon {
|
||||
height: 16px;
|
||||
margin-inline-end: 6px;
|
||||
margin-top: -2px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.SubmitFormSnippet {
|
||||
flex-direction: column;
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet .disclaimerText {
|
||||
margin: 5px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
}
|
||||
.SubmitFormSnippet p {
|
||||
margin: 0;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet {
|
||||
text-align: center;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet .message {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet .scene2Title {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
}
|
||||
.SubmitFormSnippet .ASRouterButton.primary {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Icon {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Icon img {
|
||||
width: 98px;
|
||||
display: inline-block;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Title {
|
||||
font-size: inherit;
|
||||
margin: 0 0 10px;
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
}
|
||||
.SubmitFormSnippet form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet .message {
|
||||
font-size: 14px;
|
||||
align-self: stretch;
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SubmitFormSnippet .privacyNotice {
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
.SubmitFormSnippet .innerWrapper {
|
||||
max-width: 736px;
|
||||
flex-wrap: wrap;
|
||||
justify-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.SubmitFormSnippet .footer {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: right;
|
||||
background-color: var(--newtab-background-color);
|
||||
padding: 10px 0;
|
||||
}
|
||||
.SubmitFormSnippet .footer .footer-content {
|
||||
margin: 0 auto;
|
||||
max-width: 768px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
[dir=rtl] .SubmitFormSnippet .footer .footer-content {
|
||||
text-align: left;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput {
|
||||
border-radius: 2px;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
width: 50%;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput.clean:invalid, .SubmitFormSnippet input.mainInput.clean:required {
|
||||
box-shadow: none;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput:focus {
|
||||
border: 1px solid var(--newtab-textbox-focus-color);
|
||||
box-shadow: var(--newtab-textbox-focus-boxshadow);
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt {
|
||||
text-align: start;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .scene2Icon {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .message {
|
||||
flex: 5;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .message p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-header {
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-header .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-title a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .innerWrapper {
|
||||
padding: 0 0 16px;
|
||||
}
|
||||
|
||||
.submissionStatus {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.submissionStatus .submitStatusTitle {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: var(--newtab-text-primary-color);
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
padding: 5px 14px;
|
||||
background: var(--newtab-background-color-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.EOYSnippetForm input[type=radio] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px;
|
||||
}
|
||||
.EOYSnippetForm input[type=radio]:checked + .donation-amount {
|
||||
background: var(--newtab-text-secondary-color);
|
||||
color: var(--newtab-background-color-secondary);
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
}
|
||||
.EOYSnippetForm input[type=radio]:checked:focus + .donation-amount, .EOYSnippetForm input[type=radio]:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-primary-action-background);
|
||||
}
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ input {
|
|||
--newtab-status-error: #D70022;
|
||||
--newtab-inner-box-shadow-color: rgba(0, 0, 0, 0.1);
|
||||
--newtab-overlay-color: color-mix(in srgb, var(--newtab-background-color) 85%, transparent);
|
||||
--newtab-text-emphasis-background: #FFE900;
|
||||
--newtab-text-emphasis-text-color: #0C0C0D;
|
||||
--newtab-textbox-focus-color: var(--newtab-primary-action-background);
|
||||
--newtab-textbox-focus-boxshadow: 0 0 0 1px var(--newtab-primary-action-background), 0 0 0 4px rgba(var(--newtab-primary-action-background), 0.3);
|
||||
--newtab-button-secondary-color: inherit;
|
||||
|
|
@ -4136,105 +4134,6 @@ main.has-snippet {
|
|||
transition: box-shadow 150ms;
|
||||
}
|
||||
|
||||
.SnippetBaseContainer {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border-top: 1px solid transparent;
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.SnippetBaseContainer a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-primary-action-background);
|
||||
}
|
||||
.SnippetBaseContainer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
[lwt-newtab-brighttext] .SnippetBaseContainer a {
|
||||
font-weight: bold;
|
||||
}
|
||||
.SnippetBaseContainer input[type=checkbox] {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 25px;
|
||||
padding-inline-end: 36px;
|
||||
max-width: 836px;
|
||||
}
|
||||
@media (min-width: 866px) {
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
padding-inline-end: 25px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1122px) {
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
max-width: 1092px;
|
||||
}
|
||||
}
|
||||
.SnippetBaseContainer .blockButton {
|
||||
display: none;
|
||||
background: none;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
inset-inline-end: 12px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-image: url("chrome://global/skin/icons/close.svg");
|
||||
-moz-context-properties: fill;
|
||||
color: inherit;
|
||||
fill: currentColor;
|
||||
opacity: 0.5;
|
||||
margin-top: -8px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.SnippetBaseContainer:hover .blockButton {
|
||||
display: block;
|
||||
}
|
||||
.SnippetBaseContainer .icon {
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
margin-inline-end: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.snippets-preview-banner {
|
||||
font-size: 15px;
|
||||
line-height: 42px;
|
||||
color: var(--newtab-text-primary-color);
|
||||
background: var(--newtab-background-color-secondary);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.snippets-preview-banner span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
body:not([lwt-newtab-brighttext]) .icon-dark-theme,
|
||||
body:not([lwt-newtab-brighttext]) .icon.icon-dark-theme,
|
||||
body:not([lwt-newtab-brighttext]) .scene2Icon .icon-dark-theme {
|
||||
display: none;
|
||||
}
|
||||
body[lwt-newtab-brighttext] .icon-light-theme,
|
||||
body[lwt-newtab-brighttext] .icon.icon-light-theme,
|
||||
body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.activity-stream.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -4328,464 +4227,3 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||
transition: box-shadow 150ms;
|
||||
}
|
||||
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.below-search-snippet {
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
.below-search-snippet.withButton {
|
||||
margin: auto;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper {
|
||||
min-height: 60px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper:hover {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
}
|
||||
.below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.SimpleBelowSearchSnippet {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
z-index: auto;
|
||||
}
|
||||
@media (min-width: 866px) {
|
||||
.SimpleBelowSearchSnippet {
|
||||
width: 736px;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.active {
|
||||
background-color: var(--newtab-element-hover-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
align-items: flex-start;
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
flex-direction: row;
|
||||
padding: 0;
|
||||
text-align: inherit;
|
||||
width: 696px;
|
||||
}
|
||||
}
|
||||
@media (width <= 865px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: 609px) {
|
||||
.SimpleBelowSearchSnippet .innerWrapper {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .blockButton {
|
||||
display: block;
|
||||
inset-inline-end: 10px;
|
||||
opacity: 1;
|
||||
top: 50%;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .blockButton:focus {
|
||||
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .title-inline {
|
||||
display: inline;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .textContainer {
|
||||
margin: 10px;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-end: 20px;
|
||||
}
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
margin-top: 8px;
|
||||
margin-inline-start: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton {
|
||||
line-height: 20px;
|
||||
margin-bottom: 10px;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
}
|
||||
@media (max-width: 1123px) {
|
||||
.SimpleBelowSearchSnippet.withButton .innerWrapper {
|
||||
margin: 0 40px;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton {
|
||||
display: block;
|
||||
inset-inline-end: -10%;
|
||||
opacity: 0;
|
||||
margin: auto;
|
||||
top: unset;
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton:focus {
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
}
|
||||
@media (max-width: 1123px) {
|
||||
.SimpleBelowSearchSnippet.withButton .blockButton {
|
||||
inset-inline-end: 2%;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
margin: auto 0;
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet.withButton .icon {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet.withButton .buttonContainer {
|
||||
margin: auto;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet.withButton .buttonContainer {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@media (max-width: 610px) {
|
||||
.SimpleBelowSearchSnippet button {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .body {
|
||||
display: inline;
|
||||
position: sticky;
|
||||
transform: translateY(-50%);
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
@media (min-width: 610px) {
|
||||
.SimpleBelowSearchSnippet .body {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
.SimpleBelowSearchSnippet .body a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.SimpleSnippet.tall {
|
||||
padding: 27px 0;
|
||||
}
|
||||
.SimpleSnippet p em {
|
||||
color: var(--newtab-text-emphasis-text-color);
|
||||
font-style: normal;
|
||||
background: var(--newtab-text-emphasis-background);
|
||||
}
|
||||
.SimpleSnippet.bold {
|
||||
height: 176px;
|
||||
}
|
||||
.SimpleSnippet.bold .body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SimpleSnippet.bold .icon {
|
||||
width: 71px;
|
||||
height: 71px;
|
||||
}
|
||||
.SimpleSnippet.takeover {
|
||||
height: 344px;
|
||||
}
|
||||
.SimpleSnippet.takeover .body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
.SimpleSnippet.takeover .icon {
|
||||
width: 79px;
|
||||
height: 79px;
|
||||
}
|
||||
.SimpleSnippet .title {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet .title-inline {
|
||||
display: inline;
|
||||
}
|
||||
.SimpleSnippet .titleIcon {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 14px;
|
||||
background-position: center;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 2px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.SimpleSnippet .body {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet.tall .icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
.SimpleSnippet.bold .donation-form-url,
|
||||
.SimpleSnippet.bold .donation-amount, .SimpleSnippet.takeover .donation-form-url,
|
||||
.SimpleSnippet.takeover .donation-amount {
|
||||
padding-block: 8px;
|
||||
}
|
||||
.SimpleSnippet.bold .icon, .SimpleSnippet.takeover .icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
.SimpleSnippet .icon {
|
||||
align-self: flex-start;
|
||||
}
|
||||
.SimpleSnippet.has-section-header .innerWrapper {
|
||||
flex-wrap: wrap;
|
||||
padding-top: 7px;
|
||||
}
|
||||
.SimpleSnippet .innerContentWrapper {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.SimpleSnippet .section-header {
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SimpleSnippet .section-title {
|
||||
color: var(--newtab-text-primary-color);
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
.SimpleSnippet .section-title a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.SimpleSnippet .section-title .icon {
|
||||
height: 16px;
|
||||
margin-inline-end: 6px;
|
||||
margin-top: -2px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.SubmitFormSnippet {
|
||||
flex-direction: column;
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet .disclaimerText {
|
||||
margin: 5px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
}
|
||||
.SubmitFormSnippet p {
|
||||
margin: 0;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet {
|
||||
text-align: center;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet .message {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SubmitFormSnippet.send_to_device_snippet .scene2Title {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
}
|
||||
.SubmitFormSnippet .ASRouterButton.primary {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Icon {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Icon img {
|
||||
width: 98px;
|
||||
display: inline-block;
|
||||
}
|
||||
.SubmitFormSnippet .scene2Title {
|
||||
font-size: inherit;
|
||||
margin: 0 0 10px;
|
||||
font-weight: bold;
|
||||
display: inline;
|
||||
}
|
||||
.SubmitFormSnippet form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet .message {
|
||||
font-size: 14px;
|
||||
align-self: stretch;
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SubmitFormSnippet .privacyNotice {
|
||||
font-size: 12px;
|
||||
color: var(--newtab-text-secondary-color);
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
.SubmitFormSnippet .innerWrapper {
|
||||
max-width: 736px;
|
||||
flex-wrap: wrap;
|
||||
justify-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.SubmitFormSnippet .footer {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: right;
|
||||
background-color: var(--newtab-background-color);
|
||||
padding: 10px 0;
|
||||
}
|
||||
.SubmitFormSnippet .footer .footer-content {
|
||||
margin: 0 auto;
|
||||
max-width: 768px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
[dir=rtl] .SubmitFormSnippet .footer .footer-content {
|
||||
text-align: left;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput {
|
||||
border-radius: 2px;
|
||||
background-color: var(--newtab-background-color-secondary);
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
width: 50%;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput.clean:invalid, .SubmitFormSnippet input.mainInput.clean:required {
|
||||
box-shadow: none;
|
||||
}
|
||||
.SubmitFormSnippet input.mainInput:focus {
|
||||
border: 1px solid var(--newtab-textbox-focus-color);
|
||||
box-shadow: var(--newtab-textbox-focus-boxshadow);
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt {
|
||||
text-align: start;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .scene2Icon {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .message {
|
||||
flex: 5;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .message p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-header {
|
||||
width: 100%;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-header .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .section-title a {
|
||||
color: var(--newtab-text-primary-color);
|
||||
font-weight: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.SubmitFormSnippet.scene2Alt .innerWrapper {
|
||||
padding: 0 0 16px;
|
||||
}
|
||||
|
||||
.submissionStatus {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.submissionStatus .submitStatusTitle {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: var(--newtab-text-primary-color);
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
padding: 5px 14px;
|
||||
background: var(--newtab-background-color-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.EOYSnippetForm input[type=radio] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px;
|
||||
}
|
||||
.EOYSnippetForm input[type=radio]:checked + .donation-amount {
|
||||
background: var(--newtab-text-secondary-color);
|
||||
color: var(--newtab-background-color-secondary);
|
||||
border: 1px solid var(--newtab-border-color);
|
||||
}
|
||||
.EOYSnippetForm input[type=radio]:checked:focus + .donation-amount, .EOYSnippetForm input[type=radio]:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-primary-action-background);
|
||||
}
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -683,29 +683,7 @@ const ASRouterUtils = {
|
|||
},
|
||||
|
||||
getPreviewEndpoint() {
|
||||
if (__webpack_require__.g.document && __webpack_require__.g.document.location && __webpack_require__.g.document.location.href.includes("endpoint")) {
|
||||
const params = new URLSearchParams(__webpack_require__.g.document.location.href.slice(__webpack_require__.g.document.location.href.indexOf("endpoint")));
|
||||
|
||||
try {
|
||||
const endpoint = new URL(params.get("endpoint"));
|
||||
return {
|
||||
url: endpoint.href,
|
||||
snippetId: params.get("snippetId"),
|
||||
theme: this.getPreviewTheme(),
|
||||
dir: this.getPreviewDir()
|
||||
};
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getPreviewTheme() {
|
||||
return new URLSearchParams(__webpack_require__.g.document.location.href.slice(__webpack_require__.g.document.location.href.indexOf("theme"))).get("theme");
|
||||
},
|
||||
|
||||
getPreviewDir() {
|
||||
return new URLSearchParams(__webpack_require__.g.document.location.href.slice(__webpack_require__.g.document.location.href.indexOf("dir"))).get("dir");
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ module.exports = function (config) {
|
|||
statements: 66,
|
||||
lines: 66,
|
||||
functions: 78,
|
||||
branches: 63,
|
||||
branches: 50,
|
||||
},
|
||||
"lib/TelemetryFeed.jsm": {
|
||||
statements: 98,
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
import {
|
||||
convertLinks,
|
||||
RichText,
|
||||
} from "content-src/asrouter/components/RichText/RichText";
|
||||
import { FluentBundle, FluentResource } from "@fluent/bundle";
|
||||
import {
|
||||
Localized,
|
||||
LocalizationProvider,
|
||||
ReactLocalization,
|
||||
} from "@fluent/react";
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
function mockL10nWrapper(content) {
|
||||
const bundle = new FluentBundle("en-US");
|
||||
for (const [id, value] of Object.entries(content)) {
|
||||
if (typeof value === "string") {
|
||||
bundle.addResource(new FluentResource(`${id} = ${value}`));
|
||||
}
|
||||
}
|
||||
const l10n = new ReactLocalization([bundle]);
|
||||
return {
|
||||
wrappingComponent: LocalizationProvider,
|
||||
wrappingComponentProps: { l10n },
|
||||
};
|
||||
}
|
||||
|
||||
describe("convertLinks", () => {
|
||||
let sandbox;
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
it("should return an object with anchor elements", () => {
|
||||
const cta = {
|
||||
url: "https://foo.com",
|
||||
metric: "foo",
|
||||
};
|
||||
const stub = sandbox.stub();
|
||||
const result = convertLinks({ cta }, stub);
|
||||
|
||||
assert.property(result, "cta");
|
||||
assert.propertyVal(result.cta, "type", "a");
|
||||
assert.propertyVal(result.cta.props, "href", cta.url);
|
||||
assert.propertyVal(result.cta.props, "data-metric", cta.metric);
|
||||
assert.propertyVal(result.cta.props, "onClick", stub);
|
||||
});
|
||||
it("should return an anchor element without href", () => {
|
||||
const cta = {
|
||||
url: "https://foo.com",
|
||||
metric: "foo",
|
||||
action: "OPEN_MENU",
|
||||
args: "appMenu",
|
||||
entrypoint_name: "entrypoint_name",
|
||||
entrypoint_value: "entrypoint_value",
|
||||
};
|
||||
const stub = sandbox.stub();
|
||||
const result = convertLinks({ cta }, stub);
|
||||
|
||||
assert.property(result, "cta");
|
||||
assert.propertyVal(result.cta, "type", "a");
|
||||
assert.propertyVal(result.cta.props, "href", false);
|
||||
assert.propertyVal(result.cta.props, "data-metric", cta.metric);
|
||||
assert.propertyVal(result.cta.props, "data-action", cta.action);
|
||||
assert.propertyVal(result.cta.props, "data-args", cta.args);
|
||||
assert.propertyVal(
|
||||
result.cta.props,
|
||||
"data-entrypoint_name",
|
||||
cta.entrypoint_name
|
||||
);
|
||||
assert.propertyVal(
|
||||
result.cta.props,
|
||||
"data-entrypoint_value",
|
||||
cta.entrypoint_value
|
||||
);
|
||||
assert.propertyVal(result.cta.props, "onClick", stub);
|
||||
});
|
||||
it("should follow openNewWindow prop", () => {
|
||||
const cta = { url: "https://foo.com" };
|
||||
const newWindow = convertLinks({ cta }, sandbox.stub(), false, true);
|
||||
const sameWindow = convertLinks({ cta }, sandbox.stub(), false);
|
||||
|
||||
assert.propertyVal(newWindow.cta.props, "target", "_blank");
|
||||
assert.propertyVal(sameWindow.cta.props, "target", "");
|
||||
});
|
||||
it("should allow for custom elements & styles", () => {
|
||||
const wrapper = mount(
|
||||
<RichText
|
||||
customElements={{ em: <em style={{ color: "#f05" }} /> }}
|
||||
text="<em>foo</em>"
|
||||
localization_id="text"
|
||||
/>,
|
||||
mockL10nWrapper({ text: "<em>foo</em>" })
|
||||
);
|
||||
|
||||
const localized = wrapper.find(Localized);
|
||||
assert.propertyVal(localized.props().elems.em.props.style, "color", "#f05");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue