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));
|
return ASRouterUtils.sendMessage(ac.ASRouterUserEvent(ping));
|
||||||
},
|
},
|
||||||
getPreviewEndpoint() {
|
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;
|
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
|
// AS Router
|
||||||
@import '../asrouter/components/Button/Button';
|
@import '../asrouter/components/Button/Button';
|
||||||
@import '../asrouter/components/SnippetBase/SnippetBase';
|
|
||||||
@import '../asrouter/components/ModalOverlay/ModalOverlay';
|
@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
|
// 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-status-error: #{$red-60};
|
||||||
--newtab-inner-box-shadow-color: #{$black-10};
|
--newtab-inner-box-shadow-color: #{$black-10};
|
||||||
--newtab-overlay-color: color-mix(in srgb, var(--newtab-background-color) 85%, transparent);
|
--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));
|
@include textbox-focus(var(--newtab-primary-action-background));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ input {
|
||||||
--newtab-status-error: #D70022;
|
--newtab-status-error: #D70022;
|
||||||
--newtab-inner-box-shadow-color: rgba(0, 0, 0, 0.1);
|
--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-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-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-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;
|
--newtab-button-secondary-color: inherit;
|
||||||
|
|
@ -4136,105 +4134,6 @@ main.has-snippet {
|
||||||
transition: box-shadow 150ms;
|
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 {
|
.activity-stream.modal-open {
|
||||||
overflow: hidden;
|
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);
|
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||||
transition: box-shadow 150ms;
|
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-status-error: #D70022;
|
||||||
--newtab-inner-box-shadow-color: rgba(0, 0, 0, 0.1);
|
--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-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-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-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;
|
--newtab-button-secondary-color: inherit;
|
||||||
|
|
@ -4140,105 +4138,6 @@ main.has-snippet {
|
||||||
transition: box-shadow 150ms;
|
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 {
|
.activity-stream.modal-open {
|
||||||
overflow: hidden;
|
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);
|
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||||
transition: box-shadow 150ms;
|
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-status-error: #D70022;
|
||||||
--newtab-inner-box-shadow-color: rgba(0, 0, 0, 0.1);
|
--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-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-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-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;
|
--newtab-button-secondary-color: inherit;
|
||||||
|
|
@ -4136,105 +4134,6 @@ main.has-snippet {
|
||||||
transition: box-shadow 150ms;
|
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 {
|
.activity-stream.modal-open {
|
||||||
overflow: hidden;
|
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);
|
box-shadow: 0 0 0 5px var(--newtab-element-secondary-color);
|
||||||
transition: box-shadow 150ms;
|
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() {
|
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;
|
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,
|
statements: 66,
|
||||||
lines: 66,
|
lines: 66,
|
||||||
functions: 78,
|
functions: 78,
|
||||||
branches: 63,
|
branches: 50,
|
||||||
},
|
},
|
||||||
"lib/TelemetryFeed.jsm": {
|
"lib/TelemetryFeed.jsm": {
|
||||||
statements: 98,
|
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