Skip to content

Commit 5e92886

Browse files
committed
Add mobile blocked content placeholder to CTL
1 parent adfe2d6 commit 5e92886

File tree

3 files changed

+311
-10
lines changed

3 files changed

+311
-10
lines changed

src/features/click-to-load.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createCustomEvent, sendMessage, originalWindowDispatchEvent } from '../
22
import { logoImg, loadingImages, closeIcon } from './click-to-load/ctl-assets.js'
33
import { getStyles, getConfig } from './click-to-load/ctl-config.js'
44
import ContentFeature from '../content-feature.js'
5+
import { DDGCtlPlaceholderBlocked } from './click-to-load/components/ctl-placeholder-blocked.js'
56

67
/**
78
* @typedef {'darkMode' | 'lightMode' | 'loginMode' | 'cancelMode'} displayMode
@@ -47,6 +48,10 @@ const readyToDisplayPlaceholders = new Promise(resolve => {
4748
let afterPageLoadResolver
4849
const afterPageLoad = new Promise(resolve => { afterPageLoadResolver = resolve })
4950

51+
// Used to choose between extension/desktop flow or mobile apps flow.
52+
// Updated on ClickToLoad.init()
53+
let isMobileApp
54+
5055
/*********************************************************
5156
* Widget Replacement logic
5257
*********************************************************/
@@ -544,17 +549,32 @@ function createPlaceholderElementAndReplace (widget, trackingElement) {
544549

545550
// Facebook
546551
if (widget.replaceSettings.type === 'dialog') {
547-
const icon = widget.replaceSettings.icon
548-
const button = makeButton(widget.replaceSettings.buttonText, widget.getMode())
549-
const textButton = makeTextButton(widget.replaceSettings.buttonText, widget.getMode())
550-
const { contentBlock, shadowRoot } = createContentBlock(
551-
widget, button, textButton, icon
552-
)
553-
button.addEventListener('click', widget.clickFunction(trackingElement, contentBlock))
554-
textButton.addEventListener('click', widget.clickFunction(trackingElement, contentBlock))
552+
if (isMobileApp) {
553+
const mobileBlockedPlaceholder = new DDGCtlPlaceholderBlocked({
554+
fontFaceStyle: styles.fontStyle, // DDG font-face family
555+
devMode,
556+
title: widget.replaceSettings.infoTitle, // Card title text
557+
body: widget.replaceSettings.infoText, // Card body text
558+
unblockBtnText: widget.replaceSettings.buttonText, // Unblock button text
559+
useSlimCard: false, // Flag for using less padding on card (ie YT CTL on mobile)
560+
originalElement: trackingElement, // The original element this placeholder is replacing.
561+
sharedStrings, // Shared localized string
562+
onButtonClick: widget.clickFunction.bind(widget)
563+
})
564+
replaceTrackingElement(widget, trackingElement, mobileBlockedPlaceholder)
565+
} else {
566+
const icon = widget.replaceSettings.icon
567+
const button = makeButton(widget.replaceSettings.buttonText, widget.getMode())
568+
const textButton = makeTextButton(widget.replaceSettings.buttonText, widget.getMode())
569+
const { contentBlock, shadowRoot } = createContentBlock(
570+
widget, button, textButton, icon
571+
)
572+
button.addEventListener('click', widget.clickFunction(trackingElement, contentBlock))
573+
textButton.addEventListener('click', widget.clickFunction(trackingElement, contentBlock))
555574

556-
replaceTrackingElement(widget, trackingElement, contentBlock)
557-
showExtraUnblockIfShortPlaceholder(shadowRoot, contentBlock)
575+
replaceTrackingElement(widget, trackingElement, contentBlock)
576+
showExtraUnblockIfShortPlaceholder(shadowRoot, contentBlock)
577+
}
558578
}
559579

560580
// YouTube
@@ -1634,6 +1654,8 @@ export default class ClickToLoad extends ContentFeature {
16341654
sharedStrings = localizedConfig.sharedStrings
16351655
// update styles if asset config was sent
16361656
styles = getStyles(this.assetConfig)
1657+
this.isMobileApp = this.platform.name === 'ios' || this.platform.name === 'android'
1658+
isMobileApp = this.isMobileApp
16371659

16381660
for (const entity of Object.keys(config)) {
16391661
// Strip config entities that are first-party, or aren't enabled in the
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
* {
2+
font-family: DuckDuckGoPrivacyEssentials, system, -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto,
3+
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
4+
}
5+
6+
/* SHARED STYLES */
7+
/* Button */
8+
.DuckDuckGoButton {
9+
border-radius: 8px;
10+
box-sizing: border-box;
11+
padding: 11px 22px;
12+
margin: 0px auto;
13+
border-color: #3969ef;
14+
border: none;
15+
height: 36px;
16+
font-size: 14px;
17+
18+
position: relative;
19+
cursor: pointer;
20+
box-shadow: none;
21+
z-index: 2147483646;
22+
}
23+
.DuckDuckGoButton > div {
24+
display: flex;
25+
flex-direction: row;
26+
align-items: center;
27+
border: none;
28+
padding: 0;
29+
margin: 0;
30+
}
31+
/* TODO: DARK THEME
32+
* TODO: button states */
33+
.DuckDuckGoButton.tertiary {
34+
color: rgba(0, 0, 0, 0.84);
35+
background-color: transparent;
36+
display: flex;
37+
justify-content: center;
38+
align-items: center;
39+
padding: 0px 16px;
40+
border: 1px solid rgba(0, 0, 0, 0.12);
41+
border-radius: 8px;
42+
@media (prefers-color-scheme: dark) {
43+
color: #ffffff;
44+
border: 1px solid rgba(255, 255, 255, 0.24);
45+
}
46+
}
47+
.DuckDuckGoButton.tertiary:hover {
48+
background: rgba(0, 0, 0, 0.06);
49+
border: 1px solid rgba(0, 0, 0, 0.18);
50+
@media (prefers-color-scheme: dark) {
51+
background: rgba(255, 255, 255, 0.18);
52+
border: 1px solid rgba(255, 255, 255, 0.24);
53+
}
54+
}
55+
.DuckDuckGoButton.tertiary:active {
56+
background: rgba(0, 0, 0, 0.12);
57+
border: 1px solid rgba(0, 0, 0, 0.36);
58+
@media (prefers-color-scheme: dark) {
59+
background: rgba(255, 255, 255, 0.24);
60+
border: 1px solid rgba(255, 255, 255, 0.24);
61+
}
62+
}
63+
64+
/* Link styles */
65+
.ddg-text-link {
66+
line-height: 1.4;
67+
font-size: 14px;
68+
font-weight: 700;
69+
cursor: pointer;
70+
text-decoration: none;
71+
color: #3969ef;
72+
@media (prefers-color-scheme: dark) {
73+
color: #7295f6;
74+
}
75+
}
76+
77+
/* Styles for DDGCtlPlaceholderBlocked */
78+
.DuckDuckGoButton.ddg-ctl-unblock-btn {
79+
width: 100%;
80+
margin-top: 8px;
81+
@media (min-width: 480px) {
82+
width: auto;
83+
margin-top: 12px;
84+
}
85+
}
86+
87+
.ddg-ctl-placeholder-container {
88+
display: inline-block;
89+
border: 0;
90+
padding: 0;
91+
margin: 0;
92+
max-width: 600px;
93+
min-height: 200px;
94+
}
95+
96+
.ddg-ctl-placeholder-card {
97+
box-sizing: border-box;
98+
padding: 16px;
99+
color: rgba(0, 0, 0, 0.84);
100+
background: #ffffff;
101+
box-shadow: 0px 1px 0px rgba(125, 125, 125, 0.06), 0px 0px 0px 1px rgba(150, 150, 150, 0.3);
102+
border-radius: 12px;
103+
max-width: 600px;
104+
min-height: 200px;
105+
margin: auto;
106+
display: flex;
107+
flex-direction: column;
108+
justify-content: center;
109+
align-items: center;
110+
line-height: 1;
111+
@media (prefers-color-scheme: dark) {
112+
color: rgba(255, 255, 255, 0.84);
113+
background: #222222;
114+
box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.1), 0px 0px 0px 1px rgba(150, 150, 150, 0.3);
115+
}
116+
}
117+
118+
.ddg-ctl-placeholder-card.slim-card {
119+
padding: 12px;
120+
}
121+
122+
.ddg-ctl-placeholder-card-header {
123+
display: flex;
124+
align-items: center;
125+
@media (min-width: 480px) {
126+
flex-direction: column;
127+
align-items: center;
128+
justify-content: center;
129+
}
130+
}
131+
/* Show Learn More link in the header on mobile and
132+
* tablet size screens and hide it on desktop size */
133+
@media (min-width: 720px) {
134+
.ddg-ctl-placeholder-card-header .ddg-learn-more {
135+
display: none;
136+
visibility: hidden;
137+
}
138+
}
139+
140+
.ddg-ctl-placeholder-card-title,
141+
.ddg-ctl-placeholder-card-title .ddg-text-link {
142+
font-family: DuckDuckGoPrivacyEssentialsBold;
143+
font-weight: 700;
144+
font-size: 16px;
145+
line-height: 24px;
146+
@media (min-width: 480px) {
147+
text-align: center;
148+
}
149+
}
150+
151+
.ddg-ctl-placeholder-card-header-dax {
152+
align-self: flex-start;
153+
width: 48px;
154+
height: 48px;
155+
margin: 0 8px 0 0;
156+
@media (min-width: 480px) {
157+
align-self: inherit;
158+
margin: 0 0 12px 0;
159+
}
160+
@media (min-width: 720px) {
161+
width: 56px;
162+
height: 56px;
163+
}
164+
}
165+
166+
.ddg-ctl-placeholder-card-body {
167+
font-size: 14px;
168+
line-height: 21px;
169+
padding: 0px 40px;
170+
text-align: center;
171+
margin: 12px auto auto;
172+
display: none;
173+
visibility: hidden;
174+
@media (min-width: 720px) {
175+
display: block;
176+
visibility: visible;
177+
}
178+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { html } from '../../../dom-utils'
2+
import css from '../assets/ctl-placeholder-block.css'
3+
import { logoImg as daxImg } from '../ctl-assets'
4+
5+
export class DDGCtlPlaceholderBlocked extends HTMLElement {
6+
static CUSTOM_TAG_NAME = 'ddg-ctl-placeholder-blocked'
7+
8+
/**
9+
*
10+
* @param {{
11+
* fontFaceStyle: string, // DDG font-face styles
12+
* devMode: boolean,
13+
* title: string, // Card title text
14+
* body?: string, // Card body text
15+
* unblockBtnText: string, // Unblock button text
16+
* useSlimCard?: boolean, // Flag for using less padding on card (ie YT CTL on mobile)
17+
* originalElement: HTMLElement, // The original element this placeholder is replacing.
18+
* sharedStrings: {readAbout: string, learnMore: string}, // Shared localized string
19+
* onButtonClick: (originalElement: HTMLIFrameElement | HTMLElement, replacementElement: HTMLElement) => (e: any) => void,
20+
* }} params
21+
*/
22+
constructor (params) {
23+
super()
24+
this.params = params
25+
/**
26+
* Create the shadow root, closed to prevent any outside observers
27+
* @type {ShadowRoot}
28+
*/
29+
const shadow = this.attachShadow({
30+
mode: this.params.devMode ? 'open' : 'closed'
31+
})
32+
33+
/**
34+
* Add our styles
35+
* @type {HTMLStyleElement}
36+
*/
37+
const style = document.createElement('style')
38+
const { fontFaceStyle } = this.params
39+
style.innerText = (fontFaceStyle ?? '\n') + css
40+
41+
/**
42+
* Creates the placeholder for blocked content
43+
* @type {HTMLDivElement}
44+
*/
45+
const placeholderBlocked = this.createPlaceholder()
46+
47+
/**
48+
* Append both to the shadow root
49+
*/
50+
shadow.appendChild(placeholderBlocked)
51+
shadow.appendChild(style)
52+
}
53+
54+
/**
55+
* Creates a placeholder for content blocked by Click to Load.
56+
* @returns {HTMLDivElement}
57+
*/
58+
createPlaceholder () {
59+
const { title, body, unblockBtnText, useSlimCard, originalElement, onButtonClick } = this.params
60+
61+
const container = document.createElement('div')
62+
container.classList.add('ddg-ctl-placeholder-container')
63+
64+
const learnMoreLink = this.createLearnMoreLink()
65+
66+
container.innerHTML = html`<div
67+
class="DuckDuckGoSocialContainer ddg-ctl-placeholder-card ${useSlimCard ? 'slim-card' : ''}"
68+
>
69+
<div class="ddg-ctl-placeholder-card-header">
70+
<img class="ddg-ctl-placeholder-card-header-dax" src=${daxImg} />
71+
<div class="ddg-ctl-placeholder-card-title">${title}. ${learnMoreLink}</div>
72+
</div>
73+
${body ? html`<div class="ddg-ctl-placeholder-card-body">${body} ${learnMoreLink}</div>` : ''}
74+
<button class="DuckDuckGoButton tertiary ddg-ctl-unblock-btn"><div>${unblockBtnText}</div></button>
75+
</div>`.toString()
76+
77+
container
78+
.querySelector('button.ddg-ctl-unblock-btn')
79+
?.addEventListener('click', onButtonClick(originalElement, this))
80+
81+
return container
82+
}
83+
84+
/**
85+
* Creates a template string for Learn More link.
86+
*/
87+
createLearnMoreLink () {
88+
const { sharedStrings } = this.params
89+
90+
return html`<a
91+
class="ddg-text-link ddg-learn-more"
92+
aria-label="${sharedStrings.readAbout}"
93+
href="https://help.duckduckgo.com/duckduckgo-help-pages/privacy/embedded-content-protection/"
94+
target="_blank"
95+
>
96+
${sharedStrings.learnMore}
97+
</a>`
98+
}
99+
}
100+
101+
customElements.define(DDGCtlPlaceholderBlocked.CUSTOM_TAG_NAME, DDGCtlPlaceholderBlocked)

0 commit comments

Comments
 (0)