Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * Defines the Drupal JavaScript API.
|
Chris@0
|
4 */
|
Chris@0
|
5
|
Chris@0
|
6 /**
|
Chris@0
|
7 * A jQuery object, typically the return value from a `$(selector)` call.
|
Chris@0
|
8 *
|
Chris@0
|
9 * Holds an HTMLElement or a collection of HTMLElements.
|
Chris@0
|
10 *
|
Chris@0
|
11 * @typedef {object} jQuery
|
Chris@0
|
12 *
|
Chris@0
|
13 * @prop {number} length=0
|
Chris@0
|
14 * Number of elements contained in the jQuery object.
|
Chris@0
|
15 */
|
Chris@0
|
16
|
Chris@0
|
17 /**
|
Chris@0
|
18 * Variable generated by Drupal that holds all translated strings from PHP.
|
Chris@0
|
19 *
|
Chris@0
|
20 * Content of this variable is automatically created by Drupal when using the
|
Chris@0
|
21 * Interface Translation module. It holds the translation of strings used on
|
Chris@0
|
22 * the page.
|
Chris@0
|
23 *
|
Chris@0
|
24 * This variable is used to pass data from the backend to the frontend. Data
|
Chris@0
|
25 * contained in `drupalSettings` is used during behavior initialization.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @global
|
Chris@0
|
28 *
|
Chris@0
|
29 * @var {object} drupalTranslations
|
Chris@0
|
30 */
|
Chris@0
|
31
|
Chris@0
|
32 /**
|
Chris@0
|
33 * Global Drupal object.
|
Chris@0
|
34 *
|
Chris@0
|
35 * All Drupal JavaScript APIs are contained in this namespace.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @global
|
Chris@0
|
38 *
|
Chris@0
|
39 * @namespace
|
Chris@0
|
40 */
|
Chris@0
|
41 window.Drupal = { behaviors: {}, locale: {} };
|
Chris@0
|
42
|
Chris@0
|
43 // JavaScript should be made compatible with libraries other than jQuery by
|
Chris@0
|
44 // wrapping it in an anonymous closure.
|
Chris@17
|
45 (function(Drupal, drupalSettings, drupalTranslations) {
|
Chris@0
|
46 /**
|
Chris@0
|
47 * Helper to rethrow errors asynchronously.
|
Chris@0
|
48 *
|
Chris@0
|
49 * This way Errors bubbles up outside of the original callstack, making it
|
Chris@0
|
50 * easier to debug errors in the browser.
|
Chris@0
|
51 *
|
Chris@0
|
52 * @param {Error|string} error
|
Chris@0
|
53 * The error to be thrown.
|
Chris@0
|
54 */
|
Chris@17
|
55 Drupal.throwError = function(error) {
|
Chris@0
|
56 setTimeout(() => {
|
Chris@0
|
57 throw error;
|
Chris@0
|
58 }, 0);
|
Chris@0
|
59 };
|
Chris@0
|
60
|
Chris@0
|
61 /**
|
Chris@0
|
62 * Custom error thrown after attach/detach if one or more behaviors failed.
|
Chris@0
|
63 * Initializes the JavaScript behaviors for page loads and Ajax requests.
|
Chris@0
|
64 *
|
Chris@0
|
65 * @callback Drupal~behaviorAttach
|
Chris@0
|
66 *
|
Chris@0
|
67 * @param {HTMLDocument|HTMLElement} context
|
Chris@0
|
68 * An element to detach behaviors from.
|
Chris@0
|
69 * @param {?object} settings
|
Chris@0
|
70 * An object containing settings for the current context. It is rarely used.
|
Chris@0
|
71 *
|
Chris@0
|
72 * @see Drupal.attachBehaviors
|
Chris@0
|
73 */
|
Chris@0
|
74
|
Chris@0
|
75 /**
|
Chris@0
|
76 * Reverts and cleans up JavaScript behavior initialization.
|
Chris@0
|
77 *
|
Chris@0
|
78 * @callback Drupal~behaviorDetach
|
Chris@0
|
79 *
|
Chris@0
|
80 * @param {HTMLDocument|HTMLElement} context
|
Chris@0
|
81 * An element to attach behaviors to.
|
Chris@0
|
82 * @param {object} settings
|
Chris@0
|
83 * An object containing settings for the current context.
|
Chris@0
|
84 * @param {string} trigger
|
Chris@0
|
85 * One of `'unload'`, `'move'`, or `'serialize'`.
|
Chris@0
|
86 *
|
Chris@0
|
87 * @see Drupal.detachBehaviors
|
Chris@0
|
88 */
|
Chris@0
|
89
|
Chris@0
|
90 /**
|
Chris@0
|
91 * @typedef {object} Drupal~behavior
|
Chris@0
|
92 *
|
Chris@0
|
93 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
94 * Function run on page load and after an Ajax call.
|
Chris@0
|
95 * @prop {Drupal~behaviorDetach} detach
|
Chris@0
|
96 * Function run when content is serialized or removed from the page.
|
Chris@0
|
97 */
|
Chris@0
|
98
|
Chris@0
|
99 /**
|
Chris@0
|
100 * Holds all initialization methods.
|
Chris@0
|
101 *
|
Chris@0
|
102 * @namespace Drupal.behaviors
|
Chris@0
|
103 *
|
Chris@0
|
104 * @type {Object.<string, Drupal~behavior>}
|
Chris@0
|
105 */
|
Chris@0
|
106
|
Chris@0
|
107 /**
|
Chris@0
|
108 * Defines a behavior to be run during attach and detach phases.
|
Chris@0
|
109 *
|
Chris@0
|
110 * Attaches all registered behaviors to a page element.
|
Chris@0
|
111 *
|
Chris@0
|
112 * Behaviors are event-triggered actions that attach to page elements,
|
Chris@0
|
113 * enhancing default non-JavaScript UIs. Behaviors are registered in the
|
Chris@0
|
114 * {@link Drupal.behaviors} object using the method 'attach' and optionally
|
Chris@0
|
115 * also 'detach'.
|
Chris@0
|
116 *
|
Chris@0
|
117 * {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event
|
Chris@0
|
118 * and therefore runs on initial page load. Developers implementing Ajax in
|
Chris@0
|
119 * their solutions should also call this function after new page content has
|
Chris@0
|
120 * been loaded, feeding in an element to be processed, in order to attach all
|
Chris@0
|
121 * behaviors to the new content.
|
Chris@0
|
122 *
|
Chris@0
|
123 * Behaviors should use `var elements =
|
Chris@0
|
124 * $(context).find(selector).once('behavior-name');` to ensure the behavior is
|
Chris@0
|
125 * attached only once to a given element. (Doing so enables the reprocessing
|
Chris@0
|
126 * of given elements, which may be needed on occasion despite the ability to
|
Chris@0
|
127 * limit behavior attachment to a particular element.)
|
Chris@0
|
128 *
|
Chris@0
|
129 * @example
|
Chris@0
|
130 * Drupal.behaviors.behaviorName = {
|
Chris@0
|
131 * attach: function (context, settings) {
|
Chris@0
|
132 * // ...
|
Chris@0
|
133 * },
|
Chris@0
|
134 * detach: function (context, settings, trigger) {
|
Chris@0
|
135 * // ...
|
Chris@0
|
136 * }
|
Chris@0
|
137 * };
|
Chris@0
|
138 *
|
Chris@0
|
139 * @param {HTMLDocument|HTMLElement} [context=document]
|
Chris@0
|
140 * An element to attach behaviors to.
|
Chris@0
|
141 * @param {object} [settings=drupalSettings]
|
Chris@0
|
142 * An object containing settings for the current context. If none is given,
|
Chris@0
|
143 * the global {@link drupalSettings} object is used.
|
Chris@0
|
144 *
|
Chris@0
|
145 * @see Drupal~behaviorAttach
|
Chris@0
|
146 * @see Drupal.detachBehaviors
|
Chris@0
|
147 *
|
Chris@0
|
148 * @throws {Drupal~DrupalBehaviorError}
|
Chris@0
|
149 */
|
Chris@17
|
150 Drupal.attachBehaviors = function(context, settings) {
|
Chris@0
|
151 context = context || document;
|
Chris@0
|
152 settings = settings || drupalSettings;
|
Chris@0
|
153 const behaviors = Drupal.behaviors;
|
Chris@0
|
154 // Execute all of them.
|
Chris@17
|
155 Object.keys(behaviors || {}).forEach(i => {
|
Chris@14
|
156 if (typeof behaviors[i].attach === 'function') {
|
Chris@0
|
157 // Don't stop the execution of behaviors in case of an error.
|
Chris@0
|
158 try {
|
Chris@0
|
159 behaviors[i].attach(context, settings);
|
Chris@17
|
160 } catch (e) {
|
Chris@0
|
161 Drupal.throwError(e);
|
Chris@0
|
162 }
|
Chris@0
|
163 }
|
Chris@14
|
164 });
|
Chris@0
|
165 };
|
Chris@0
|
166
|
Chris@0
|
167 /**
|
Chris@0
|
168 * Detaches registered behaviors from a page element.
|
Chris@0
|
169 *
|
Chris@0
|
170 * Developers implementing Ajax in their solutions should call this function
|
Chris@0
|
171 * before page content is about to be removed, feeding in an element to be
|
Chris@0
|
172 * processed, in order to allow special behaviors to detach from the content.
|
Chris@0
|
173 *
|
Chris@0
|
174 * Such implementations should use `.findOnce()` and `.removeOnce()` to find
|
Chris@0
|
175 * elements with their corresponding `Drupal.behaviors.behaviorName.attach`
|
Chris@0
|
176 * implementation, i.e. `.removeOnce('behaviorName')`, to ensure the behavior
|
Chris@0
|
177 * is detached only from previously processed elements.
|
Chris@0
|
178 *
|
Chris@0
|
179 * @param {HTMLDocument|HTMLElement} [context=document]
|
Chris@0
|
180 * An element to detach behaviors from.
|
Chris@0
|
181 * @param {object} [settings=drupalSettings]
|
Chris@0
|
182 * An object containing settings for the current context. If none given,
|
Chris@0
|
183 * the global {@link drupalSettings} object is used.
|
Chris@0
|
184 * @param {string} [trigger='unload']
|
Chris@0
|
185 * A string containing what's causing the behaviors to be detached. The
|
Chris@0
|
186 * possible triggers are:
|
Chris@0
|
187 * - `'unload'`: The context element is being removed from the DOM.
|
Chris@0
|
188 * - `'move'`: The element is about to be moved within the DOM (for example,
|
Chris@0
|
189 * during a tabledrag row swap). After the move is completed,
|
Chris@0
|
190 * {@link Drupal.attachBehaviors} is called, so that the behavior can undo
|
Chris@0
|
191 * whatever it did in response to the move. Many behaviors won't need to
|
Chris@0
|
192 * do anything simply in response to the element being moved, but because
|
Chris@0
|
193 * IFRAME elements reload their "src" when being moved within the DOM,
|
Chris@0
|
194 * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
|
Chris@0
|
195 * take some action.
|
Chris@0
|
196 * - `'serialize'`: When an Ajax form is submitted, this is called with the
|
Chris@0
|
197 * form as the context. This provides every behavior within the form an
|
Chris@0
|
198 * opportunity to ensure that the field elements have correct content
|
Chris@0
|
199 * in them before the form is serialized. The canonical use-case is so
|
Chris@0
|
200 * that WYSIWYG editors can update the hidden textarea to which they are
|
Chris@0
|
201 * bound.
|
Chris@0
|
202 *
|
Chris@0
|
203 * @throws {Drupal~DrupalBehaviorError}
|
Chris@0
|
204 *
|
Chris@0
|
205 * @see Drupal~behaviorDetach
|
Chris@0
|
206 * @see Drupal.attachBehaviors
|
Chris@0
|
207 */
|
Chris@17
|
208 Drupal.detachBehaviors = function(context, settings, trigger) {
|
Chris@0
|
209 context = context || document;
|
Chris@0
|
210 settings = settings || drupalSettings;
|
Chris@0
|
211 trigger = trigger || 'unload';
|
Chris@0
|
212 const behaviors = Drupal.behaviors;
|
Chris@0
|
213 // Execute all of them.
|
Chris@17
|
214 Object.keys(behaviors || {}).forEach(i => {
|
Chris@14
|
215 if (typeof behaviors[i].detach === 'function') {
|
Chris@0
|
216 // Don't stop the execution of behaviors in case of an error.
|
Chris@0
|
217 try {
|
Chris@0
|
218 behaviors[i].detach(context, settings, trigger);
|
Chris@17
|
219 } catch (e) {
|
Chris@0
|
220 Drupal.throwError(e);
|
Chris@0
|
221 }
|
Chris@0
|
222 }
|
Chris@14
|
223 });
|
Chris@0
|
224 };
|
Chris@0
|
225
|
Chris@0
|
226 /**
|
Chris@0
|
227 * Encodes special characters in a plain-text string for display as HTML.
|
Chris@0
|
228 *
|
Chris@0
|
229 * @param {string} str
|
Chris@0
|
230 * The string to be encoded.
|
Chris@0
|
231 *
|
Chris@0
|
232 * @return {string}
|
Chris@0
|
233 * The encoded string.
|
Chris@0
|
234 *
|
Chris@0
|
235 * @ingroup sanitization
|
Chris@0
|
236 */
|
Chris@17
|
237 Drupal.checkPlain = function(str) {
|
Chris@17
|
238 str = str
|
Chris@17
|
239 .toString()
|
Chris@0
|
240 .replace(/&/g, '&')
|
Chris@12
|
241 .replace(/</g, '<')
|
Chris@12
|
242 .replace(/>/g, '>')
|
Chris@0
|
243 .replace(/"/g, '"')
|
Chris@12
|
244 .replace(/'/g, ''');
|
Chris@0
|
245 return str;
|
Chris@0
|
246 };
|
Chris@0
|
247
|
Chris@0
|
248 /**
|
Chris@0
|
249 * Replaces placeholders with sanitized values in a string.
|
Chris@0
|
250 *
|
Chris@0
|
251 * @param {string} str
|
Chris@0
|
252 * A string with placeholders.
|
Chris@0
|
253 * @param {object} args
|
Chris@0
|
254 * An object of replacements pairs to make. Incidences of any key in this
|
Chris@0
|
255 * array are replaced with the corresponding value. Based on the first
|
Chris@0
|
256 * character of the key, the value is escaped and/or themed:
|
Chris@0
|
257 * - `'!variable'`: inserted as is.
|
Chris@0
|
258 * - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}).
|
Chris@0
|
259 * - `'%variable'`: escape text and theme as a placeholder for user-
|
Chris@0
|
260 * submitted content ({@link Drupal.checkPlain} +
|
Chris@0
|
261 * `{@link Drupal.theme}('placeholder')`).
|
Chris@0
|
262 *
|
Chris@0
|
263 * @return {string}
|
Chris@0
|
264 * The formatted string.
|
Chris@0
|
265 *
|
Chris@0
|
266 * @see Drupal.t
|
Chris@0
|
267 */
|
Chris@17
|
268 Drupal.formatString = function(str, args) {
|
Chris@0
|
269 // Keep args intact.
|
Chris@0
|
270 const processedArgs = {};
|
Chris@0
|
271 // Transform arguments before inserting them.
|
Chris@17
|
272 Object.keys(args || {}).forEach(key => {
|
Chris@14
|
273 switch (key.charAt(0)) {
|
Chris@14
|
274 // Escaped only.
|
Chris@14
|
275 case '@':
|
Chris@14
|
276 processedArgs[key] = Drupal.checkPlain(args[key]);
|
Chris@14
|
277 break;
|
Chris@0
|
278
|
Chris@14
|
279 // Pass-through.
|
Chris@14
|
280 case '!':
|
Chris@14
|
281 processedArgs[key] = args[key];
|
Chris@14
|
282 break;
|
Chris@0
|
283
|
Chris@14
|
284 // Escaped and placeholder.
|
Chris@14
|
285 default:
|
Chris@14
|
286 processedArgs[key] = Drupal.theme('placeholder', args[key]);
|
Chris@14
|
287 break;
|
Chris@0
|
288 }
|
Chris@14
|
289 });
|
Chris@0
|
290
|
Chris@0
|
291 return Drupal.stringReplace(str, processedArgs, null);
|
Chris@0
|
292 };
|
Chris@0
|
293
|
Chris@0
|
294 /**
|
Chris@0
|
295 * Replaces substring.
|
Chris@0
|
296 *
|
Chris@0
|
297 * The longest keys will be tried first. Once a substring has been replaced,
|
Chris@0
|
298 * its new value will not be searched again.
|
Chris@0
|
299 *
|
Chris@0
|
300 * @param {string} str
|
Chris@0
|
301 * A string with placeholders.
|
Chris@0
|
302 * @param {object} args
|
Chris@0
|
303 * Key-value pairs.
|
Chris@0
|
304 * @param {Array|null} keys
|
Chris@0
|
305 * Array of keys from `args`. Internal use only.
|
Chris@0
|
306 *
|
Chris@0
|
307 * @return {string}
|
Chris@0
|
308 * The replaced string.
|
Chris@0
|
309 */
|
Chris@17
|
310 Drupal.stringReplace = function(str, args, keys) {
|
Chris@0
|
311 if (str.length === 0) {
|
Chris@0
|
312 return str;
|
Chris@0
|
313 }
|
Chris@0
|
314
|
Chris@0
|
315 // If the array of keys is not passed then collect the keys from the args.
|
Chris@0
|
316 if (!Array.isArray(keys)) {
|
Chris@14
|
317 keys = Object.keys(args || {});
|
Chris@0
|
318
|
Chris@0
|
319 // Order the keys by the character length. The shortest one is the first.
|
Chris@0
|
320 keys.sort((a, b) => a.length - b.length);
|
Chris@0
|
321 }
|
Chris@0
|
322
|
Chris@0
|
323 if (keys.length === 0) {
|
Chris@0
|
324 return str;
|
Chris@0
|
325 }
|
Chris@0
|
326
|
Chris@0
|
327 // Take next longest one from the end.
|
Chris@0
|
328 const key = keys.pop();
|
Chris@0
|
329 const fragments = str.split(key);
|
Chris@0
|
330
|
Chris@0
|
331 if (keys.length) {
|
Chris@0
|
332 for (let i = 0; i < fragments.length; i++) {
|
Chris@0
|
333 // Process each fragment with a copy of remaining keys.
|
Chris@0
|
334 fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0));
|
Chris@0
|
335 }
|
Chris@0
|
336 }
|
Chris@0
|
337
|
Chris@0
|
338 return fragments.join(args[key]);
|
Chris@0
|
339 };
|
Chris@0
|
340
|
Chris@0
|
341 /**
|
Chris@0
|
342 * Translates strings to the page language, or a given language.
|
Chris@0
|
343 *
|
Chris@0
|
344 * See the documentation of the server-side t() function for further details.
|
Chris@0
|
345 *
|
Chris@0
|
346 * @param {string} str
|
Chris@0
|
347 * A string containing the English text to translate.
|
Chris@0
|
348 * @param {Object.<string, string>} [args]
|
Chris@0
|
349 * An object of replacements pairs to make after translation. Incidences
|
Chris@0
|
350 * of any key in this array are replaced with the corresponding value.
|
Chris@0
|
351 * See {@link Drupal.formatString}.
|
Chris@0
|
352 * @param {object} [options]
|
Chris@0
|
353 * Additional options for translation.
|
Chris@0
|
354 * @param {string} [options.context='']
|
Chris@0
|
355 * The context the source string belongs to.
|
Chris@0
|
356 *
|
Chris@0
|
357 * @return {string}
|
Chris@0
|
358 * The formatted string.
|
Chris@0
|
359 * The translated string.
|
Chris@0
|
360 */
|
Chris@17
|
361 Drupal.t = function(str, args, options) {
|
Chris@0
|
362 options = options || {};
|
Chris@0
|
363 options.context = options.context || '';
|
Chris@0
|
364
|
Chris@0
|
365 // Fetch the localized version of the string.
|
Chris@17
|
366 if (
|
Chris@17
|
367 typeof drupalTranslations !== 'undefined' &&
|
Chris@17
|
368 drupalTranslations.strings &&
|
Chris@17
|
369 drupalTranslations.strings[options.context] &&
|
Chris@17
|
370 drupalTranslations.strings[options.context][str]
|
Chris@17
|
371 ) {
|
Chris@0
|
372 str = drupalTranslations.strings[options.context][str];
|
Chris@0
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 if (args) {
|
Chris@0
|
376 str = Drupal.formatString(str, args);
|
Chris@0
|
377 }
|
Chris@0
|
378 return str;
|
Chris@0
|
379 };
|
Chris@0
|
380
|
Chris@0
|
381 /**
|
Chris@0
|
382 * Returns the URL to a Drupal page.
|
Chris@0
|
383 *
|
Chris@0
|
384 * @param {string} path
|
Chris@0
|
385 * Drupal path to transform to URL.
|
Chris@0
|
386 *
|
Chris@0
|
387 * @return {string}
|
Chris@0
|
388 * The full URL.
|
Chris@0
|
389 */
|
Chris@17
|
390 Drupal.url = function(path) {
|
Chris@0
|
391 return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path;
|
Chris@0
|
392 };
|
Chris@0
|
393
|
Chris@0
|
394 /**
|
Chris@0
|
395 * Returns the passed in URL as an absolute URL.
|
Chris@0
|
396 *
|
Chris@0
|
397 * @param {string} url
|
Chris@0
|
398 * The URL string to be normalized to an absolute URL.
|
Chris@0
|
399 *
|
Chris@0
|
400 * @return {string}
|
Chris@0
|
401 * The normalized, absolute URL.
|
Chris@0
|
402 *
|
Chris@0
|
403 * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
|
Chris@0
|
404 * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript
|
Chris@0
|
405 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
|
Chris@0
|
406 */
|
Chris@17
|
407 Drupal.url.toAbsolute = function(url) {
|
Chris@0
|
408 const urlParsingNode = document.createElement('a');
|
Chris@0
|
409
|
Chris@0
|
410 // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8
|
Chris@0
|
411 // strings may throw an exception.
|
Chris@0
|
412 try {
|
Chris@0
|
413 url = decodeURIComponent(url);
|
Chris@17
|
414 } catch (e) {
|
Chris@0
|
415 // Empty.
|
Chris@0
|
416 }
|
Chris@0
|
417
|
Chris@0
|
418 urlParsingNode.setAttribute('href', url);
|
Chris@0
|
419
|
Chris@0
|
420 // IE <= 7 normalizes the URL when assigned to the anchor node similar to
|
Chris@0
|
421 // the other browsers.
|
Chris@0
|
422 return urlParsingNode.cloneNode(false).href;
|
Chris@0
|
423 };
|
Chris@0
|
424
|
Chris@0
|
425 /**
|
Chris@0
|
426 * Returns true if the URL is within Drupal's base path.
|
Chris@0
|
427 *
|
Chris@0
|
428 * @param {string} url
|
Chris@0
|
429 * The URL string to be tested.
|
Chris@0
|
430 *
|
Chris@0
|
431 * @return {bool}
|
Chris@0
|
432 * `true` if local.
|
Chris@0
|
433 *
|
Chris@0
|
434 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58
|
Chris@0
|
435 */
|
Chris@17
|
436 Drupal.url.isLocal = function(url) {
|
Chris@0
|
437 // Always use browser-derived absolute URLs in the comparison, to avoid
|
Chris@0
|
438 // attempts to break out of the base path using directory traversal.
|
Chris@0
|
439 let absoluteUrl = Drupal.url.toAbsolute(url);
|
Chris@17
|
440 let { protocol } = window.location;
|
Chris@0
|
441
|
Chris@0
|
442 // Consider URLs that match this site's base URL but use HTTPS instead of HTTP
|
Chris@0
|
443 // as local as well.
|
Chris@0
|
444 if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) {
|
Chris@0
|
445 protocol = 'https:';
|
Chris@0
|
446 }
|
Chris@17
|
447 let baseUrl = `${protocol}//${
|
Chris@17
|
448 window.location.host
|
Chris@17
|
449 }${drupalSettings.path.baseUrl.slice(0, -1)}`;
|
Chris@0
|
450
|
Chris@0
|
451 // Decoding non-UTF-8 strings may throw an exception.
|
Chris@0
|
452 try {
|
Chris@0
|
453 absoluteUrl = decodeURIComponent(absoluteUrl);
|
Chris@17
|
454 } catch (e) {
|
Chris@0
|
455 // Empty.
|
Chris@0
|
456 }
|
Chris@0
|
457 try {
|
Chris@0
|
458 baseUrl = decodeURIComponent(baseUrl);
|
Chris@17
|
459 } catch (e) {
|
Chris@0
|
460 // Empty.
|
Chris@0
|
461 }
|
Chris@0
|
462
|
Chris@0
|
463 // The given URL matches the site's base URL, or has a path under the site's
|
Chris@0
|
464 // base URL.
|
Chris@0
|
465 return absoluteUrl === baseUrl || absoluteUrl.indexOf(`${baseUrl}/`) === 0;
|
Chris@0
|
466 };
|
Chris@0
|
467
|
Chris@0
|
468 /**
|
Chris@0
|
469 * Formats a string containing a count of items.
|
Chris@0
|
470 *
|
Chris@0
|
471 * This function ensures that the string is pluralized correctly. Since
|
Chris@0
|
472 * {@link Drupal.t} is called by this function, make sure not to pass
|
Chris@0
|
473 * already-localized strings to it.
|
Chris@0
|
474 *
|
Chris@0
|
475 * See the documentation of the server-side
|
Chris@0
|
476 * \Drupal\Core\StringTranslation\TranslationInterface::formatPlural()
|
Chris@0
|
477 * function for more details.
|
Chris@0
|
478 *
|
Chris@0
|
479 * @param {number} count
|
Chris@0
|
480 * The item count to display.
|
Chris@0
|
481 * @param {string} singular
|
Chris@0
|
482 * The string for the singular case. Please make sure it is clear this is
|
Chris@0
|
483 * singular, to ease translation (e.g. use "1 new comment" instead of "1
|
Chris@0
|
484 * new"). Do not use @count in the singular string.
|
Chris@0
|
485 * @param {string} plural
|
Chris@0
|
486 * The string for the plural case. Please make sure it is clear this is
|
Chris@0
|
487 * plural, to ease translation. Use @count in place of the item count, as in
|
Chris@0
|
488 * "@count new comments".
|
Chris@0
|
489 * @param {object} [args]
|
Chris@0
|
490 * An object of replacements pairs to make after translation. Incidences
|
Chris@0
|
491 * of any key in this array are replaced with the corresponding value.
|
Chris@0
|
492 * See {@link Drupal.formatString}.
|
Chris@0
|
493 * Note that you do not need to include @count in this array.
|
Chris@0
|
494 * This replacement is done automatically for the plural case.
|
Chris@0
|
495 * @param {object} [options]
|
Chris@0
|
496 * The options to pass to the {@link Drupal.t} function.
|
Chris@0
|
497 *
|
Chris@0
|
498 * @return {string}
|
Chris@0
|
499 * A translated string.
|
Chris@0
|
500 */
|
Chris@17
|
501 Drupal.formatPlural = function(count, singular, plural, args, options) {
|
Chris@0
|
502 args = args || {};
|
Chris@0
|
503 args['@count'] = count;
|
Chris@0
|
504
|
Chris@0
|
505 const pluralDelimiter = drupalSettings.pluralDelimiter;
|
Chris@17
|
506 const translations = Drupal.t(
|
Chris@17
|
507 singular + pluralDelimiter + plural,
|
Chris@17
|
508 args,
|
Chris@17
|
509 options,
|
Chris@17
|
510 ).split(pluralDelimiter);
|
Chris@0
|
511 let index = 0;
|
Chris@0
|
512
|
Chris@0
|
513 // Determine the index of the plural form.
|
Chris@17
|
514 if (
|
Chris@17
|
515 typeof drupalTranslations !== 'undefined' &&
|
Chris@17
|
516 drupalTranslations.pluralFormula
|
Chris@17
|
517 ) {
|
Chris@17
|
518 index =
|
Chris@17
|
519 count in drupalTranslations.pluralFormula
|
Chris@17
|
520 ? drupalTranslations.pluralFormula[count]
|
Chris@17
|
521 : drupalTranslations.pluralFormula.default;
|
Chris@17
|
522 } else if (args['@count'] !== 1) {
|
Chris@0
|
523 index = 1;
|
Chris@0
|
524 }
|
Chris@0
|
525
|
Chris@0
|
526 return translations[index];
|
Chris@0
|
527 };
|
Chris@0
|
528
|
Chris@0
|
529 /**
|
Chris@0
|
530 * Encodes a Drupal path for use in a URL.
|
Chris@0
|
531 *
|
Chris@0
|
532 * For aesthetic reasons slashes are not escaped.
|
Chris@0
|
533 *
|
Chris@0
|
534 * @param {string} item
|
Chris@0
|
535 * Unencoded path.
|
Chris@0
|
536 *
|
Chris@0
|
537 * @return {string}
|
Chris@0
|
538 * The encoded path.
|
Chris@0
|
539 */
|
Chris@17
|
540 Drupal.encodePath = function(item) {
|
Chris@0
|
541 return window.encodeURIComponent(item).replace(/%2F/g, '/');
|
Chris@0
|
542 };
|
Chris@0
|
543
|
Chris@0
|
544 /**
|
Chris@0
|
545 * Generates the themed representation of a Drupal object.
|
Chris@0
|
546 *
|
Chris@0
|
547 * All requests for themed output must go through this function. It examines
|
Chris@0
|
548 * the request and routes it to the appropriate theme function. If the current
|
Chris@0
|
549 * theme does not provide an override function, the generic theme function is
|
Chris@0
|
550 * called.
|
Chris@0
|
551 *
|
Chris@0
|
552 * @example
|
Chris@0
|
553 * <caption>To retrieve the HTML for text that should be emphasized and
|
Chris@0
|
554 * displayed as a placeholder inside a sentence.</caption>
|
Chris@0
|
555 * Drupal.theme('placeholder', text);
|
Chris@0
|
556 *
|
Chris@0
|
557 * @namespace
|
Chris@0
|
558 *
|
Chris@0
|
559 * @param {function} func
|
Chris@0
|
560 * The name of the theme function to call.
|
Chris@0
|
561 * @param {...args}
|
Chris@0
|
562 * Additional arguments to pass along to the theme function.
|
Chris@0
|
563 *
|
Chris@0
|
564 * @return {string|object|HTMLElement|jQuery}
|
Chris@0
|
565 * Any data the theme function returns. This could be a plain HTML string,
|
Chris@0
|
566 * but also a complex object.
|
Chris@0
|
567 */
|
Chris@17
|
568 Drupal.theme = function(func, ...args) {
|
Chris@0
|
569 if (func in Drupal.theme) {
|
Chris@14
|
570 return Drupal.theme[func](...args);
|
Chris@0
|
571 }
|
Chris@0
|
572 };
|
Chris@0
|
573
|
Chris@0
|
574 /**
|
Chris@0
|
575 * Formats text for emphasized display in a placeholder inside a sentence.
|
Chris@0
|
576 *
|
Chris@0
|
577 * @param {string} str
|
Chris@0
|
578 * The text to format (plain-text).
|
Chris@0
|
579 *
|
Chris@0
|
580 * @return {string}
|
Chris@0
|
581 * The formatted text (html).
|
Chris@0
|
582 */
|
Chris@17
|
583 Drupal.theme.placeholder = function(str) {
|
Chris@0
|
584 return `<em class="placeholder">${Drupal.checkPlain(str)}</em>`;
|
Chris@0
|
585 };
|
Chris@17
|
586 })(Drupal, window.drupalSettings, window.drupalTranslations);
|