aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/captions.js34
-rw-r--r--src/js/config/defaults.js3
-rw-r--r--src/js/controls.js38
-rw-r--r--src/js/fullscreen.js24
-rw-r--r--src/js/listeners.js99
-rw-r--r--src/js/plugins/ads.js6
-rw-r--r--src/js/plugins/youtube.js20
-rw-r--r--src/js/plyr.js7
-rw-r--r--src/js/source.js2
-rw-r--r--src/js/ui.js61
-rw-r--r--src/js/utils/elements.js7
-rw-r--r--src/js/utils/events.js7
-rw-r--r--src/js/utils/is.js5
-rw-r--r--src/js/utils/strings.js5
14 files changed, 215 insertions, 103 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index 94500290..28d5cb91 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -6,12 +6,20 @@
import controls from './controls';
import i18n from './i18n';
import support from './support';
+import { dedupe } from './utils/arrays';
import browser from './utils/browser';
-import { createElement, emptyElement, getAttributesFromSelector, insertAfter, removeElement, toggleClass, toggleState } from './utils/elements';
+import {
+ createElement,
+ emptyElement,
+ getAttributesFromSelector,
+ insertAfter,
+ removeElement,
+ toggleClass,
+ toggleState,
+} from './utils/elements';
import { on, triggerEvent } from './utils/events';
import fetch from './utils/fetch';
import is from './utils/is';
-import { dedupe } from './utils/arrays';
import { getHTML } from './utils/strings';
import { parseUrl } from './utils/urls';
@@ -26,7 +34,11 @@ const captions = {
// Only Vimeo and HTML5 video supported at this point
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
// Clear menu and hide
- if (is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
+ if (
+ is.array(this.config.controls) &&
+ this.config.controls.includes('settings') &&
+ this.config.settings.includes('captions')
+ ) {
controls.setCaptionsMenu.call(this);
}
@@ -49,7 +61,11 @@ const captions = {
const src = track.getAttribute('src');
const url = parseUrl(src);
- if (url !== null && url.hostname !== window.location.href.hostname && ['http:', 'https:'].includes(url.protocol)) {
+ if (
+ url !== null &&
+ url.hostname !== window.location.href.hostname &&
+ ['http:', 'https:'].includes(url.protocol)
+ ) {
fetch(src, 'blob')
.then(blob => {
track.setAttribute('src', window.URL.createObjectURL(blob));
@@ -68,8 +84,9 @@ const captions = {
// * active: The state preferred by user settings or config
// * toggled: The real captions state
- const languages = dedupe(Array.from(navigator.languages || navigator.userLanguage)
- .map(language => language.split('-')[0]));
+ const languages = dedupe(
+ Array.from(navigator.languages || navigator.userLanguage).map(language => language.split('-')[0]),
+ );
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
@@ -165,10 +182,7 @@ const captions = {
// Force language if the call isn't passive and there is no matching language to toggle to
if (!this.language && active && !passive) {
const tracks = captions.getTracks.call(this);
- const track = captions.findTrack.call(this, [
- this.captions.language,
- ...this.captions.languages,
- ], true);
+ const track = captions.findTrack.call(this, [this.captions.language, ...this.captions.languages], true);
// Override user preferences to avoid switching languages if a matching track is added
this.captions.language = track.language;
diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js
index 34fb5519..0e07b75c 100644
--- a/src/js/config/defaults.js
+++ b/src/js/config/defaults.js
@@ -197,7 +197,8 @@ const defaults = {
},
youtube: {
sdk: 'https://www.youtube.com/iframe_api',
- api: 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet',
+ api:
+ 'https://www.googleapis.com/youtube/v3/videos?id={0}&key={1}&fields=items(snippet(title))&part=snippet',
},
googleIMA: {
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
diff --git a/src/js/controls.js b/src/js/controls.js
index 19c531af..d8d770a5 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -9,8 +9,20 @@ import support from './support';
import { repaint, transitionEndEvent } from './utils/animation';
import { dedupe } from './utils/arrays';
import browser from './utils/browser';
-import { createElement, emptyElement, getAttributesFromSelector, getElement, getElements, hasClass, removeElement, setAttributes, toggleClass, toggleHidden, toggleState } from './utils/elements';
-import { on, off } from './utils/events';
+import {
+ createElement,
+ emptyElement,
+ getAttributesFromSelector,
+ getElement,
+ getElements,
+ hasClass,
+ removeElement,
+ setAttributes,
+ toggleClass,
+ toggleHidden,
+ toggleState,
+} from './utils/elements';
+import { off, on } from './utils/events';
import is from './utils/is';
import loadSprite from './utils/loadSprite';
import { extend } from './utils/objects';
@@ -68,7 +80,9 @@ const controls = {
// Seek tooltip
if (is.element(this.elements.progress)) {
- this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`);
+ this.elements.display.seekTooltip = this.elements.progress.querySelector(
+ `.${this.config.classNames.tooltip}`,
+ );
}
return true;
@@ -331,10 +345,10 @@ const controls = {
if (type !== 'volume') {
progress.appendChild(createElement('span', null, '0'));
- const suffixKey = ({
+ const suffixKey = {
played: 'played',
buffer: 'buffered',
- })[type];
+ }[type];
const suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
@@ -519,7 +533,12 @@ const controls = {
// Update hover tooltip for seeking
updateSeekTooltip(event) {
// Bail if setting not true
- if (!this.config.tooltips.seek || !is.element(this.elements.inputs.seek) || !is.element(this.elements.display.seekTooltip) || this.duration === 0) {
+ if (
+ !this.config.tooltips.seek ||
+ !is.element(this.elements.inputs.seek) ||
+ !is.element(this.elements.display.seekTooltip) ||
+ this.duration === 0
+ ) {
return;
}
@@ -573,7 +592,12 @@ const controls = {
const invert = !is.element(this.elements.display.duration) && this.config.invertTime;
// Duration
- controls.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert);
+ controls.updateTimeDisplay.call(
+ this,
+ this.elements.display.currentTime,
+ invert ? this.duration - this.currentTime : this.currentTime,
+ invert,
+ );
// Ignore updates while seeking
if (event && event.type === 'timeupdate' && this.media.seeking) {
diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js
index a8d8b7e5..ded581f9 100644
--- a/src/js/fullscreen.js
+++ b/src/js/fullscreen.js
@@ -63,10 +63,15 @@ class Fullscreen {
// Register event listeners
// Handle event (incase user presses escape etc)
- on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => {
- // TODO: Filter for target??
- onChange.call(this);
- });
+ on.call(
+ this.player,
+ document,
+ this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
+ () => {
+ // TODO: Filter for target??
+ onChange.call(this);
+ },
+ );
// Fullscreen toggle on double click
on.call(this.player, this.player.elements.container, 'dblclick', event => {
@@ -84,7 +89,12 @@ class Fullscreen {
// Determine if native supported
static get native() {
- return !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled);
+ return !!(
+ document.fullscreenEnabled ||
+ document.webkitFullscreenEnabled ||
+ document.mozFullScreenEnabled ||
+ document.msFullscreenEnabled
+ );
}
// Get the prefix for handlers
@@ -142,7 +152,9 @@ class Fullscreen {
// Get target element
get target() {
- return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
+ return browser.isIos && this.player.config.fullscreen.iosNative
+ ? this.player.media
+ : this.player.elements.container;
}
// Update UI
diff --git a/src/js/listeners.js b/src/js/listeners.js
index d962761c..9d987508 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -52,9 +52,10 @@ class Listeners {
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = getFocusElement();
- if (is.element(focused) && (
- focused !== this.player.elements.inputs.seek &&
- matches(focused, this.player.config.selectors.editable))
+ if (
+ is.element(focused) &&
+ (focused !== this.player.elements.inputs.seek &&
+ matches(focused, this.player.config.selectors.editable))
) {
return;
}
@@ -174,7 +175,6 @@ class Listeners {
// Add touch class
toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
-
}
// Global window & document listeners
@@ -217,40 +217,49 @@ class Listeners {
});
// Toggle controls on mouse events and entering fullscreen
- on.call(this.player, this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', event => {
- const { controls } = this.player.elements;
+ on.call(
+ this.player,
+ this.player.elements.container,
+ 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
+ event => {
+ const { controls } = this.player.elements;
- // Remove button states for fullscreen
- if (event.type === 'enterfullscreen') {
- controls.pressed = false;
- controls.hover = false;
- }
+ // Remove button states for fullscreen
+ if (event.type === 'enterfullscreen') {
+ controls.pressed = false;
+ controls.hover = false;
+ }
- // Show, then hide after a timeout unless another control event occurs
- const show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);
+ // Show, then hide after a timeout unless another control event occurs
+ const show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);
- let delay = 0;
+ let delay = 0;
- if (show) {
- ui.toggleControls.call(this.player, true);
- // Use longer timeout for touch devices
- delay = this.player.touch ? 3000 : 2000;
- }
+ if (show) {
+ ui.toggleControls.call(this.player, true);
+ // Use longer timeout for touch devices
+ delay = this.player.touch ? 3000 : 2000;
+ }
- // Clear timer
- clearTimeout(this.player.timers.controls);
- // Timer to prevent flicker when seeking
- this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
- });
+ // Clear timer
+ clearTimeout(this.player.timers.controls);
+ // Timer to prevent flicker when seeking
+ this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
+ },
+ );
}
// Listen for media events
media() {
// Time change on media
- on.call(this.player, this.player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(this.player, event));
+ on.call(this.player, this.player.media, 'timeupdate seeking seeked', event =>
+ controls.timeUpdate.call(this.player, event),
+ );
// Display duration
- on.call(this.player, this.player.media, 'durationchange loadeddata loadedmetadata', event => controls.durationUpdate.call(this.player, event));
+ on.call(this.player, this.player.media, 'durationchange loadeddata loadedmetadata', event =>
+ controls.durationUpdate.call(this.player, event),
+ );
// Check for audio tracks on load
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
@@ -269,16 +278,24 @@ class Listeners {
});
// Check for buffer progress
- on.call(this.player, this.player.media, 'progress playing seeking seeked', event => controls.updateProgress.call(this.player, event));
+ on.call(this.player, this.player.media, 'progress playing seeking seeked', event =>
+ controls.updateProgress.call(this.player, event),
+ );
// Handle volume changes
- on.call(this.player, this.player.media, 'volumechange', event => controls.updateVolume.call(this.player, event));
+ on.call(this.player, this.player.media, 'volumechange', event =>
+ controls.updateVolume.call(this.player, event),
+ );
// Handle play/pause
- on.call(this.player, this.player.media, 'playing play pause ended emptied timeupdate', event => ui.checkPlaying.call(this.player, event));
+ on.call(this.player, this.player.media, 'playing play pause ended emptied timeupdate', event =>
+ ui.checkPlaying.call(this.player, event),
+ );
// Loading state
- on.call(this.player, this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
+ on.call(this.player, this.player.media, 'waiting canplay seeked playing', event =>
+ ui.checkLoading.call(this.player, event),
+ );
// If autoplay, then load advertisement if required
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
@@ -324,7 +341,8 @@ class Listeners {
// Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) {
- on.call(this.player,
+ on.call(
+ this.player,
this.player.elements.wrapper,
'contextmenu',
event => {
@@ -365,7 +383,7 @@ class Listeners {
// Bubble up key events for Edge
const proxyEvents = this.player.config.events.concat(['keyup', 'keydown']).join(' ');
on.call(this.player, this.player.media, proxyEvents, event => {
- let {detail = {}} = event;
+ let { detail = {} } = event;
// Get error details from media
if (event.type === 'error') {
@@ -403,7 +421,13 @@ class Listeners {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = is.function(customHandler);
- on.call(this.player, element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
+ on.call(
+ this.player,
+ element,
+ type,
+ event => proxy(event, defaultHandler, customHandlerKey),
+ passive && !hasCustomHandler,
+ );
};
// Play/pause toggle
@@ -592,7 +616,9 @@ class Listeners {
}
// Seek tooltip
- bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
+ bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
+ controls.updateSeekTooltip.call(this.player, event),
+ );
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
@@ -665,7 +691,10 @@ class Listeners {
}
// Don't break page scrolling at max and min
- if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
+ if (
+ (direction === 1 && this.player.media.volume < 1) ||
+ (direction === -1 && this.player.media.volume > 0)
+ ) {
event.preventDefault();
}
},
diff --git a/src/js/plugins/ads.js b/src/js/plugins/ads.js
index 19df7666..e0d49265 100644
--- a/src/js/plugins/ads.js
+++ b/src/js/plugins/ads.js
@@ -150,7 +150,11 @@ class Ads {
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
// Listen and respond to ads loaded and error events
- this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false);
+ this.loader.addEventListener(
+ google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
+ event => this.onAdsManagerLoaded(event),
+ false,
+ );
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
// Request video ads
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index 65b6db75..64b6fff7 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -210,13 +210,14 @@ const youtube = {
if (!player.media.error) {
const code = event.data;
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
- const message = ({
- 2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
- 5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
- 100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
- 101: 'The owner of the requested video does not allow it to be played in embedded players.',
- 150: 'The owner of the requested video does not allow it to be played in embedded players.',
- }[code]) || 'An unknown error occured';
+ const message =
+ {
+ 2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
+ 5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
+ 100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
+ 101: 'The owner of the requested video does not allow it to be played in embedded players.',
+ 150: 'The owner of the requested video does not allow it to be played in embedded players.',
+ }[code] || 'An unknown error occured';
player.media.error = { code, message };
@@ -453,7 +454,10 @@ const youtube = {
}
// Get quality
- controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
+ controls.setQualityMenu.call(
+ player,
+ mapQualityUnits(instance.getAvailableQualityLevels()),
+ );
}
break;
diff --git a/src/js/plyr.js b/src/js/plyr.js
index dcbe384b..7ecb810a 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -679,7 +679,12 @@ class Plyr {
return;
}
- let quality = [!is.empty(input) && Number(input), this.storage.get('quality'), config.selected, config.default].find(is.number);
+ let quality = [
+ !is.empty(input) && Number(input),
+ this.storage.get('quality'),
+ config.selected,
+ config.default,
+ ].find(is.number);
if (!options.includes(quality)) {
const value = closest(options, quality);
diff --git a/src/js/source.js b/src/js/source.js
index c62db15a..8c9fdf44 100644
--- a/src/js/source.js
+++ b/src/js/source.js
@@ -8,8 +8,8 @@ import media from './media';
import support from './support';
import ui from './ui';
import { createElement, insertElement, removeElement } from './utils/elements';
-import { getDeep } from './utils/objects';
import is from './utils/is';
+import { getDeep } from './utils/objects';
const source = {
// Add elements to HTML5 media (source, tracks, etc)
diff --git a/src/js/ui.js b/src/js/ui.js
index 5d7a6ae3..e0d7c6ae 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -86,7 +86,11 @@ const ui = {
ui.checkPlaying.call(this);
// Check for picture-in-picture support
- toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo);
+ toggleClass(
+ this.elements.container,
+ this.config.classNames.pip.supported,
+ support.pip && this.isHTML5 && this.isVideo,
+ );
// Check for airplay support
toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
@@ -174,32 +178,35 @@ const ui = {
this.media.setAttribute('poster', poster);
// Wait until ui is ready
- return ready.call(this)
- // Load image
- .then(() => loadImage(poster))
- .catch(err => {
- // Hide poster on error unless it's been set by another call
- if (poster === this.poster) {
- ui.togglePoster.call(this, false);
- }
- // Rethrow
- throw err;
- })
- .then(() => {
- // Prevent race conditions
- if (poster !== this.poster) {
- throw new Error('setPoster cancelled by later call to setPoster');
- }
- })
- .then(() => {
- Object.assign(this.elements.poster.style, {
- backgroundImage: `url('${poster}')`,
- // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
- backgroundSize: '',
- });
- ui.togglePoster.call(this, true);
- return poster;
- });
+ return (
+ ready
+ .call(this)
+ // Load image
+ .then(() => loadImage(poster))
+ .catch(err => {
+ // Hide poster on error unless it's been set by another call
+ if (poster === this.poster) {
+ ui.togglePoster.call(this, false);
+ }
+ // Rethrow
+ throw err;
+ })
+ .then(() => {
+ // Prevent race conditions
+ if (poster !== this.poster) {
+ throw new Error('setPoster cancelled by later call to setPoster');
+ }
+ })
+ .then(() => {
+ Object.assign(this.elements.poster.style, {
+ backgroundImage: `url('${poster}')`,
+ // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
+ backgroundSize: '',
+ });
+ ui.togglePoster.call(this, true);
+ return poster;
+ })
+ );
},
// Check playing state
diff --git a/src/js/utils/elements.js b/src/js/utils/elements.js
index 2d314ed8..19e98f6f 100644
--- a/src/js/utils/elements.js
+++ b/src/js/utils/elements.js
@@ -218,7 +218,12 @@ export function matches(element, selector) {
return Array.from(document.querySelectorAll(selector)).includes(this);
}
- const matches = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
+ const matches =
+ prototype.matches ||
+ prototype.webkitMatchesSelector ||
+ prototype.mozMatchesSelector ||
+ prototype.msMatchesSelector ||
+ match;
return matches.call(element, selector);
}
diff --git a/src/js/utils/events.js b/src/js/utils/events.js
index 9009d1cc..9f734f04 100644
--- a/src/js/utils/events.js
+++ b/src/js/utils/events.js
@@ -113,7 +113,8 @@ export function unbindListeners() {
}
// Run method when / if player is ready
-export function ready () {
- return new Promise(resolve => this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve))
- .then(() => {});
+export function ready() {
+ return new Promise(
+ resolve => (this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve)),
+ ).then(() => {});
}
diff --git a/src/js/utils/is.js b/src/js/utils/is.js
index d34d3aed..cb2c07c6 100644
--- a/src/js/utils/is.js
+++ b/src/js/utils/is.js
@@ -47,7 +47,10 @@ const is = {
return instanceOf(input, TextTrack) || (!is.nullOrUndefined(input) && is.string(input.kind));
},
url(input) {
- return !is.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
+ return (
+ !is.nullOrUndefined(input) &&
+ /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input)
+ );
},
nullOrUndefined(input) {
return input === null || typeof input === 'undefined';
diff --git a/src/js/utils/strings.js b/src/js/utils/strings.js
index 8ca14ff8..c872498c 100644
--- a/src/js/utils/strings.js
+++ b/src/js/utils/strings.js
@@ -29,7 +29,10 @@ export function getPercentage(current, max) {
// Replace all occurances of a string in a string
export function replaceAll(input = '', find = '', replace = '') {
- return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
+ return input.replace(
+ new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'),
+ replace.toString(),
+ );
}
// Convert to title case