@pushwoosh/dumb-components 1.1.222 → 1.1.223
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CLICK_MAP_BADGE_ATTRIBUTE, decorateClickMap, matchClickMapItems } from '../clickMap';
|
|
2
2
|
const PROMO = 'http://spartak.com/matchtowin';
|
|
3
3
|
const TICKETS = 'https://tickets.spartak.com/zones/18956';
|
|
4
|
+
const SHOP = 'https://shop.spartak.com/';
|
|
4
5
|
const HTML = `
|
|
5
6
|
<a href="${PROMO}?utm_source=email&utm_content=img"><img src="banner.png" alt=""></a>
|
|
6
7
|
<p>Подробности на <a href="${PROMO}?utm_source=email">странице акции</a>.</p>
|
|
@@ -8,12 +9,15 @@ const HTML = `
|
|
|
8
9
|
<a href="${TICKETS}?utm_source=email">Купить билеты</a>
|
|
9
10
|
<a href="${TICKETS}?utm_source=email">Купить билеты</a>
|
|
10
11
|
<a href="[% param.url_unsub %]">Отписаться</a>
|
|
12
|
+
<a data-pw-lid="aaaa11112222" href="${SHOP}">В магазин</a>
|
|
13
|
+
<a data-pw-lid="bbbb33334444" href="${SHOP}">В магазин</a>
|
|
11
14
|
`;
|
|
12
|
-
const item = (key, link, title, clicks) => ({
|
|
15
|
+
const item = (key, link, title, clicks, linkId) => ({
|
|
13
16
|
key,
|
|
14
17
|
link,
|
|
15
18
|
title,
|
|
16
|
-
clicks
|
|
19
|
+
clicks,
|
|
20
|
+
linkId
|
|
17
21
|
});
|
|
18
22
|
function createDocument() {
|
|
19
23
|
const frameDocument = document.implementation.createHTMLDocument('preview');
|
|
@@ -64,6 +68,33 @@ describe('matchClickMapItems', () => {
|
|
|
64
68
|
expect(placement.pooled).toEqual(['promo']);
|
|
65
69
|
expect(matches).toHaveLength(3);
|
|
66
70
|
});
|
|
71
|
+
it('tells identical anchors apart by the stamped link id', () => {
|
|
72
|
+
const {
|
|
73
|
+
matches,
|
|
74
|
+
placement
|
|
75
|
+
} = matchClickMapItems(createDocument(), [item('top', SHOP, 'В магазин', 40, 'aaaa11112222'), item('bottom', SHOP, 'В магазин', 9, 'bbbb33334444')]);
|
|
76
|
+
expect(placement).toEqual({
|
|
77
|
+
placed: ['top', 'bottom'],
|
|
78
|
+
pooled: [],
|
|
79
|
+
unplaced: []
|
|
80
|
+
});
|
|
81
|
+
expect(matches.map(({
|
|
82
|
+
anchor,
|
|
83
|
+
items: matched
|
|
84
|
+
}) => [anchor.getAttribute('data-pw-lid'), matched.map(row => row.key)])).toEqual([['aaaa11112222', ['top']], ['bbbb33334444', ['bottom']]]);
|
|
85
|
+
});
|
|
86
|
+
it('falls back to link and text when the template no longer carries the recorded link id', () => {
|
|
87
|
+
const {
|
|
88
|
+
matches,
|
|
89
|
+
placement
|
|
90
|
+
} = matchClickMapItems(createDocument(), [item('old', SHOP, 'В магазин', 3, 'cccc55556666'), item('unstamped', PROMO, 'странице акции', 1)]);
|
|
91
|
+
expect(placement).toEqual({
|
|
92
|
+
placed: ['unstamped'],
|
|
93
|
+
pooled: ['old'],
|
|
94
|
+
unplaced: []
|
|
95
|
+
});
|
|
96
|
+
expect(matches).toHaveLength(3);
|
|
97
|
+
});
|
|
67
98
|
it('reports rows whose link is not in the template as unplaced', () => {
|
|
68
99
|
const {
|
|
69
100
|
matches,
|
|
@@ -19,7 +19,9 @@ export type EmailClickMapDecorateOptions = {
|
|
|
19
19
|
};
|
|
20
20
|
/** Attribute every badge carries, so tests and consumers can find them. */
|
|
21
21
|
export declare const CLICK_MAP_BADGE_ATTRIBUTE = "data-pw-click-map-badge";
|
|
22
|
-
/**
|
|
22
|
+
/** Stable anchor id stamped into the template html on save; the click carries it as `linkId`. */
|
|
23
|
+
export declare const CLICK_MAP_LINK_ID_ATTRIBUTE = "data-pw-lid";
|
|
24
|
+
/** Lands click-tracking rows on template anchors by stamped `linkId`, else by (link, text), else every anchor of the link. */
|
|
23
25
|
export declare function matchClickMapItems(document: Document, items: EmailClickMapItem[]): EmailClickMapMatchResult;
|
|
24
26
|
/** Draws click-count badges over matched anchors, outlines `activeKey`; returns a cleanup. */
|
|
25
27
|
export declare function decorateClickMap(document: Document, options: EmailClickMapDecorateOptions): () => void;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Attribute every badge carries, so tests and consumers can find them. */
|
|
2
2
|
export const CLICK_MAP_BADGE_ATTRIBUTE = 'data-pw-click-map-badge';
|
|
3
|
+
/** Stable anchor id stamped into the template html on save; the click carries it as `linkId`. */
|
|
4
|
+
export const CLICK_MAP_LINK_ID_ATTRIBUTE = 'data-pw-lid';
|
|
3
5
|
const ACTIVE_COLOR = '#00b2ff';
|
|
4
6
|
const IDLE_COLOR = 'rgba(36, 48, 66, 0.82)';
|
|
5
7
|
const compactNumber = new Intl.NumberFormat('en', {
|
|
@@ -10,14 +12,19 @@ const stripQuery = url => url.split('?')[0].trim();
|
|
|
10
12
|
const normalizeText = text => text.replace(/\s+/g, ' ').trim();
|
|
11
13
|
const anchorText = anchor => normalizeText(anchor.textContent ?? '');
|
|
12
14
|
const anchorLink = anchor => stripQuery(anchor.getAttribute('href') ?? '');
|
|
13
|
-
/** Lands click-tracking rows on template anchors by (link, text),
|
|
15
|
+
/** Lands click-tracking rows on template anchors by stamped `linkId`, else by (link, text), else every anchor of the link. */
|
|
14
16
|
export function matchClickMapItems(document, items) {
|
|
15
17
|
const anchorsByLink = new Map();
|
|
18
|
+
const anchorsByLinkId = new Map();
|
|
16
19
|
document.querySelectorAll('a[href]').forEach(anchor => {
|
|
17
20
|
const link = anchorLink(anchor);
|
|
21
|
+
const linkId = anchor.getAttribute(CLICK_MAP_LINK_ID_ATTRIBUTE);
|
|
18
22
|
if (link) {
|
|
19
23
|
anchorsByLink.set(link, [...(anchorsByLink.get(link) ?? []), anchor]);
|
|
20
24
|
}
|
|
25
|
+
if (linkId) {
|
|
26
|
+
anchorsByLinkId.set(linkId, [...(anchorsByLinkId.get(linkId) ?? []), anchor]);
|
|
27
|
+
}
|
|
21
28
|
});
|
|
22
29
|
const itemsByAnchor = new Map();
|
|
23
30
|
const placement = {
|
|
@@ -25,11 +32,15 @@ export function matchClickMapItems(document, items) {
|
|
|
25
32
|
pooled: [],
|
|
26
33
|
unplaced: []
|
|
27
34
|
};
|
|
28
|
-
|
|
35
|
+
const targetsByLinkAndText = item => {
|
|
29
36
|
const candidates = anchorsByLink.get(stripQuery(item.link)) ?? [];
|
|
30
37
|
const title = normalizeText(item.title);
|
|
31
38
|
const exact = candidates.filter(anchor => anchorText(anchor) === title);
|
|
32
|
-
|
|
39
|
+
return exact.length > 0 ? exact : candidates;
|
|
40
|
+
};
|
|
41
|
+
items.forEach(item => {
|
|
42
|
+
const stamped = item.linkId ? anchorsByLinkId.get(item.linkId) : undefined;
|
|
43
|
+
const targets = stamped ?? targetsByLinkAndText(item);
|
|
33
44
|
if (targets.length === 0) {
|
|
34
45
|
placement.unplaced.push(item.key);
|
|
35
46
|
return;
|
|
@@ -6,6 +6,8 @@ export type EmailClickMapItem = {
|
|
|
6
6
|
link: string;
|
|
7
7
|
/** Anchor text recorded with the click; empty for image links. */
|
|
8
8
|
title: string;
|
|
9
|
+
/** Stable anchor id (`data-pw-lid`) recorded with the click; matched before link and text when set. */
|
|
10
|
+
linkId?: string;
|
|
9
11
|
clicks: number;
|
|
10
12
|
};
|
|
11
13
|
/** How the rows of {@link EmailClickMapProps.items} landed on the rendered template. */
|