PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.14.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.14.0
2.14.0 2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 81 releases
ablocks / assets / js / link-passthrough.js

link-passthrough.js in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.14.0, at assets/js/link-passthrough.js

234 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * aBlocks — Query Param Passthrough (frontend).
3 *
4 * Reads tracked query params (ref, utm_*, …) from the current URL and appends
5 * whichever are present to every "tagged" link on the page. A link is tagged
6 * when it (or a wrapping block) carries the configured CSS class — so it works
7 * both when the class sits on the <a> and when Gutenberg puts it on the block
8 * wrapper with the <a> nested inside.
9 *
10 * Config is injected before this file via wp_add_inline_script as
11 * window.ABlocksLinkPassthrough = { keys: string[], linkClass: string }.
12 */
13 ( function () {
14 'use strict';
15
16 var config = window.ABlocksLinkPassthrough;
17 if ( ! config || ! Array.isArray( config.keys ) || ! config.keys.length ) {
18 return;
19 }
20 if (
21 typeof window.URL !== 'function' ||
22 typeof window.URLSearchParams !== 'function'
23 ) {
24 return; // Very old browser — bail rather than throw.
25 }
26
27 var mode = config.mode || 'class';
28 var linkClass = ( config.linkClass || 'aff-link' ).trim();
29 var keywords = ( Array.isArray( config.keywords ) ? config.keywords : [] )
30 .map( function ( word ) {
31 return String( word ).toLowerCase();
32 } )
33 .filter( Boolean );
34
35 // Nothing can match — bail before touching the DOM.
36 if ( mode === 'class' && ! linkClass ) {
37 return;
38 }
39 if ( mode === 'keyword' && ! keywords.length ) {
40 return;
41 }
42
43 var persist = config.persist !== false;
44 var cookieDays = parseInt( config.cookieDays, 10 );
45 if ( isNaN( cookieDays ) ) {
46 cookieDays = 30;
47 }
48 var COOKIE = 'ablocks_pt';
49
50 function readCookie( name ) {
51 var parts = ( document.cookie || '' ).split( '; ' );
52 for ( var i = 0; i < parts.length; i++ ) {
53 var eq = parts[ i ].indexOf( '=' );
54 if ( eq > -1 && parts[ i ].slice( 0, eq ) === name ) {
55 return decodeURIComponent( parts[ i ].slice( eq + 1 ) );
56 }
57 }
58 return '';
59 }
60
61 function writeCookie( name, value, days ) {
62 var expires = '';
63 if ( days > 0 ) {
64 var d = new Date();
65 d.setTime( d.getTime() + days * 24 * 60 * 60 * 1000 );
66 expires = '; expires=' + d.toUTCString();
67 }
68 document.cookie =
69 name +
70 '=' +
71 encodeURIComponent( value ) +
72 expires +
73 '; path=/; SameSite=Lax';
74 }
75
76 // Fresh values come from the current URL; remembered values come from the
77 // cookie. The URL always wins (a newer ?ref= overrides a stored one).
78 var current = new URLSearchParams( window.location.search );
79 var fresh = {};
80 config.keys.forEach( function ( key ) {
81 var value = current.get( key );
82 if ( value !== null && value !== '' ) {
83 fresh[ key ] = value;
84 }
85 } );
86
87 var stored = {};
88 if ( persist ) {
89 var raw = readCookie( COOKIE );
90 if ( raw ) {
91 try {
92 new URLSearchParams( raw ).forEach( function ( value, key ) {
93 if (
94 config.keys.indexOf( key ) !== -1 &&
95 value !== '' &&
96 fresh[ key ] === undefined
97 ) {
98 stored[ key ] = value;
99 }
100 } );
101 } catch ( e ) {}
102 }
103 }
104
105 // Effective set = remembered values overlaid by anything fresh in the URL.
106 var effective = {};
107 config.keys.forEach( function ( key ) {
108 if ( fresh[ key ] !== undefined ) {
109 effective[ key ] = fresh[ key ];
110 } else if ( stored[ key ] !== undefined ) {
111 effective[ key ] = stored[ key ];
112 }
113 } );
114
115 var present = Object.keys( effective ).map( function ( key ) {
116 return [ key, effective[ key ] ];
117 } );
118
119 if ( ! present.length ) {
120 return;
121 }
122
123 // Persist whenever the URL brought fresh params, so later pages that drop
124 // the query string can still recover them from the cookie.
125 if ( persist && Object.keys( fresh ).length ) {
126 var out = new URLSearchParams();
127 present.forEach( function ( pair ) {
128 out.set( pair[ 0 ], pair[ 1 ] );
129 } );
130 writeCookie( COOKIE, out.toString(), cookieDays );
131 }
132
133 // In 'class' mode, match the <a> directly or any <a> inside a tagged
134 // wrapper; other modes start from every link and filter per anchor.
135 var selector =
136 mode === 'class'
137 ? 'a.' + linkClass + ', .' + linkClass + ' a'
138 : 'a[href]';
139
140 function decorate( root ) {
141 var anchors;
142 try {
143 anchors = ( root || document ).querySelectorAll( selector );
144 } catch ( e ) {
145 return;
146 }
147
148 anchors.forEach( function ( anchor ) {
149 var href = anchor.getAttribute( 'href' );
150 if ( ! href ) {
151 return;
152 }
153
154 var lowered = href.toLowerCase().replace( /^\s+/, '' );
155 if (
156 lowered.charAt( 0 ) === '#' ||
157 lowered.indexOf( 'mailto:' ) === 0 ||
158 lowered.indexOf( 'tel:' ) === 0 ||
159 lowered.indexOf( 'javascript:' ) === 0 || // eslint-disable-line no-script-url
160 lowered.indexOf( 'data:' ) === 0
161 ) {
162 return;
163 }
164
165 var url;
166 try {
167 url = new URL( href, window.location.href );
168 } catch ( e ) {
169 return;
170 }
171
172 if ( url.protocol !== 'http:' && url.protocol !== 'https:' ) {
173 return;
174 }
175
176 // 'all' mode is same-site only, so the ref is never leaked to
177 // unrelated external domains.
178 if ( mode === 'all' && url.origin !== window.location.origin ) {
179 return;
180 }
181
182 // 'keyword' mode only touches links whose URL contains a word.
183 if ( mode === 'keyword' ) {
184 var haystack = url.href.toLowerCase();
185 var matched = keywords.some( function ( word ) {
186 return haystack.indexOf( word ) !== -1;
187 } );
188 if ( ! matched ) {
189 return;
190 }
191 }
192
193 var changed = false;
194 present.forEach( function ( pair ) {
195 if ( url.searchParams.get( pair[ 0 ] ) === pair[ 1 ] ) {
196 return; // Already carries this exact value.
197 }
198 url.searchParams.set( pair[ 0 ], pair[ 1 ] );
199 changed = true;
200 } );
201
202 if ( changed ) {
203 anchor.setAttribute( 'href', url.toString() );
204 }
205 } );
206 }
207
208 function run() {
209 decorate( document );
210
211 // Re-decorate links injected later (interactive blocks, AJAX content…).
212 if ( typeof window.MutationObserver === 'function' ) {
213 var observer = new MutationObserver( function ( mutations ) {
214 for ( var i = 0; i < mutations.length; i++ ) {
215 if ( mutations[ i ].addedNodes.length ) {
216 decorate( document );
217 return;
218 }
219 }
220 } );
221 observer.observe( document.body, {
222 childList: true,
223 subtree: true,
224 } );
225 }
226 }
227
228 if ( document.readyState === 'loading' ) {
229 document.addEventListener( 'DOMContentLoaded', run );
230 } else {
231 run();
232 }
233 } )();
234