PageSourceSearch

https://www.ncdor.gov/modules/contrib/linkpurpose/js/linkpurpose-drupal.js?tlsf9d

js ncdor.gov collected 2026-09-24 08:28:49 UTC 14,385 bytes, 356 lines download raw bytes

1/**
2 * Drupal initializer.
3 * Launch as behavior and pull variables from config.
4 */
5
6((Drupal, drupalSettings) => {
7  // Prevent multiple inits in modules that re-trigger the document context.
8  let linkPurposeOnce;
9
10  Drupal.behaviors.linkPurpose = {
11    attach: (context) => {
12      if (
13        context === document &&
14        !linkPurposeOnce &&
15        CSS.supports('selector(:is(body))')
16      ) {
17        // "Supports" selector drops old browsers.
18        linkPurposeOnce = true;
19
20        const invalidClass = /([^A-Za-z0-9\-_])+/g;
21        const invalidClasses = /([^A-Za-z0-9,\-_])+/g;
22
23        const sanitizeClass = (string, defaultString) => {
24          const cleanClass = string
25            ? string.replaceAll(invalidClass, '')
26            : false;
27          return cleanClass || defaultString;
28        };
29
30        const classArray = (source, defaults) => {
31          if (!source) {
32            return defaults;
33          }
34
35          // Normalize spaces to commas.
36          source = source.replaceAll(/(,*\s+)/g, ',');
37
38          // Sanitize
39          source = source.replaceAll(invalidClasses, '');
40
41          // Split and remove empties.
42          source = source.split(',').filter(Boolean);
43
44          return source.length > 0 ? source : defaults;
45        };
46
47        const queryCheck = document.createDocumentFragment();
48        const validateSelector = (string, fallback, selector) => {
49          if (string && string.trim()) {
50            // Selector exists
51            try {
52              queryCheck.querySelector(string);
53            } catch (e) {
54              console.warn(
55                `Invalid selector in Link Purpose Icons for "${selector}."`,
56              );
57              return fallback;
58            }
59            return string;
60          }
61          return fallback;
62        };
63
64        const ignores = validateSelector(
65          drupalSettings.linkpurpose.ignore,
66          '',
67          'Ignore elements',
68        );
69        let options = {
70          domain: drupalSettings.linkpurpose.domain,
71          roots: validateSelector(
72            drupalSettings.linkpurpose.roots,
73            'body',
74            'Check roots',
75          ),
76          shadowComponents: validateSelector(
77            drupalSettings.linkpurpose.shadowComponents,
78            '',
79            'Shadow components',
80          ),
81          shadowCSS: drupalSettings.linkpurpose.css_url
82            ? [
83                `${drupalSettings.linkpurpose.css_url}/library/css/linkpurpose.css`,
84              ]
85            : false,
86          ignore: ignores
87            ? `${ignores}, .ck-editor a, .form-textarea-wrapper a`
88            : '.ck-editor, .form-textarea-wrapper',
89          hideIcon: validateSelector(
90            drupalSettings.linkpurpose.hideIcon,
91            false,
92            'Hide icon on element',
93          ),
94          noRunIfPresent: validateSelector(
95            drupalSettings.linkpurpose.noRunIfPresent,
96            '#quickedit-entity-toolbar, .layout-builder-form',
97            'No run if present',
98          ),
99          noRunIfAbsent: validateSelector(
100            drupalSettings.linkpurpose.noRunIfAbsent,
101            '',
102            'No run if absent',
103          ),
104          baseSelector: validateSelector(
105            drupalSettings.linkpurpose.baseSelector,
106            'a[href]',
107            'Base selector',
108          ),
109          baseLinkClass: sanitizeClass(
110            drupalSettings.linkpurpose.baseLinkClass,
111            'link-purpose',
112          ),
113          baseIconWrapperClass: sanitizeClass(
114            drupalSettings.linkpurpose.baseIconWrapperClass,
115            'link-purpose-icon',
116          ),
117          noBreakClass: sanitizeClass(
118            drupalSettings.linkpurpose.noBreakClass,
119            'link-purpose-nobreak',
120          ),
121          noReferrer: !!drupalSettings.linkpurpose.purposeExternalNoReferrer,
122          noIconOnImages: !!drupalSettings.linkpurpose.noIconOnImages,
123          suppressNoBreak: validateSelector(
124            drupalSettings.linkpurpose.suppressNoBreak,
125            false,
126            'Suppress no-break',
127          ),
128          insertIconOutsideHiddenSpan: validateSelector(
129            drupalSettings.linkpurpose.insertIconOutsideHiddenSpan,
130            '.sr-only, .visually-hidden',
131            'Insert icon outside hidden span',
132          ),
133
134          purposes: {
135            external: {
136              selector: drupalSettings.linkpurpose.purposeExternal
137                ? '[href^="https://"], [href^="http://"], [href^="//"]'
138                : false,
139              additionalSelector: validateSelector(
140                drupalSettings.linkpurpose.purposeExternalSelector,
141                '',
142                'External link selector',
143              ),
144              newWindow: !!drupalSettings.linkpurpose.purposeExternalNewWindow,
145              message: drupalSettings.linkpurpose.purposeExternalMessage
146                ? drupalSettings.linkpurpose.purposeExternalMessage
147                : 'Link is external',
148              linkClass: sanitizeClass(
149                drupalSettings.linkpurpose.purposeExternalClass,
150                'link-purpose-external',
151              ),
152              iconWrapperClass: sanitizeClass(
153                drupalSettings.linkpurpose.purposeExternalIconWrapperClass,
154                'link-purpose-external-icon',
155              ),
156              iconType: drupalSettings.linkpurpose.purposeExternalIconType
157                ? drupalSettings.linkpurpose.purposeExternalIconType
158                : 'html',
159              iconPosition: drupalSettings.linkpurpose
160                .purposeExternalIconPosition
161                ? drupalSettings.linkpurpose.purposeExternalIconPosition
162                : 'beforeend',
163              iconClasses: classArray(
164                drupalSettings.linkpurpose.purposeExternalIconClasses,
165                ['fa-solid', 'fa-up-right-from-square'],
166              ),
167            },
168
169            document: {
170              selector: drupalSettings.linkpurpose.purposeDocument
171                ? validateSelector(
172                    drupalSettings.linkpurpose.purposeDocumentSelector,
173                    "[href^='/document/'], [href$='.pdf'], [href*='.pdf?'], [href$='.doc'], [href$='.docx'], [href*='.doc?'], [href*='.docx?'], [href$='.ppt'], [href$='.pptx'], [href*='.ppt?'], [href*='.pptx?'], [href^='https://docs.google']",
174                    'Document selector',
175                  )
176                : false,
177              message: drupalSettings.linkpurpose.purposeDocumentMessage
178                ? drupalSettings.linkpurpose.purposeDocumentMessage
179                : 'Link downloads document',
180              linkClass: sanitizeClass(
181                drupalSettings.linkpurpose.purposeDocumentClass,
182                'link-purpose-document',
183              ),
184              iconWrapperClass: sanitizeClass(
185                drupalSettings.linkpurpose.purposeDocumentIconWrapperClass,
186                'link-purpose-document-icon',
187              ),
188              iconType: drupalSettings.linkpurpose.purposeDocumentIconType
189                ? drupalSettings.linkpurpose.purposeDocumentIconType
190                : 'html',
191              iconPosition: drupalSettings.linkpurpose
192                .purposeDocumentIconPosition
193                ? drupalSettings.linkpurpose.purposeDocumentIconPosition
194                : 'beforeend',
195              iconClasses: classArray(
196                drupalSettings.linkpurpose.purposeDocumentIconClasses,
197                ['fa-regular', 'fa-file-lines'],
198              ),
199            },
200
201            download: {
202              selector: drupalSettings.linkpurpose.purposeDownload
203                ? validateSelector(
204                    drupalSettings.linkpurpose.purposeDownloadSelector,
205                    '[download]',
206                    'Download selector',
207                  )
208                : false,
209              message: drupalSettings.linkpurpose.purposeDownloadMessage
210                ? drupalSettings.linkpurpose.purposeDownloadMessage
211                : 'Link downloads file',
212              linkClass: sanitizeClass(
213                drupalSettings.linkpurpose.purposeDownloadClass,
214                'link-purpose-download',
215              ),
216              iconWrapperClass: sanitizeClass(
217                drupalSettings.linkpurpose.purposeDownloadIconWrapperClass,
218                'link-purpose-download-icon',
219              ),
220              iconType: drupalSettings.linkpurpose.purposeDownloadIconType
221                ? drupalSettings.linkpurpose.purposeDownloadIconType
222                : 'html',
223              iconPosition: drupalSettings.linkpurpose
224                .purposeDownloadIconPosition
225                ? drupalSettings.linkpurpose.purposeDownloadIconPosition
226                : 'beforeend',
227              iconClasses: classArray(
228                drupalSettings.linkpurpose.purposeDownloadIconClasses,
229                ['fa-regular', 'fa-download'],
230              ),
231            },
232
233            app: {
234              selector: drupalSettings.linkpurpose.purposeApp
235                ? validateSelector(
236                    drupalSettings.linkpurpose.purposeAppSelector,
237                    ':is([href*="://"]):not([href^="/"], [href^="https:"], [href^="http:"], [href^="file:"])',
238                    'App selector',
239                  )
240                : false,
241              message: drupalSettings.linkpurpose.purposeAppMessage
242                ? drupalSettings.linkpurpose.purposeAppMessage
243                : 'Link opens app',
244              linkClass: sanitizeClass(
245                drupalSettings.linkpurpose.purposeAppClass,
246                'link-purpose-app',
247              ),
248              iconWrapperClass: sanitizeClass(
249                drupalSettings.linkpurpose.purposeAppIconWrapperClass,
250                'link-purpose-app-icon',
251              ),
252              iconType: drupalSettings.linkpurpose.purposeAppIconType
253                ? drupalSettings.linkpurpose.purposeAppIconType
254                : 'html',
255              iconPosition: drupalSettings.linkpurpose.purposeAppIconPosition
256                ? drupalSettings.linkpurpose.purposeAppIconPosition
257                : 'beforeend',
258              iconClasses: classArray(
259                drupalSettings.linkpurpose.purposeAppIconClasses,
260                ['fa-regular', 'fa-window-restore'],
261              ),
262            },
263
264            mail: {
265              selector: drupalSettings.linkpurpose.purposeMail
266                ? '[href^="mailto:"]'
267                : false,
268              message: drupalSettings.linkpurpose.purposeMailMessage
269                ? drupalSettings.linkpurpose.purposeMailMessage
270                : 'Link opens email app',
271              linkClass: sanitizeClass(
272                drupalSettings.linkpurpose.purposeMailClass,
273                'link-purpose-mailto',
274              ),
275              iconWrapperClass: sanitizeClass(
276                drupalSettings.linkpurpose.purposeMailIconWrapperClass,
277                'link-purpose-mail-icon',
278              ),
279              iconType: drupalSettings.linkpurpose.purposeMailIconType
280                ? drupalSettings.linkpurpose.purposeMailIconType
281                : 'html',
282              iconPosition: drupalSettings.linkpurpose.purposeMailIconPosition
283                ? drupalSettings.linkpurpose.purposeMailIconPosition
284                : 'beforeend',
285              iconClasses: classArray(
286                drupalSettings.linkpurpose.purposeMailIconClasses,
287                ['fa-regular', 'fa-envelope'],
288              ),
289            },
290
291            newWindow: {
292              selector: drupalSettings.linkpurpose.purposeNewWindow
293                ? '[target="_blank"]'
294                : false,
295              message: drupalSettings.linkpurpose.purposeNewWindowMessage
296                ? drupalSettings.linkpurpose.purposeNewWindowMessage
297                : 'Link opens phone app',
298              linkClass: sanitizeClass(
299                drupalSettings.linkpurpose.purposeNewWindowClass,
300                'link-purpose-window',
301              ),
302              iconWrapperClass: sanitizeClass(
303                drupalSettings.linkpurpose.purposeNewWindowIconWrapperClass,
304                'link-purpose-window-icon',
305              ),
306              iconType: drupalSettings.linkpurpose.purposeNewWindowIconType
307                ? drupalSettings.linkpurpose.purposeNewWindowIconType
308                : 'html', // html, src or classes
309              iconPosition: drupalSettings.linkpurpose
310                .purposeNewWindowIconPosition
311                ? drupalSettings.linkpurpose.purposeNewWindowIconPosition
312                : 'beforeend',
313              iconClasses: classArray(
314                drupalSettings.linkpurpose.purposeNewWindowIconClasses,
315                ['fa-regular', 'fa-window-restore'],
316              ),
317            },
318
319            tel: {
320              selector: drupalSettings.linkpurpose.purposeTel
321                ? '[href^="tel:"]'
322                : false,
323              message: drupalSettings.linkpurpose.purposeTelMessage
324                ? drupalSettings.linkpurpose.purposeTelMessage
325                : 'Link opens in new window',
326              linkClass: sanitizeClass(
327                drupalSettings.linkpurpose.purposeTelClass,
328                'link-purpose-tel',
329              ),
330              iconWrapperClass: sanitizeClass(
331                drupalSettings.linkpurpose.purposeTelIconWrapperClass,
332                'link-purpose-tel-icon',
333              ),
334              iconType: drupalSettings.linkpurpose.purposeTelIconType
335                ? drupalSettings.linkpurpose.purposeTelIconType
336                : 'html', // html, src or classes
337              iconPosition: drupalSettings.linkpurpose.purposeTelIconPosition
338                ? drupalSettings.linkpurpose.purposeTelIconPosition
339                : 'beforeend',
340              iconClasses: classArray(
341                drupalSettings.linkpurpose.purposeTelIconClasses,
342                ['fa-solid', 'fa-square-phone'],
343              ),
344            },
345          },
346        };
347
348        if (typeof linkPurposeOptionsOverride !== 'undefined') { // eslint-disable-line
349          options = linkPurposeOptions(options); // eslint-disable-line
350        }
351
352        const linkPurpose = new LinkPurpose(options); //eslint-disable-line
353      }
354    },
355  };
356})(Drupal, drupalSettings); // eslint-disable-line no-undef

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.