diff --git a/.appveyor.yml b/.appveyor.yml index 9e426110418..db631d033b5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,8 +13,9 @@ install: - npm install test_script: - - npm --silent run build - - npm --silent run test -- --browsers IE --webgl-stub --suppressPassed + - npm --silent run minifyRelease + - npm --silent run build-specs + - npm --silent run test -- --browsers IE --webgl-stub --release --suppressPassed # Don't actually build. build: off diff --git a/.eslintignore b/.eslintignore index 71525bb5214..f6d729a5154 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,8 +4,11 @@ Build/** Documentation/** Source/Shaders/** Source/ThirdParty/** -Source/Workers/cesiumWorkerBootstrapper.js +Source/Workers/** +!Source/Workers/transferTypedArrayTest.js ThirdParty/** Tools/** Apps/Sandcastle/jsHintOptions.js Apps/Sandcastle/gallery/gallery-index.js +Source/Core/buildModuleUrl.js +Specs/spec-main.js diff --git a/.eslintrc.json b/.eslintrc.json index 01a053756fc..6c69dcbf3be 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,5 +5,18 @@ ], "rules": { "no-unused-vars": ["error", {"vars": "all", "args": "none"}] - } + }, + "overrides": [ + { + "files": [ + "index.js", + "server.js", + "gulpfile.js", + "Source/Workers/transferTypedArrayTest.js" + ], + "parserOptions": { + "sourceType": "script" + } + } + ] } diff --git a/.gitignore b/.gitignore index 3d22bfa7d15..0a6c57a54ee 100644 --- a/.gitignore +++ b/.gitignore @@ -14,12 +14,12 @@ Thumbs.db /Source/Cesium.js -/Source/Shaders/*.js -/Source/Shaders/*/*.js -/Source/Shaders/*/*/*.js -/Source/ThirdParty/Shaders/*.js - /Specs/SpecList.js +/Source/Shaders/**/*.js +/Source/ThirdParty/Shaders/**/*.js +/Source/Workers/** +!/Source/Workers/cesiumWorkerBootstrapper.js +!/Source/Workers/transferTypedArrayTest.js /node_modules npm-debug.log diff --git a/.npmignore b/.npmignore index da787f59d72..98c5270d4a0 100644 --- a/.npmignore +++ b/.npmignore @@ -11,10 +11,11 @@ /.travis.yml /.vscode /Apps +/Build/Apps /Build/Coverage /Build/minifyShaders.state -/Build/Stubs /Build/Documentation +/Build/Specs /Cesium-*.zip /Documentation /favicon.ico @@ -24,7 +25,6 @@ /launches /server.js /Source/copyrightHeader.js -/Source/main.js /Specs /ThirdParty /Tools diff --git a/.travis.yml b/.travis.yml index de00e0c3019..be47106c948 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ script: - npm --silent run deploy-s3 -- -b cesium-dev -d cesium/$TRAVIS_BRANCH --confirm -c 'no-cache' - npm --silent run deploy-status -- --status success --message Deployed + - npm --silent run build-specs - npm --silent run test -- --browsers ChromeCI --failTaskOnError --webgl-stub --release --suppressPassed # Various Node.js smoke-screen tests diff --git a/Apps/CesiumViewer/CesiumViewerStartup.js b/Apps/CesiumViewer/CesiumViewerStartup.js deleted file mode 100644 index dd0502edb06..00000000000 --- a/Apps/CesiumViewer/CesiumViewerStartup.js +++ /dev/null @@ -1,11 +0,0 @@ -/*eslint-disable strict*/ -require({ - baseUrl : '.', - paths : { - domReady : '../../ThirdParty/requirejs-2.1.20/domReady', - Cesium : '../../Source' - } -}, [ - 'CesiumViewer' - ], function() { -}); diff --git a/Apps/CesiumViewer/index.html b/Apps/CesiumViewer/index.html index 1851db5ea96..35b67a8902b 100644 --- a/Apps/CesiumViewer/index.html +++ b/Apps/CesiumViewer/index.html @@ -10,10 +10,10 @@ Cesium Viewer -
+ diff --git a/Apps/Sandcastle/.eslintrc.json b/Apps/Sandcastle/.eslintrc.json index b2f72019ab4..deade47938c 100644 --- a/Apps/Sandcastle/.eslintrc.json +++ b/Apps/Sandcastle/.eslintrc.json @@ -1,5 +1,8 @@ { "extends": "../../.eslintrc.json", + "env": { + "amd": true + }, "globals": { "JSON": true, "require": true, @@ -7,10 +10,25 @@ "Sandcastle": true, "Cesium": true }, + "parserOptions": { + "ecmaVersion": 5, + "sourceType": "script" + }, "rules": { "no-alert": ["off"], "no-implicit-globals": "off", "no-unused-vars": ["off"], "quotes": "off" - } + }, + "overrides": [ + { + "files": [ + "load-cesium-es6.js" + ], + "parserOptions": { + "ecmaVersion": 2015, + "sourceType": "module" + } + } + ] } diff --git a/Apps/Sandcastle/CesiumSandcastle.js b/Apps/Sandcastle/CesiumSandcastle.js index 4d3b2e6703a..b19af12ab3f 100644 --- a/Apps/Sandcastle/CesiumSandcastle.js +++ b/Apps/Sandcastle/CesiumSandcastle.js @@ -1,6 +1,6 @@ /*global JSHINT */ /*global decodeBase64Data, embedInSandcastleTemplate */ -/*global gallery_demos, has_new_gallery_demos, hello_world_index*/// defined in gallery/gallery-index.js, created by build +/*global gallery_demos, has_new_gallery_demos, hello_world_index, VERSION*/// defined in gallery/gallery-index.js, created by build /*global sandcastleJsHintOptions*/// defined by jsHintOptions.js, created by build require({ baseUrl: '../../Source', @@ -13,13 +13,6 @@ require({ }, { name: 'Sandcastle', location: '../Apps/Sandcastle' - }, { - name: 'Source', - location: '.' - }, { - name: 'CesiumUnminified', - location: '../Build/CesiumUnminified', - main: 'Cesium' }, { name: 'CodeMirror', location: '../ThirdParty/codemirror-4.6' @@ -49,8 +42,8 @@ require({ 'dojo/promise/all', 'dojo/query', 'dojo/when', + 'dojo/request/script', 'Sandcastle/LinkButton', - 'Source/Cesium', 'ThirdParty/clipboard.min', 'ThirdParty/pako.min', 'CodeMirror/addon/hint/show-hint', @@ -94,20 +87,16 @@ require({ all, query, when, + dojoscript, LinkButton, - Cesium, ClipboardJS, pako) { 'use strict'; // attach clipboard handling to our Copy button var clipboardjs = new ClipboardJS('.copyButton'); - if (typeof Cesium !== 'object') { - //For built sandcastle - Cesium = window.Cesium; - } else if (typeof window.Cesium === 'undefined') { - //In order for CodeMirror auto-complete to work, Cesium needs to be defined as a global. - window.Cesium = Cesium; + function defined(value) { + return value !== undefined && value !== null; } parser.parse(); @@ -228,7 +217,7 @@ require({ } function openDocTab(title, link) { - if (!Cesium.defined(docTabs[title])) { + if (!defined(docTabs[title])) { docTabs[title] = new ContentPane({ title : title, focused : true, @@ -285,7 +274,7 @@ require({ function onCursorActivity() { docNode.style.left = '-999px'; - if (Cesium.defined(docTimer)) { + if (defined(docTimer)) { window.clearTimeout(docTimer); } docTimer = window.setTimeout(showDocPopup, 500); @@ -309,7 +298,7 @@ require({ } function closeGalleryTooltip() { - if (Cesium.defined(activeGalleryTooltipDemo)) { + if (defined(activeGalleryTooltipDemo)) { popup.close(demoTooltips[activeGalleryTooltipDemo.name]); activeGalleryTooltipDemo = undefined; } @@ -326,7 +315,7 @@ require({ suffix = 'searchDemo'; } - if (Cesium.defined(activeGalleryTooltipDemo)) { + if (defined(activeGalleryTooltipDemo)) { popup.open({ popup : demoTooltips[activeGalleryTooltipDemo.name], around : dom.byId(activeGalleryTooltipDemo.name + suffix), @@ -338,7 +327,7 @@ require({ function scheduleGalleryTooltip(demo) { if (demo !== activeGalleryTooltipDemo) { activeGalleryTooltipDemo = demo; - if (Cesium.defined(galleryTooltipTimer)) { + if (defined(galleryTooltipTimer)) { window.clearTimeout(galleryTooltipTimer); } galleryTooltipTimer = window.setTimeout(openGalleryTooltip, 220); @@ -386,7 +375,7 @@ require({ var hints = JSHINT.errors; for (i = 0, len = hints.length; i < len; ++i) { var hint = hints[i]; - if (hint !== null && Cesium.defined(hint.reason) && hint.line > 0) { + if (hint !== null && defined(hint.reason) && hint.line > 0) { line = jsEditor.setGutterMarker(scriptLineToEditorLine(hint.line), 'hintGutter', makeLineLabel(hint.reason, 'hintMarker')); jsEditor.addLineClass(line, 'text', 'hintLine'); errorLines.push(line); @@ -397,7 +386,7 @@ require({ } function scheduleHint() { - if (Cesium.defined(hintTimer)) { + if (defined(hintTimer)) { window.clearTimeout(hintTimer); } hintTimer = setTimeout(clearErrorsAddHints, 550); @@ -405,14 +394,14 @@ require({ } function scheduleHintNoChange() { - if (Cesium.defined(hintTimer)) { + if (defined(hintTimer)) { window.clearTimeout(hintTimer); } hintTimer = setTimeout(clearErrorsAddHints, 550); } function scrollToLine(lineNumber) { - if (Cesium.defined(lineNumber)) { + if (defined(lineNumber)) { jsEditor.setCursor(lineNumber); // set selection twice in order to force the editor to scroll // to this location if the cursor is already there @@ -471,13 +460,13 @@ require({ function registerScroll(demoContainer) { if (document.onmousewheel !== undefined) { demoContainer.addEventListener('mousewheel', function(e) { - if (Cesium.defined(e.wheelDelta) && e.wheelDelta) { + if (defined(e.wheelDelta) && e.wheelDelta) { demoContainer.scrollLeft -= e.wheelDelta * 70 / 120; } }, false); } else { demoContainer.addEventListener('DOMMouseScroll', function(e) { - if (Cesium.defined(e.detail) && e.detail) { + if (defined(e.detail) && e.detail) { demoContainer.scrollLeft += e.detail * 70 / 3; } }, false); @@ -577,6 +566,7 @@ require({ return; } if (nodes.length > 0) { + while(nodes.length > 0){ node = nodes.shift(); var scriptElement = bucketDoc.createElement('script'); var hasSrc = false; @@ -597,6 +587,7 @@ require({ bucketDoc.head.appendChild(scriptElement); loadScript(); } + } } else { // Apply user JS to bucket var element = bucketDoc.createElement('script'); @@ -661,7 +652,7 @@ require({ function loadBucket(bucketName) { if (local.bucketName !== bucketName) { local.bucketName = bucketName; - if (Cesium.defined(bucketTypes[bucketName])) { + if (defined(bucketTypes[bucketName])) { local.headers = bucketTypes[bucketName]; } else { local.headers = ''; @@ -685,10 +676,10 @@ require({ if (window.location.search) { queryObject = ioQuery.queryToObject(window.location.search.substring(1)); } - if (!Cesium.defined(queryObject.src)) { + if (!defined(queryObject.src)) { queryObject.src = defaultDemo + '.html'; } - if (!Cesium.defined(queryObject.label)) { + if (!defined(queryObject.label)) { queryObject.label = defaultLabel; } @@ -717,19 +708,20 @@ require({ } var json, code, html; - if (Cesium.defined(queryObject.gist)) { - Cesium.Resource.fetchJsonp('https://api.github.com/gists/' + queryObject.gist + '?access_token=dd8f755c2e5d9bbb26806bb93eaa2291f2047c60') - .then(function(data) { - var files = data.data.files; - var code = files['Cesium-Sandcastle.js'].content; - var htmlFile = files['Cesium-Sandcastle.html']; - var html = Cesium.defined(htmlFile) ? htmlFile.content : defaultHtml; // Use the default html for old gists - applyLoadedDemo(code, html); - }).otherwise(function(error) { + if (defined(queryObject.gist)) { + dojoscript.get('https://api.github.com/gists/' + queryObject.gist + '?access_token=dd8f755c2e5d9bbb26806bb93eaa2291f2047c60', { + jsonp: 'callback' + }).then(function(data) { + var files = data.data.files; + var code = files['Cesium-Sandcastle.js'].content; + var htmlFile = files['Cesium-Sandcastle.html']; + var html = defined(htmlFile) ? htmlFile.content : defaultHtml; // Use the default html for old gists + applyLoadedDemo(code, html); + }).otherwise(function(error) { appendConsole('consoleError', 'Unable to GET from GitHub API. This could be due to too many request, try again in an hour or copy and paste the code from the gist: https://gist.github.com/' + queryObject.gist, true); console.log(error); }); - } else if (Cesium.defined(queryObject.code)) { + } else if (defined(queryObject.code)) { //The code query parameter is a Base64 encoded JSON string with `code` and `html` properties. json = JSON.parse(window.atob(queryObject.code)); code = json.code; @@ -811,14 +803,14 @@ require({ appendConsole('consoleLog', 'Unable to load demo named ' + queryObject.src.replace('.html', '') + '. Redirecting to HelloWorld.\n', true); } } - } else if (Cesium.defined(e.data.log)) { + } else if (defined(e.data.log)) { // Console log messages from the iframe display in Sandcastle. appendConsole('consoleLog', e.data.log, false); - } else if (Cesium.defined(e.data.error)) { + } else if (defined(e.data.error)) { // Console error messages from the iframe display in Sandcastle var errorMsg = e.data.error; var lineNumber = e.data.lineNumber; - if (Cesium.defined(lineNumber)) { + if (defined(lineNumber)) { errorMsg += ' (on line '; if (e.data.url) { @@ -833,10 +825,10 @@ require({ } } appendConsole('consoleError', errorMsg, true); - } else if (Cesium.defined(e.data.warn)) { + } else if (defined(e.data.warn)) { // Console warning messages from the iframe display in Sandcastle. appendConsole('consoleWarn', e.data.warn, true); - } else if (Cesium.defined(e.data.highlight)) { + } else if (defined(e.data.highlight)) { // Hovering objects in the embedded Cesium window. highlightLine(e.data.highlight); } @@ -1058,7 +1050,7 @@ require({ }); } - var newInLabel = 'New in ' + window.Cesium.VERSION; + var newInLabel = 'New in ' + VERSION; function loadDemoFromFile(demo) { return requestDemo(demo.name).then(function(value) { // Store the file contents for later searching. @@ -1083,11 +1075,11 @@ require({ } // Select the demo to load upon opening based on the query parameter. - if (Cesium.defined(queryObject.src)) { + if (defined(queryObject.src)) { if (demo.name === queryObject.src.replace('.html', '')) { loadFromGallery(demo).then(function() { window.history.replaceState(demo, demo.name, getPushStateUrl(demo)); - if (Cesium.defined(queryObject.gist)) { + if (defined(queryObject.gist)) { document.title = 'Gist Import - Cesium Sandcastle'; } else { document.title = demo.name + ' - Cesium Sandcastle'; @@ -1110,7 +1102,7 @@ require({ var loading = true; function setSubtab(tabName) { - currentTab = Cesium.defined(tabName) && !loading ? tabName : queryObject.label; + currentTab = defined(tabName) && !loading ? tabName : queryObject.label; queryObject.label = tabName; loading = false; } @@ -1162,7 +1154,7 @@ require({ function createGalleryButton(demo, tabName) { var imgSrc = 'templates/Gallery_tile.jpg'; - if (Cesium.defined(demo.img)) { + if (defined(demo.img)) { imgSrc = 'gallery/' + demo.img; } @@ -1214,7 +1206,7 @@ require({ } var promise; - if (!Cesium.defined(gallery_demos)) { + if (!defined(gallery_demos)) { galleryErrorMsg.textContent = 'No demos found, please run the build script.'; galleryErrorMsg.style.display = 'inline-block'; } else { @@ -1230,7 +1222,7 @@ require({ registerScroll(dom.byId('showcasesContainer')); if (has_new_gallery_demos) { - var name = 'New in ' + window.Cesium.VERSION; + var name = 'New in ' + VERSION; subtabs[name] = new ContentPane({ content: '
', title: name, diff --git a/Apps/Sandcastle/Sandcastle-helpers.js b/Apps/Sandcastle/Sandcastle-helpers.js index 2634f960cc2..079610424c7 100644 --- a/Apps/Sandcastle/Sandcastle-helpers.js +++ b/Apps/Sandcastle/Sandcastle-helpers.js @@ -11,9 +11,8 @@ ' Sandcastle.finishedLoading();\n' + '}\n' + 'if (typeof Cesium !== \'undefined\') {\n' + + ' window.startupCalled = true;\n' + ' startup(Cesium);\n' + - '} else if (typeof require === \'function\') {\n' + - ' require([\'Cesium\'], startup);\n' + '}\n'; }; window.decodeBase64Data = function(base64String, pako) { diff --git a/Apps/Sandcastle/index.html b/Apps/Sandcastle/index.html index 9ddfe6faf13..89669f703fb 100644 --- a/Apps/Sandcastle/index.html +++ b/Apps/Sandcastle/index.html @@ -132,7 +132,7 @@
-
+
diff --git a/Apps/Sandcastle/load-cesium-es6.js b/Apps/Sandcastle/load-cesium-es6.js new file mode 100644 index 00000000000..35ab6dfca53 --- /dev/null +++ b/Apps/Sandcastle/load-cesium-es6.js @@ -0,0 +1,10 @@ +// This file loads the unbuilt ES6 version of Cesium +// into the global scope during local developmnet +import * as Cesium from "../../../Source/Cesium.js"; +window.Cesium = Cesium; + +// Since ES6 modules have no guaranteed load order, +// only call startup if it's already defined but hasn't been called yet +if (!window.startupCalled && typeof window.startup === 'function') { + window.startup(Cesium); +} diff --git a/Apps/Sandcastle/standalone.html b/Apps/Sandcastle/standalone.html index e314fd6623e..4a4dbb26a80 100644 --- a/Apps/Sandcastle/standalone.html +++ b/Apps/Sandcastle/standalone.html @@ -8,7 +8,8 @@ - + + + diff --git a/Source/Core/TaskProcessor.js b/Source/Core/TaskProcessor.js index 6aee28d6aa1..83a3fbfe531 100644 --- a/Source/Core/TaskProcessor.js +++ b/Source/Core/TaskProcessor.js @@ -123,23 +123,16 @@ import RuntimeError from './RuntimeError.js'; worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage); var bootstrapMessage = { - loaderConfig : {}, - workerModule : TaskProcessor._workerModulePrefix + processor._workerName - }; - - if (defined(TaskProcessor._loaderConfig)) { - bootstrapMessage.loaderConfig = TaskProcessor._loaderConfig; - } else { - if (!(defined(define.amd) && !define.amd.toUrlUndefined && defined(require.toUrl))) { - bootstrapMessage.loaderConfig.paths = { + loaderConfig: { + paths: { 'Workers': buildModuleUrl('Workers') - }; - } - bootstrapMessage.loaderConfig.baseUrl = buildModuleUrl.getCesiumBaseUrl().url; - } + }, + baseUrl: buildModuleUrl.getCesiumBaseUrl().url + }, + workerModule: TaskProcessor._workerModulePrefix + processor._workerName + }; worker.postMessage(bootstrapMessage); - worker.onmessage = function(event) { completeTask(processor, event.data); }; @@ -341,6 +334,5 @@ import RuntimeError from './RuntimeError.js'; // exposed for testing purposes TaskProcessor._defaultWorkerModulePrefix = 'Workers/'; TaskProcessor._workerModulePrefix = TaskProcessor._defaultWorkerModulePrefix; - TaskProcessor._loaderConfig = undefined; TaskProcessor._canTransferArrayBuffer = undefined; export default TaskProcessor; diff --git a/Source/Core/buildModuleUrl.js b/Source/Core/buildModuleUrl.js index 724f3d992e6..fb975bce20a 100644 --- a/Source/Core/buildModuleUrl.js +++ b/Source/Core/buildModuleUrl.js @@ -1,5 +1,6 @@ import defined from './defined.js'; import DeveloperError from './DeveloperError.js'; +import FeatureDetection from './FeatureDetection.js'; import getAbsoluteUri from './getAbsoluteUri.js'; import Resource from './Resource.js'; @@ -46,12 +47,16 @@ import Resource from './Resource.js'; baseUrlString = CESIUM_BASE_URL; } else if (typeof define === 'object' && defined(define.amd) && !define.amd.toUrlUndefined && defined(require.toUrl)) { baseUrlString = getAbsoluteUri('..', buildModuleUrl('Core/buildModuleUrl.js')); - } else if (defined(import.meta.url)){ - baseUrlString = getAbsoluteUri('..', import.meta.url) + } else if (!FeatureDetection.isInternetExplorer() && /\/buildModuleUrl\.js$/.test(import.meta.url)) { + baseUrlString = getAbsoluteUri('..', import.meta.url); } else { baseUrlString = getBaseUrlFromCesiumScript(); } + if (baseUrlString === '') { + baseUrlString = '.'; + } + //>>includeStart('debug', pragmas.debug); if (!defined(baseUrlString)) { throw new DeveloperError('Unable to determine Cesium base URL automatically, try defining a global variable called CESIUM_BASE_URL.'); diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index db5aec464c3..84263f47470 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -140,9 +140,8 @@ import WallGraphics from './WallGraphics.js'; var autolinker = new Autolinker({ stripPrefix : false, - twitter : false, email : false, - replaceFn : function(linker, match) { + replaceFn : function(match) { if (!match.protocolUrlMatch) { //Prevent matching of non-explicit urls. //i.e. foo.id won't match but http://foo.id will diff --git a/Source/Scene/ShadowVolumeAppearance.js b/Source/Scene/ShadowVolumeAppearance.js index bebade72c61..6ce4022c138 100644 --- a/Source/Scene/ShadowVolumeAppearance.js +++ b/Source/Scene/ShadowVolumeAppearance.js @@ -16,13 +16,6 @@ import ShaderSource from '../Renderer/ShaderSource.js'; import PerInstanceColorAppearance from '../Scene/PerInstanceColorAppearance.js'; import ShadowVolumeAppearanceFS from '../Shaders/ShadowVolumeAppearanceFS.js'; - var projectionExtentDefines = { - eastMostYhighDefine : '', - eastMostYlowDefine : '', - westMostYhighDefine : '', - westMostYlowDefine : '' - }; - /** * Creates shaders for a ClassificationPrimitive to use a given Appearance, as well as for picking. * @@ -40,6 +33,13 @@ import ShadowVolumeAppearanceFS from '../Shaders/ShadowVolumeAppearanceFS.js'; Check.typeOf.bool('useFloatBatchTable', useFloatBatchTable); //>>includeEnd('debug'); + this._projectionExtentDefines = { + eastMostYhighDefine : '', + eastMostYlowDefine : '', + westMostYhighDefine : '', + westMostYlowDefine : '' + }; + this._useFloatBatchTable = useFloatBatchTable; // Compute shader dependencies @@ -184,7 +184,7 @@ import ShadowVolumeAppearanceFS from '../Shaders/ShadowVolumeAppearanceFS.js'; Check.typeOf.bool('columbusView2D', columbusView2D); Check.defined('mapProjection', mapProjection); //>>includeEnd('debug'); - return createShadowVolumeAppearanceVS(this._colorShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, this._appearance, mapProjection, this._useFloatBatchTable); + return createShadowVolumeAppearanceVS(this._colorShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, this._appearance, mapProjection, this._useFloatBatchTable, this._projectionExtentDefines); }; /** @@ -203,7 +203,7 @@ import ShadowVolumeAppearanceFS from '../Shaders/ShadowVolumeAppearanceFS.js'; Check.typeOf.bool('columbusView2D', columbusView2D); Check.defined('mapProjection', mapProjection); //>>includeEnd('debug'); - return createShadowVolumeAppearanceVS(this._pickShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, undefined, mapProjection, this._useFloatBatchTable); + return createShadowVolumeAppearanceVS(this._pickShaderDependencies, this._planarExtents, columbusView2D, defines, vertexShaderSource, undefined, mapProjection, this._useFloatBatchTable, this._projectionExtentDefines); }; var longitudeExtentsCartesianScratch = new Cartesian3(); @@ -212,7 +212,7 @@ import ShadowVolumeAppearanceFS from '../Shaders/ShadowVolumeAppearanceFS.js'; high : 0.0, low : 0.0 }; - function createShadowVolumeAppearanceVS(shaderDependencies, planarExtents, columbusView2D, defines, vertexShaderSource, appearance, mapProjection, useFloatBatchTable) { + function createShadowVolumeAppearanceVS(shaderDependencies, planarExtents, columbusView2D, defines, vertexShaderSource, appearance, mapProjection, useFloatBatchTable, projectionExtentDefines) { var allDefines = defines.slice(); if (projectionExtentDefines.eastMostYhighDefine === '') { diff --git a/Source/ThirdParty/Autolinker.js b/Source/ThirdParty/Autolinker.js index 6b2ec1ba542..e2ca956c056 100644 --- a/Source/ThirdParty/Autolinker.js +++ b/Source/ThirdParty/Autolinker.js @@ -1,2756 +1,4216 @@ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module unless amdModuleId is set - define([], function () { - return (root['Autolinker'] = factory()); - }); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - root['Autolinker'] = factory(); - } -}(this, function () { +var tmp = {}; /*! * Autolinker.js - * 0.17.1 + * 3.11.0 * - * Copyright(c) 2015 Gregory Jacobs - * MIT Licensed. http://www.opensource.org/licenses/mit-license.php + * Copyright(c) 2019 Gregory Jacobs + * MIT License * * https://github.com/gregjacobs/Autolinker.js */ -/** - * @class Autolinker - * @extends Object - * - * Utility class used to process a given string of text, and wrap the matches in - * the appropriate anchor (<a>) tags to turn them into links. - * - * Any of the configuration options may be provided in an Object (map) provided - * to the Autolinker constructor, which will configure how the {@link #link link()} - * method will process the links. - * - * For example: - * - * var autolinker = new Autolinker( { - * newWindow : false, - * truncate : 30 - * } ); - * - * var html = autolinker.link( "Joe went to www.yahoo.com" ); - * // produces: 'Joe went to yahoo.com' - * - * - * The {@link #static-link static link()} method may also be used to inline options into a single call, which may - * be more convenient for one-off uses. For example: - * - * var html = Autolinker.link( "Joe went to www.yahoo.com", { - * newWindow : false, - * truncate : 30 - * } ); - * // produces: 'Joe went to yahoo.com' - * - * - * ## Custom Replacements of Links - * - * If the configuration options do not provide enough flexibility, a {@link #replaceFn} - * may be provided to fully customize the output of Autolinker. This function is - * called once for each URL/Email/Phone#/Twitter Handle/Hashtag match that is - * encountered. - * - * For example: - * - * var input = "..."; // string with URLs, Email Addresses, Phone #s, Twitter Handles, and Hashtags - * - * var linkedText = Autolinker.link( input, { - * replaceFn : function( autolinker, match ) { - * console.log( "href = ", match.getAnchorHref() ); - * console.log( "text = ", match.getAnchorText() ); - * - * switch( match.getType() ) { - * case 'url' : - * console.log( "url: ", match.getUrl() ); - * - * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes - * tag.setAttr( 'rel', 'nofollow' ); - * tag.addClass( 'external-link' ); - * - * return tag; - * - * } else { - * return true; // let Autolinker perform its normal anchor tag replacement - * } - * - * case 'email' : - * var email = match.getEmail(); - * console.log( "email: ", email ); - * - * if( email === "my@own.address" ) { - * return false; // don't auto-link this particular email address; leave as-is - * } else { - * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) - * } - * - * case 'phone' : - * var phoneNumber = match.getPhoneNumber(); - * console.log( phoneNumber ); - * - * return '' + phoneNumber + ''; - * - * case 'twitter' : - * var twitterHandle = match.getTwitterHandle(); - * console.log( twitterHandle ); - * - * return '' + twitterHandle + ''; - * - * case 'hashtag' : - * var hashtag = match.getHashtag(); - * console.log( hashtag ); - * - * return '' + hashtag + ''; - * } - * } - * } ); - * - * - * The function may return the following values: - * - * - `true` (Boolean): Allow Autolinker to replace the match as it normally would. - * - `false` (Boolean): Do not replace the current match at all - leave as-is. - * - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for - * the match. - * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text. - * - * @constructor - * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map). - */ -var Autolinker = function( cfg ) { - Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs. - - // Validate the value of the `hashtag` cfg. - var hashtag = this.hashtag; - if( hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' ) { - throw new Error( "invalid `hashtag` cfg - see docs" ); - } -}; - -Autolinker.prototype = { - constructor : Autolinker, // fix constructor property - - /** - * @cfg {Boolean} urls - * - * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. - */ - urls : true, - - /** - * @cfg {Boolean} email - * - * `true` if email addresses should be automatically linked, `false` if they should not be. - */ - email : true, - - /** - * @cfg {Boolean} twitter - * - * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. - */ - twitter : true, - - /** - * @cfg {Boolean} phone - * - * `true` if Phone numbers ("(555)555-5555") should be automatically linked, `false` if they should not be. - */ - phone: true, - - /** - * @cfg {Boolean/String} hashtag - * - * A string for the service name to have hashtags (ex: "#myHashtag") - * auto-linked to. The currently-supported values are: - * - * - 'twitter' - * - 'facebook' - * - * Pass `false` to skip auto-linking of hashtags. - */ - hashtag : false, - - /** - * @cfg {Boolean} newWindow - * - * `true` if the links should open in a new window, `false` otherwise. - */ - newWindow : true, - - /** - * @cfg {Boolean} stripPrefix - * - * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped - * from the beginning of URL links' text, `false` otherwise. - */ - stripPrefix : true, - - /** - * @cfg {Number} truncate - * - * A number for how many characters long matched text should be truncated to inside the text of - * a link. If the matched text is over this number of characters, it will be truncated to this length by - * adding a two period ellipsis ('..') to the end of the string. - * - * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look - * something like this: 'yahoo.com/some/long/pat..' - */ - truncate : undefined, - - /** - * @cfg {String} className - * - * A CSS class name to add to the generated links. This class will be added to all links, as well as this class - * plus match suffixes for styling url/email/phone/twitter/hashtag links differently. - * - * For example, if this config is provided as "myLink", then: - * - * - URL links will have the CSS classes: "myLink myLink-url" - * - Email links will have the CSS classes: "myLink myLink-email", and - * - Twitter links will have the CSS classes: "myLink myLink-twitter" - * - Phone links will have the CSS classes: "myLink myLink-phone" - * - Hashtag links will have the CSS classes: "myLink myLink-hashtag" - */ - className : "", - - /** - * @cfg {Function} replaceFn - * - * A function to individually process each match found in the input string. - * - * See the class's description for usage. - * - * This function is called with the following parameters: - * - * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such - * as the instance's {@link #getTagBuilder tag builder}). - * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the - * match that the `replaceFn` is currently processing. See {@link Autolinker.match.Match} subclasses for details. - */ - - - /** - * @private - * @property {Autolinker.htmlParser.HtmlParser} htmlParser - * - * The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated - * in the {@link #getHtmlParser} method. - */ - htmlParser : undefined, - - /** - * @private - * @property {Autolinker.matchParser.MatchParser} matchParser - * - * The MatchParser instance used to find matches in the text nodes of an input string passed to - * {@link #link}. This is lazily instantiated in the {@link #getMatchParser} method. - */ - matchParser : undefined, - - /** - * @private - * @property {Autolinker.AnchorTagBuilder} tagBuilder - * - * The AnchorTagBuilder instance used to build match replacement anchor tags. Note: this is lazily instantiated - * in the {@link #getTagBuilder} method. - */ - tagBuilder : undefined, - - /** - * Automatically links URLs, Email addresses, Phone numbers, Twitter - * handles, and Hashtags found in the given chunk of HTML. Does not link - * URLs found within HTML tags. - * - * For instance, if given the text: `You should go to http://www.yahoo.com`, - * then the result will be `You should go to - * <a href="http://www.yahoo.com">http://www.yahoo.com</a>` - * - * This method finds the text around any HTML elements in the input - * `textOrHtml`, which will be the text that is processed. Any original HTML - * elements will be left as-is, as well as the text that is already wrapped - * in anchor (<a>) tags. - * - * @param {String} textOrHtml The HTML or text to autolink matches within - * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, - * {@link #twitter}, and {@link #hashtag} options are enabled). - * @return {String} The HTML, with matches automatically linked. - */ - link : function( textOrHtml ) { - var htmlParser = this.getHtmlParser(), - htmlNodes = htmlParser.parse( textOrHtml ), - anchorTagStackCount = 0, // used to only process text around anchor tags, and any inner text/html they may have - resultHtml = []; - - for( var i = 0, len = htmlNodes.length; i < len; i++ ) { - var node = htmlNodes[ i ], - nodeType = node.getType(), - nodeText = node.getText(); - - if( nodeType === 'element' ) { - // Process HTML nodes in the input `textOrHtml` - if( node.getTagName() === 'a' ) { - if( !node.isClosing() ) { // it's the start tag - anchorTagStackCount++; - } else { // it's the end tag - anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0 - } - } - resultHtml.push( nodeText ); // now add the text of the tag itself verbatim - - } else if( nodeType === 'entity' || nodeType === 'comment' ) { - resultHtml.push( nodeText ); // append HTML entity nodes (such as ' ') or HTML comments (such as '') verbatim - - } else { - // Process text nodes in the input `textOrHtml` - if( anchorTagStackCount === 0 ) { - // If we're not within an tag, process the text node to linkify - var linkifiedStr = this.linkifyStr( nodeText ); - resultHtml.push( linkifiedStr ); - - } else { - // `text` is within an tag, simply append the text - we do not want to autolink anything - // already within an ... tag - resultHtml.push( nodeText ); - } - } - } - - return resultHtml.join( "" ); - }, - - /** - * Process the text that lies in between HTML tags, performing the anchor - * tag replacements for the matches, and returns the string with the - * replacements made. - * - * This method does the actual wrapping of matches with anchor tags. - * - * @private - * @param {String} str The string of text to auto-link. - * @return {String} The text with anchor tags auto-filled. - */ - linkifyStr : function( str ) { - return this.getMatchParser().replace( str, this.createMatchReturnVal, this ); - }, - - - /** - * Creates the return string value for a given match in the input string, - * for the {@link #linkifyStr} method. - * - * This method handles the {@link #replaceFn}, if one was provided. - * - * @private - * @param {Autolinker.match.Match} match The Match object that represents the match. - * @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but - * may be the `matchStr` itself if the match is not to be replaced. - */ - createMatchReturnVal : function( match ) { - // Handle a custom `replaceFn` being provided - var replaceFnResult; - if( this.replaceFn ) { - replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg - } - - if( typeof replaceFnResult === 'string' ) { - return replaceFnResult; // `replaceFn` returned a string, use that - - } else if( replaceFnResult === false ) { - return match.getMatchedText(); // no replacement for the match - - } else if( replaceFnResult instanceof Autolinker.HtmlTag ) { - return replaceFnResult.toAnchorString(); - - } else { // replaceFnResult === true, or no/unknown return value from function - // Perform Autolinker's default anchor tag generation - var tagBuilder = this.getTagBuilder(), - anchorTag = tagBuilder.build( match ); // returns an Autolinker.HtmlTag instance - - return anchorTag.toAnchorString(); - } - }, - - - /** - * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance. - * - * @protected - * @return {Autolinker.htmlParser.HtmlParser} - */ - getHtmlParser : function() { - var htmlParser = this.htmlParser; - - if( !htmlParser ) { - htmlParser = this.htmlParser = new Autolinker.htmlParser.HtmlParser(); - } - - return htmlParser; - }, - - - /** - * Lazily instantiates and returns the {@link #matchParser} instance for this Autolinker instance. - * - * @protected - * @return {Autolinker.matchParser.MatchParser} - */ - getMatchParser : function() { - var matchParser = this.matchParser; - - if( !matchParser ) { - matchParser = this.matchParser = new Autolinker.matchParser.MatchParser( { - urls : this.urls, - email : this.email, - twitter : this.twitter, - phone : this.phone, - hashtag : this.hashtag, - stripPrefix : this.stripPrefix - } ); - } - - return matchParser; - }, - - - /** - * Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it - * if it does not yet exist. - * - * This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that - * Autolinker would normally generate, and then allow for modifications before returning it. For example: - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance - * tag.setAttr( 'rel', 'nofollow' ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test google.com - * - * @return {Autolinker.AnchorTagBuilder} - */ - getTagBuilder : function() { - var tagBuilder = this.tagBuilder; - - if( !tagBuilder ) { - tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( { - newWindow : this.newWindow, - truncate : this.truncate, - className : this.className - } ); - } - - return tagBuilder; - } - -}; - - -/** - * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles, - * and Hashtags found in the given chunk of HTML. Does not link URLs found - * within HTML tags. - * - * For instance, if given the text: `You should go to http://www.yahoo.com`, - * then the result will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` - * - * Example: - * - * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } ); - * // Produces: "Go to google.com" - * - * @static - * @param {String} textOrHtml The HTML or text to find matches within (depending - * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter}, - * and {@link #hashtag} options are enabled). - * @param {Object} [options] Any of the configuration options for the Autolinker - * class, specified in an Object (map). See the class description for an - * example call. - * @return {String} The HTML text, with matches automatically linked. - */ -Autolinker.link = function( textOrHtml, options ) { - var autolinker = new Autolinker( options ); - return autolinker.link( textOrHtml ); -}; - - -// Autolinker Namespaces -Autolinker.match = {}; -Autolinker.htmlParser = {}; -Autolinker.matchParser = {}; - -/*global Autolinker */ -/*jshint eqnull:true, boss:true */ -/** - * @class Autolinker.Util - * @singleton - * - * A few utility methods for Autolinker. - */ -Autolinker.Util = { - - /** - * @property {Function} abstractMethod - * - * A function object which represents an abstract method. - */ - abstractMethod : function() { throw "abstract"; }, - - - /** - * @private - * @property {RegExp} trimRegex - * - * The regular expression used to trim the leading and trailing whitespace - * from a string. - */ - trimRegex : /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - - /** - * Assigns (shallow copies) the properties of `src` onto `dest`. - * - * @param {Object} dest The destination object. - * @param {Object} src The source object. - * @return {Object} The destination object (`dest`) - */ - assign : function( dest, src ) { - for( var prop in src ) { - if( src.hasOwnProperty( prop ) ) { - dest[ prop ] = src[ prop ]; - } - } - - return dest; - }, - - - /** - * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype. - * - * @param {Function} superclass The constructor function for the superclass. - * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the - * special property `constructor`, which will be used as the new subclass's constructor function. - * @return {Function} The new subclass function. - */ - extend : function( superclass, protoProps ) { - var superclassProto = superclass.prototype; - - var F = function() {}; - F.prototype = superclassProto; - - var subclass; - if( protoProps.hasOwnProperty( 'constructor' ) ) { - subclass = protoProps.constructor; - } else { - subclass = function() { superclassProto.constructor.apply( this, arguments ); }; - } - - var subclassProto = subclass.prototype = new F(); // set up prototype chain - subclassProto.constructor = subclass; // fix constructor property - subclassProto.superclass = superclassProto; - - delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there - Autolinker.Util.assign( subclassProto, protoProps ); - - return subclass; - }, - - - /** - * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the - * end of the string (by default, two periods: '..'). If the `str` length does not exceed - * `len`, the string will be returned unchanged. - * - * @param {String} str The string to truncate and add an ellipsis to. - * @param {Number} truncateLen The length to truncate the string at. - * @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str` - * when truncated. Defaults to '..' - */ - ellipsis : function( str, truncateLen, ellipsisChars ) { - if( str.length > truncateLen ) { - ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars; - str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars; - } - return str; - }, - - - /** - * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below). - * - * @param {Array} arr The array to find an element of. - * @param {*} element The element to find in the array, and return the index of. - * @return {Number} The index of the `element`, or -1 if it was not found. - */ - indexOf : function( arr, element ) { - if( Array.prototype.indexOf ) { - return arr.indexOf( element ); - - } else { - for( var i = 0, len = arr.length; i < len; i++ ) { - if( arr[ i ] === element ) return i; - } - return -1; - } - }, - - - - /** - * Performs the functionality of what modern browsers do when `String.prototype.split()` is called - * with a regular expression that contains capturing parenthesis. - * - * For example: - * - * // Modern browsers: - * "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ] - * - * // Old IE (including IE8): - * "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ] - * - * This method emulates the functionality of modern browsers for the old IE case. - * - * @param {String} str The string to split. - * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting - * character(s) will be spliced into the array, as in the "modern browsers" example in the - * description of this method. - * Note #1: the supplied regular expression **must** have the 'g' flag specified. - * Note #2: for simplicity's sake, the regular expression does not need - * to contain capturing parenthesis - it will be assumed that any match has them. - * @return {String[]} The split array of strings, with the splitting character(s) included. - */ - splitAndCapture : function( str, splitRegex ) { - if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" ); - - var result = [], - lastIdx = 0, - match; - - while( match = splitRegex.exec( str ) ) { - result.push( str.substring( lastIdx, match.index ) ); - result.push( match[ 0 ] ); // push the splitting char(s) - - lastIdx = match.index + match[ 0 ].length; - } - result.push( str.substring( lastIdx ) ); - - return result; - }, - - - /** - * Trims the leading and trailing whitespace from a string. - * - * @param {String} str The string to trim. - * @return {String} - */ - trim : function( str ) { - return str.replace( this.trimRegex, '' ); - } - -}; -/*global Autolinker */ -/*jshint boss:true */ -/** - * @class Autolinker.HtmlTag - * @extends Object - * - * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically. - * - * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use - * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}. - * - * ## Examples - * - * Example instantiation: - * - * var tag = new Autolinker.HtmlTag( { - * tagName : 'a', - * attrs : { 'href': 'http://google.com', 'class': 'external-link' }, - * innerHtml : 'Google' - * } ); - * - * tag.toAnchorString(); // Google - * - * // Individual accessor methods - * tag.getTagName(); // 'a' - * tag.getAttr( 'href' ); // 'http://google.com' - * tag.hasClass( 'external-link' ); // true - * - * - * Using mutator methods (which may be used in combination with instantiation config properties): - * - * var tag = new Autolinker.HtmlTag(); - * tag.setTagName( 'a' ); - * tag.setAttr( 'href', 'http://google.com' ); - * tag.addClass( 'external-link' ); - * tag.setInnerHtml( 'Google' ); - * - * tag.getTagName(); // 'a' - * tag.getAttr( 'href' ); // 'http://google.com' - * tag.hasClass( 'external-link' ); // true - * - * tag.toAnchorString(); // Google - * - * - * ## Example use within a {@link Autolinker#replaceFn replaceFn} - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text - * tag.setAttr( 'rel', 'nofollow' ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test google.com - * - * - * ## Example use with a new tag for the replacement - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = new Autolinker.HtmlTag( { - * tagName : 'button', - * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, - * innerHtml : 'Load URL: ' + match.getAnchorText() - * } ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test - */ -Autolinker.HtmlTag = Autolinker.Util.extend( Object, { - - /** - * @cfg {String} tagName - * - * The tag name. Ex: 'a', 'button', etc. - * - * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString} - * is executed. - */ - - /** - * @cfg {Object.} attrs - * - * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the - * values are the attribute values. - */ - - /** - * @cfg {String} innerHtml - * - * The inner HTML for the tag. - * - * Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym - * naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML} - * if you prefer, but this one is recommended. - */ - - /** - * @cfg {String} innerHTML - * - * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version - * for acronym names. - */ - - - /** - * @protected - * @property {RegExp} whitespaceRegex - * - * Regular expression used to match whitespace in a string of CSS classes. - */ - whitespaceRegex : /\s+/, - - - /** - * @constructor - * @param {Object} [cfg] The configuration properties for this class, in an Object (map) - */ - constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); - - this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym - }, - - - /** - * Sets the tag name that will be used to generate the tag with. - * - * @param {String} tagName - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setTagName : function( tagName ) { - this.tagName = tagName; - return this; - }, - - - /** - * Retrieves the tag name. - * - * @return {String} - */ - getTagName : function() { - return this.tagName || ""; - }, - - - /** - * Sets an attribute on the HtmlTag. - * - * @param {String} attrName The attribute name to set. - * @param {String} attrValue The attribute value to set. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setAttr : function( attrName, attrValue ) { - var tagAttrs = this.getAttrs(); - tagAttrs[ attrName ] = attrValue; - - return this; - }, - - - /** - * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`. - * - * @param {String} name The attribute name to retrieve. - * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag. - */ - getAttr : function( attrName ) { - return this.getAttrs()[ attrName ]; - }, - - - /** - * Sets one or more attributes on the HtmlTag. - * - * @param {Object.} attrs A key/value Object (map) of the attributes to set. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setAttrs : function( attrs ) { - var tagAttrs = this.getAttrs(); - Autolinker.Util.assign( tagAttrs, attrs ); - - return this; - }, - - - /** - * Retrieves the attributes Object (map) for the HtmlTag. - * - * @return {Object.} A key/value object of the attributes for the HtmlTag. - */ - getAttrs : function() { - return this.attrs || ( this.attrs = {} ); - }, - - - /** - * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag. - * - * @param {String} cssClass One or more space-separated CSS classes to set (overwrite). - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setClass : function( cssClass ) { - return this.setAttr( 'class', cssClass ); - }, - - - /** - * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes. - * - * @param {String} cssClass One or more space-separated CSS classes to add. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - addClass : function( cssClass ) { - var classAttr = this.getClass(), - whitespaceRegex = this.whitespaceRegex, - indexOf = Autolinker.Util.indexOf, // to support IE8 and below - classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), - newClasses = cssClass.split( whitespaceRegex ), - newClass; - - while( newClass = newClasses.shift() ) { - if( indexOf( classes, newClass ) === -1 ) { - classes.push( newClass ); - } - } - - this.getAttrs()[ 'class' ] = classes.join( " " ); - return this; - }, - - - /** - * Convenience method to remove one or more CSS classes from the HtmlTag. - * - * @param {String} cssClass One or more space-separated CSS classes to remove. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - removeClass : function( cssClass ) { - var classAttr = this.getClass(), - whitespaceRegex = this.whitespaceRegex, - indexOf = Autolinker.Util.indexOf, // to support IE8 and below - classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), - removeClasses = cssClass.split( whitespaceRegex ), - removeClass; - - while( classes.length && ( removeClass = removeClasses.shift() ) ) { - var idx = indexOf( classes, removeClass ); - if( idx !== -1 ) { - classes.splice( idx, 1 ); - } - } - - this.getAttrs()[ 'class' ] = classes.join( " " ); - return this; - }, - - - /** - * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when - * there are multiple. - * - * @return {String} - */ - getClass : function() { - return this.getAttrs()[ 'class' ] || ""; - }, - - - /** - * Convenience method to check if the tag has a CSS class or not. - * - * @param {String} cssClass The CSS class to check for. - * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise. - */ - hasClass : function( cssClass ) { - return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1; - }, - - - /** - * Sets the inner HTML for the tag. - * - * @param {String} html The inner HTML to set. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setInnerHtml : function( html ) { - this.innerHtml = html; - - return this; - }, - - - /** - * Retrieves the inner HTML for the tag. - * - * @return {String} - */ - getInnerHtml : function() { - return this.innerHtml || ""; - }, - - - /** - * Override of superclass method used to generate the HTML string for the tag. - * - * @return {String} - */ - toAnchorString : function() { - var tagName = this.getTagName(), - attrsStr = this.buildAttrsStr(); - - attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes - - return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '' ].join( "" ); - }, - - - /** - * Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate - * the stringified HtmlTag. - * - * @protected - * @return {String} Example return: `attr1="value1" attr2="value2"` - */ - buildAttrsStr : function() { - if( !this.attrs ) return ""; // no `attrs` Object (map) has been set, return empty string - - var attrs = this.getAttrs(), - attrsArr = []; - - for( var prop in attrs ) { - if( attrs.hasOwnProperty( prop ) ) { - attrsArr.push( prop + '="' + attrs[ prop ] + '"' ); - } - } - return attrsArr.join( " " ); - } - -} ); - -/*global Autolinker */ -/*jshint sub:true */ -/** - * @protected - * @class Autolinker.AnchorTagBuilder - * @extends Object - * - * Builds anchor (<a>) tags for the Autolinker utility when a match is found. - * - * Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may - * actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances - * which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example: - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance - * tag.setAttr( 'rel', 'nofollow' ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test google.com - */ -Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { - - /** - * @cfg {Boolean} newWindow - * @inheritdoc Autolinker#newWindow - */ +(function (global, factory) { + global.Autolinker = factory(); +}(tmp, function () { 'use strict'; + + /** + * Assigns (shallow copies) the properties of `src` onto `dest`, if the + * corresponding property on `dest` === `undefined`. + * + * @param {Object} dest The destination object. + * @param {Object} src The source object. + * @return {Object} The destination object (`dest`) + */ + function defaults(dest, src) { + for (var prop in src) { + if (src.hasOwnProperty(prop) && dest[prop] === undefined) { + dest[prop] = src[prop]; + } + } + return dest; + } + /** + * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the + * end of the string (by default, two periods: '..'). If the `str` length does not exceed + * `len`, the string will be returned unchanged. + * + * @param {String} str The string to truncate and add an ellipsis to. + * @param {Number} truncateLen The length to truncate the string at. + * @param {String} [ellipsisChars=...] The ellipsis character(s) to add to the end of `str` + * when truncated. Defaults to '...' + */ + function ellipsis(str, truncateLen, ellipsisChars) { + var ellipsisLength; + if (str.length > truncateLen) { + if (ellipsisChars == null) { + ellipsisChars = '…'; + ellipsisLength = 3; + } + else { + ellipsisLength = ellipsisChars.length; + } + str = str.substring(0, truncateLen - ellipsisLength) + ellipsisChars; + } + return str; + } + /** + * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below). + * + * @param {Array} arr The array to find an element of. + * @param {*} element The element to find in the array, and return the index of. + * @return {Number} The index of the `element`, or -1 if it was not found. + */ + function indexOf(arr, element) { + if (Array.prototype.indexOf) { + return arr.indexOf(element); + } + else { + for (var i = 0, len = arr.length; i < len; i++) { + if (arr[i] === element) + return i; + } + return -1; + } + } + /** + * Removes array elements based on a filtering function. Mutates the input + * array. + * + * Using this instead of the ES5 Array.prototype.filter() function, to allow + * Autolinker compatibility with IE8, and also to prevent creating many new + * arrays in memory for filtering. + * + * @param {Array} arr The array to remove elements from. This array is + * mutated. + * @param {Function} fn A function which should return `true` to + * remove an element. + * @return {Array} The mutated input `arr`. + */ + function remove(arr, fn) { + for (var i = arr.length - 1; i >= 0; i--) { + if (fn(arr[i]) === true) { + arr.splice(i, 1); + } + } + } + /** + * Performs the functionality of what modern browsers do when `String.prototype.split()` is called + * with a regular expression that contains capturing parenthesis. + * + * For example: + * + * // Modern browsers: + * "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ] + * + * // Old IE (including IE8): + * "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ] + * + * This method emulates the functionality of modern browsers for the old IE case. + * + * @param {String} str The string to split. + * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting + * character(s) will be spliced into the array, as in the "modern browsers" example in the + * description of this method. + * Note #1: the supplied regular expression **must** have the 'g' flag specified. + * Note #2: for simplicity's sake, the regular expression does not need + * to contain capturing parenthesis - it will be assumed that any match has them. + * @return {String[]} The split array of strings, with the splitting character(s) included. + */ + function splitAndCapture(str, splitRegex) { + if (!splitRegex.global) + throw new Error("`splitRegex` must have the 'g' flag set"); + var result = [], lastIdx = 0, match; + while (match = splitRegex.exec(str)) { + result.push(str.substring(lastIdx, match.index)); + result.push(match[0]); // push the splitting char(s) + lastIdx = match.index + match[0].length; + } + result.push(str.substring(lastIdx)); + return result; + } + /** + * Function that should never be called but is used to check that every + * enum value is handled using TypeScript's 'never' type. + */ + function throwUnhandledCaseError(theValue) { + throw new Error("Unhandled case for value: '" + theValue + "'"); + } + + /** + * @class Autolinker.HtmlTag + * @extends Object + * + * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically. + * + * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use + * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}. + * + * ## Examples + * + * Example instantiation: + * + * var tag = new Autolinker.HtmlTag( { + * tagName : 'a', + * attrs : { 'href': 'http://google.com', 'class': 'external-link' }, + * innerHtml : 'Google' + * } ); + * + * tag.toAnchorString(); // Google + * + * // Individual accessor methods + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * + * Using mutator methods (which may be used in combination with instantiation config properties): + * + * var tag = new Autolinker.HtmlTag(); + * tag.setTagName( 'a' ); + * tag.setAttr( 'href', 'http://google.com' ); + * tag.addClass( 'external-link' ); + * tag.setInnerHtml( 'Google' ); + * + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * tag.toAnchorString(); // Google + * + * + * ## Example use within a {@link Autolinker#replaceFn replaceFn} + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( match ) { + * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + * + * + * ## Example use with a new tag for the replacement + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( match ) { + * var tag = new Autolinker.HtmlTag( { + * tagName : 'button', + * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, + * innerHtml : 'Load URL: ' + match.getAnchorText() + * } ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test + */ + var HtmlTag = /** @class */ (function () { + /** + * @method constructor + * @param {Object} [cfg] The configuration properties for this class, in an Object (map) + */ + function HtmlTag(cfg) { + if (cfg === void 0) { cfg = {}; } + /** + * @cfg {String} tagName + * + * The tag name. Ex: 'a', 'button', etc. + * + * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString} + * is executed. + */ + this.tagName = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Object.} attrs + * + * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the + * values are the attribute values. + */ + this.attrs = {}; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {String} innerHTML + * + * The inner HTML for the tag. + */ + this.innerHTML = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @protected + * @property {RegExp} whitespaceRegex + * + * Regular expression used to match whitespace in a string of CSS classes. + */ + this.whitespaceRegex = /\s+/; // default value just to get the above doc comment in the ES5 output and documentation generator + this.tagName = cfg.tagName || ''; + this.attrs = cfg.attrs || {}; + this.innerHTML = cfg.innerHtml || cfg.innerHTML || ''; // accept either the camelCased form or the fully capitalized acronym as in the DOM + } + /** + * Sets the tag name that will be used to generate the tag with. + * + * @param {String} tagName + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.setTagName = function (tagName) { + this.tagName = tagName; + return this; + }; + /** + * Retrieves the tag name. + * + * @return {String} + */ + HtmlTag.prototype.getTagName = function () { + return this.tagName || ''; + }; + /** + * Sets an attribute on the HtmlTag. + * + * @param {String} attrName The attribute name to set. + * @param {String} attrValue The attribute value to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.setAttr = function (attrName, attrValue) { + var tagAttrs = this.getAttrs(); + tagAttrs[attrName] = attrValue; + return this; + }; + /** + * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`. + * + * @param {String} attrName The attribute name to retrieve. + * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag. + */ + HtmlTag.prototype.getAttr = function (attrName) { + return this.getAttrs()[attrName]; + }; + /** + * Sets one or more attributes on the HtmlTag. + * + * @param {Object.} attrs A key/value Object (map) of the attributes to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.setAttrs = function (attrs) { + Object.assign(this.getAttrs(), attrs); + return this; + }; + /** + * Retrieves the attributes Object (map) for the HtmlTag. + * + * @return {Object.} A key/value object of the attributes for the HtmlTag. + */ + HtmlTag.prototype.getAttrs = function () { + return this.attrs || (this.attrs = {}); + }; + /** + * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to set (overwrite). + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.setClass = function (cssClass) { + return this.setAttr('class', cssClass); + }; + /** + * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes. + * + * @param {String} cssClass One or more space-separated CSS classes to add. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.addClass = function (cssClass) { + var classAttr = this.getClass(), whitespaceRegex = this.whitespaceRegex, classes = (!classAttr) ? [] : classAttr.split(whitespaceRegex), newClasses = cssClass.split(whitespaceRegex), newClass; + while (newClass = newClasses.shift()) { + if (indexOf(classes, newClass) === -1) { + classes.push(newClass); + } + } + this.getAttrs()['class'] = classes.join(" "); + return this; + }; + /** + * Convenience method to remove one or more CSS classes from the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to remove. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.removeClass = function (cssClass) { + var classAttr = this.getClass(), whitespaceRegex = this.whitespaceRegex, classes = (!classAttr) ? [] : classAttr.split(whitespaceRegex), removeClasses = cssClass.split(whitespaceRegex), removeClass; + while (classes.length && (removeClass = removeClasses.shift())) { + var idx = indexOf(classes, removeClass); + if (idx !== -1) { + classes.splice(idx, 1); + } + } + this.getAttrs()['class'] = classes.join(" "); + return this; + }; + /** + * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when + * there are multiple. + * + * @return {String} + */ + HtmlTag.prototype.getClass = function () { + return this.getAttrs()['class'] || ""; + }; + /** + * Convenience method to check if the tag has a CSS class or not. + * + * @param {String} cssClass The CSS class to check for. + * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise. + */ + HtmlTag.prototype.hasClass = function (cssClass) { + return (' ' + this.getClass() + ' ').indexOf(' ' + cssClass + ' ') !== -1; + }; + /** + * Sets the inner HTML for the tag. + * + * @param {String} html The inner HTML to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.setInnerHTML = function (html) { + this.innerHTML = html; + return this; + }; + /** + * Backwards compatibility method name. + * + * @param {String} html The inner HTML to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + HtmlTag.prototype.setInnerHtml = function (html) { + return this.setInnerHTML(html); + }; + /** + * Retrieves the inner HTML for the tag. + * + * @return {String} + */ + HtmlTag.prototype.getInnerHTML = function () { + return this.innerHTML || ""; + }; + /** + * Backward compatibility method name. + * + * @return {String} + */ + HtmlTag.prototype.getInnerHtml = function () { + return this.getInnerHTML(); + }; + /** + * Override of superclass method used to generate the HTML string for the tag. + * + * @return {String} + */ + HtmlTag.prototype.toAnchorString = function () { + var tagName = this.getTagName(), attrsStr = this.buildAttrsStr(); + attrsStr = (attrsStr) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes + return ['<', tagName, attrsStr, '>', this.getInnerHtml(), ''].join(""); + }; + /** + * Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate + * the stringified HtmlTag. + * + * @protected + * @return {String} Example return: `attr1="value1" attr2="value2"` + */ + HtmlTag.prototype.buildAttrsStr = function () { + if (!this.attrs) + return ""; // no `attrs` Object (map) has been set, return empty string + var attrs = this.getAttrs(), attrsArr = []; + for (var prop in attrs) { + if (attrs.hasOwnProperty(prop)) { + attrsArr.push(prop + '="' + attrs[prop] + '"'); + } + } + return attrsArr.join(" "); + }; + return HtmlTag; + }()); + + /** + * Date: 2015-10-05 + * Author: Kasper Søfren (https://github.com/kafoso) + * + * A truncation feature, where the ellipsis will be placed at a section within + * the URL making it still somewhat human readable. + * + * @param {String} url A URL. + * @param {Number} truncateLen The maximum length of the truncated output URL string. + * @param {String} ellipsisChars The characters to place within the url, e.g. "...". + * @return {String} The truncated URL. + */ + function truncateSmart(url, truncateLen, ellipsisChars) { + var ellipsisLengthBeforeParsing; + var ellipsisLength; + if (ellipsisChars == null) { + ellipsisChars = '…'; + ellipsisLength = 3; + ellipsisLengthBeforeParsing = 8; + } + else { + ellipsisLength = ellipsisChars.length; + ellipsisLengthBeforeParsing = ellipsisChars.length; + } + var parse_url = function (url) { + var urlObj = {}; + var urlSub = url; + var match = urlSub.match(/^([a-z]+):\/\//i); + if (match) { + urlObj.scheme = match[1]; + urlSub = urlSub.substr(match[0].length); + } + match = urlSub.match(/^(.*?)(?=(\?|#|\/|$))/i); + if (match) { + urlObj.host = match[1]; + urlSub = urlSub.substr(match[0].length); + } + match = urlSub.match(/^\/(.*?)(?=(\?|#|$))/i); + if (match) { + urlObj.path = match[1]; + urlSub = urlSub.substr(match[0].length); + } + match = urlSub.match(/^\?(.*?)(?=(#|$))/i); + if (match) { + urlObj.query = match[1]; + urlSub = urlSub.substr(match[0].length); + } + match = urlSub.match(/^#(.*?)$/i); + if (match) { + urlObj.fragment = match[1]; + //urlSub = urlSub.substr(match[0].length); -- not used. Uncomment if adding another block. + } + return urlObj; + }; + var buildUrl = function (urlObj) { + var url = ""; + if (urlObj.scheme && urlObj.host) { + url += urlObj.scheme + "://"; + } + if (urlObj.host) { + url += urlObj.host; + } + if (urlObj.path) { + url += "/" + urlObj.path; + } + if (urlObj.query) { + url += "?" + urlObj.query; + } + if (urlObj.fragment) { + url += "#" + urlObj.fragment; + } + return url; + }; + var buildSegment = function (segment, remainingAvailableLength) { + var remainingAvailableLengthHalf = remainingAvailableLength / 2, startOffset = Math.ceil(remainingAvailableLengthHalf), endOffset = (-1) * Math.floor(remainingAvailableLengthHalf), end = ""; + if (endOffset < 0) { + end = segment.substr(endOffset); + } + return segment.substr(0, startOffset) + ellipsisChars + end; + }; + if (url.length <= truncateLen) { + return url; + } + var availableLength = truncateLen - ellipsisLength; + var urlObj = parse_url(url); + // Clean up the URL + if (urlObj.query) { + var matchQuery = urlObj.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i); + if (matchQuery) { + // Malformed URL; two or more "?". Removed any content behind the 2nd. + urlObj.query = urlObj.query.substr(0, matchQuery[1].length); + url = buildUrl(urlObj); + } + } + if (url.length <= truncateLen) { + return url; + } + if (urlObj.host) { + urlObj.host = urlObj.host.replace(/^www\./, ""); + url = buildUrl(urlObj); + } + if (url.length <= truncateLen) { + return url; + } + // Process and build the URL + var str = ""; + if (urlObj.host) { + str += urlObj.host; + } + if (str.length >= availableLength) { + if (urlObj.host.length == truncateLen) { + return (urlObj.host.substr(0, (truncateLen - ellipsisLength)) + ellipsisChars).substr(0, availableLength + ellipsisLengthBeforeParsing); + } + return buildSegment(str, availableLength).substr(0, availableLength + ellipsisLengthBeforeParsing); + } + var pathAndQuery = ""; + if (urlObj.path) { + pathAndQuery += "/" + urlObj.path; + } + if (urlObj.query) { + pathAndQuery += "?" + urlObj.query; + } + if (pathAndQuery) { + if ((str + pathAndQuery).length >= availableLength) { + if ((str + pathAndQuery).length == truncateLen) { + return (str + pathAndQuery).substr(0, truncateLen); + } + var remainingAvailableLength = availableLength - str.length; + return (str + buildSegment(pathAndQuery, remainingAvailableLength)).substr(0, availableLength + ellipsisLengthBeforeParsing); + } + else { + str += pathAndQuery; + } + } + if (urlObj.fragment) { + var fragment = "#" + urlObj.fragment; + if ((str + fragment).length >= availableLength) { + if ((str + fragment).length == truncateLen) { + return (str + fragment).substr(0, truncateLen); + } + var remainingAvailableLength2 = availableLength - str.length; + return (str + buildSegment(fragment, remainingAvailableLength2)).substr(0, availableLength + ellipsisLengthBeforeParsing); + } + else { + str += fragment; + } + } + if (urlObj.scheme && urlObj.host) { + var scheme = urlObj.scheme + "://"; + if ((str + scheme).length < availableLength) { + return (scheme + str).substr(0, truncateLen); + } + } + if (str.length <= truncateLen) { + return str; + } + var end = ""; + if (availableLength > 0) { + end = str.substr((-1) * Math.floor(availableLength / 2)); + } + return (str.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing); + } + + /** + * Date: 2015-10-05 + * Author: Kasper Søfren (https://github.com/kafoso) + * + * A truncation feature, where the ellipsis will be placed in the dead-center of the URL. + * + * @param {String} url A URL. + * @param {Number} truncateLen The maximum length of the truncated output URL string. + * @param {String} ellipsisChars The characters to place within the url, e.g. "..". + * @return {String} The truncated URL. + */ + function truncateMiddle(url, truncateLen, ellipsisChars) { + if (url.length <= truncateLen) { + return url; + } + var ellipsisLengthBeforeParsing; + var ellipsisLength; + if (ellipsisChars == null) { + ellipsisChars = '…'; + ellipsisLengthBeforeParsing = 8; + ellipsisLength = 3; + } + else { + ellipsisLengthBeforeParsing = ellipsisChars.length; + ellipsisLength = ellipsisChars.length; + } + var availableLength = truncateLen - ellipsisLength; + var end = ""; + if (availableLength > 0) { + end = url.substr((-1) * Math.floor(availableLength / 2)); + } + return (url.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing); + } + + /** + * A truncation feature where the ellipsis will be placed at the end of the URL. + * + * @param {String} anchorText + * @param {Number} truncateLen The maximum length of the truncated output URL string. + * @param {String} ellipsisChars The characters to place within the url, e.g. "..". + * @return {String} The truncated URL. + */ + function truncateEnd(anchorText, truncateLen, ellipsisChars) { + return ellipsis(anchorText, truncateLen, ellipsisChars); + } + + /** + * @protected + * @class Autolinker.AnchorTagBuilder + * @extends Object + * + * Builds anchor (<a>) tags for the Autolinker utility when a match is + * found. + * + * Normally this class is instantiated, configured, and used internally by an + * {@link Autolinker} instance, but may actually be used indirectly in a + * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} + * instances which may be modified before returning from the + * {@link Autolinker#replaceFn replaceFn}. For example: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( match ) { + * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + */ + var AnchorTagBuilder = /** @class */ (function () { + /** + * @method constructor + * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). + */ + function AnchorTagBuilder(cfg) { + if (cfg === void 0) { cfg = {}; } + /** + * @cfg {Boolean} newWindow + * @inheritdoc Autolinker#newWindow + */ + this.newWindow = false; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Object} truncate + * @inheritdoc Autolinker#truncate + */ + this.truncate = {}; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {String} className + * @inheritdoc Autolinker#className + */ + this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + this.newWindow = cfg.newWindow || false; + this.truncate = cfg.truncate || {}; + this.className = cfg.className || ''; + } + /** + * Generates the actual anchor (<a>) tag to use in place of the + * matched text, via its `match` object. + * + * @param {Autolinker.match.Match} match The Match instance to generate an + * anchor tag from. + * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag. + */ + AnchorTagBuilder.prototype.build = function (match) { + return new HtmlTag({ + tagName: 'a', + attrs: this.createAttrs(match), + innerHtml: this.processAnchorText(match.getAnchorText()) + }); + }; + /** + * Creates the Object (map) of the HTML attributes for the anchor (<a>) + * tag being generated. + * + * @protected + * @param {Autolinker.match.Match} match The Match instance to generate an + * anchor tag from. + * @return {Object} A key/value Object (map) of the anchor tag's attributes. + */ + AnchorTagBuilder.prototype.createAttrs = function (match) { + var attrs = { + 'href': match.getAnchorHref() // we'll always have the `href` attribute + }; + var cssClass = this.createCssClass(match); + if (cssClass) { + attrs['class'] = cssClass; + } + if (this.newWindow) { + attrs['target'] = "_blank"; + attrs['rel'] = "noopener noreferrer"; // Issue #149. See https://mathiasbynens.github.io/rel-noopener/ + } + if (this.truncate) { + if (this.truncate.length && this.truncate.length < match.getAnchorText().length) { + attrs['title'] = match.getAnchorHref(); + } + } + return attrs; + }; + /** + * Creates the CSS class that will be used for a given anchor tag, based on + * the `matchType` and the {@link #className} config. + * + * Example returns: + * + * - "" // no {@link #className} + * - "myLink myLink-url" // url match + * - "myLink myLink-email" // email match + * - "myLink myLink-phone" // phone match + * - "myLink myLink-hashtag" // hashtag match + * - "myLink myLink-mention myLink-twitter" // mention match with Twitter service + * + * @protected + * @param {Autolinker.match.Match} match The Match instance to generate an + * anchor tag from. + * @return {String} The CSS class string for the link. Example return: + * "myLink myLink-url". If no {@link #className} was configured, returns + * an empty string. + */ + AnchorTagBuilder.prototype.createCssClass = function (match) { + var className = this.className; + if (!className) { + return ""; + } + else { + var returnClasses = [className], cssClassSuffixes = match.getCssClassSuffixes(); + for (var i = 0, len = cssClassSuffixes.length; i < len; i++) { + returnClasses.push(className + '-' + cssClassSuffixes[i]); + } + return returnClasses.join(' '); + } + }; + /** + * Processes the `anchorText` by truncating the text according to the + * {@link #truncate} config. + * + * @private + * @param {String} anchorText The anchor tag's text (i.e. what will be + * displayed). + * @return {String} The processed `anchorText`. + */ + AnchorTagBuilder.prototype.processAnchorText = function (anchorText) { + anchorText = this.doTruncate(anchorText); + return anchorText; + }; + /** + * Performs the truncation of the `anchorText` based on the {@link #truncate} + * option. If the `anchorText` is longer than the length specified by the + * {@link #truncate} option, the truncation is performed based on the + * `location` property. See {@link #truncate} for details. + * + * @private + * @param {String} anchorText The anchor tag's text (i.e. what will be + * displayed). + * @return {String} The truncated anchor text. + */ + AnchorTagBuilder.prototype.doTruncate = function (anchorText) { + var truncate = this.truncate; + if (!truncate || !truncate.length) + return anchorText; + var truncateLength = truncate.length, truncateLocation = truncate.location; + if (truncateLocation === 'smart') { + return truncateSmart(anchorText, truncateLength); + } + else if (truncateLocation === 'middle') { + return truncateMiddle(anchorText, truncateLength); + } + else { + return truncateEnd(anchorText, truncateLength); + } + }; + return AnchorTagBuilder; + }()); + + /** + * @abstract + * @class Autolinker.match.Match + * + * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a + * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match. + * + * For example: + * + * var input = "..."; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram, Soundcloud) + * + * var linkedText = Autolinker.link( input, { + * replaceFn : function( match ) { + * console.log( "href = ", match.getAnchorHref() ); + * console.log( "text = ", match.getAnchorText() ); + * + * switch( match.getType() ) { + * case 'url' : + * console.log( "url: ", match.getUrl() ); + * + * case 'email' : + * console.log( "email: ", match.getEmail() ); + * + * case 'mention' : + * console.log( "mention: ", match.getMention() ); + * } + * } + * } ); + * + * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}. + */ + var Match = /** @class */ (function () { + /** + * @member Autolinker.match.Match + * @method constructor + * @param {Object} cfg The configuration properties for the Match + * instance, specified in an Object (map). + */ + function Match(cfg) { + /** + * @cfg {Autolinker.AnchorTagBuilder} tagBuilder (required) + * + * Reference to the AnchorTagBuilder instance to use to generate an anchor + * tag for the Match. + */ + this.__jsduckDummyDocProp = null; // property used just to get the above doc comment into the ES5 output and documentation generator + /** + * @cfg {String} matchedText (required) + * + * The original text that was matched by the {@link Autolinker.matcher.Matcher}. + */ + this.matchedText = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Number} offset (required) + * + * The offset of where the match was made in the input string. + */ + this.offset = 0; // default value just to get the above doc comment in the ES5 output and documentation generator + this.tagBuilder = cfg.tagBuilder; + this.matchedText = cfg.matchedText; + this.offset = cfg.offset; + } + /** + * Returns the original text that was matched. + * + * @return {String} + */ + Match.prototype.getMatchedText = function () { + return this.matchedText; + }; + /** + * Sets the {@link #offset} of where the match was made in the input string. + * + * A {@link Autolinker.matcher.Matcher} will be fed only HTML text nodes, + * and will therefore set an original offset that is relative to the HTML + * text node itself. However, we want this offset to be relative to the full + * HTML input string, and thus if using {@link Autolinker#parse} (rather + * than calling a {@link Autolinker.matcher.Matcher} directly), then this + * offset is corrected after the Matcher itself has done its job. + * + * @param {Number} offset + */ + Match.prototype.setOffset = function (offset) { + this.offset = offset; + }; + /** + * Returns the offset of where the match was made in the input string. This + * is the 0-based index of the match. + * + * @return {Number} + */ + Match.prototype.getOffset = function () { + return this.offset; + }; + /** + * Returns the CSS class suffix(es) for this match. + * + * A CSS class suffix is appended to the {@link Autolinker#className} in + * the {@link Autolinker.AnchorTagBuilder} when a match is translated into + * an anchor tag. + * + * For example, if {@link Autolinker#className} was configured as 'myLink', + * and this method returns `[ 'url' ]`, the final class name of the element + * will become: 'myLink myLink-url'. + * + * The match may provide multiple CSS class suffixes to be appended to the + * {@link Autolinker#className} in order to facilitate better styling + * options for different match criteria. See {@link Autolinker.match.Mention} + * for an example. + * + * By default, this method returns a single array with the match's + * {@link #getType type} name, but may be overridden by subclasses. + * + * @return {String[]} + */ + Match.prototype.getCssClassSuffixes = function () { + return [this.getType()]; + }; + /** + * Builds and returns an {@link Autolinker.HtmlTag} instance based on the + * Match. + * + * This can be used to easily generate anchor tags from matches, and either + * return their HTML string, or modify them before doing so. + * + * Example Usage: + * + * var tag = match.buildTag(); + * tag.addClass( 'cordova-link' ); + * tag.setAttr( 'target', '_system' ); + * + * tag.toAnchorString(); // Google + * + * Example Usage in {@link Autolinker#replaceFn}: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( match ) { + * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + */ + Match.prototype.buildTag = function () { + return this.tagBuilder.build(this); + }; + return Match; + }()); + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + /** + * @class Autolinker.match.Email + * @extends Autolinker.match.Match + * + * Represents a Email match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + var EmailMatch = /** @class */ (function (_super) { + __extends(EmailMatch, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match + * instance, specified in an Object (map). + */ + function EmailMatch(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @cfg {String} email (required) + * + * The email address that was matched. + */ + _this.email = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + _this.email = cfg.email; + return _this; + } + /** + * Returns a string name for the type of match that this class represents. + * For the case of EmailMatch, returns 'email'. + * + * @return {String} + */ + EmailMatch.prototype.getType = function () { + return 'email'; + }; + /** + * Returns the email address that was matched. + * + * @return {String} + */ + EmailMatch.prototype.getEmail = function () { + return this.email; + }; + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + EmailMatch.prototype.getAnchorHref = function () { + return 'mailto:' + this.email; + }; + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + EmailMatch.prototype.getAnchorText = function () { + return this.email; + }; + return EmailMatch; + }(Match)); + + /** + * @class Autolinker.match.Hashtag + * @extends Autolinker.match.Match + * + * Represents a Hashtag match found in an input string which should be + * Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more + * details. + */ + var HashtagMatch = /** @class */ (function (_super) { + __extends(HashtagMatch, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match + * instance, specified in an Object (map). + */ + function HashtagMatch(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @cfg {String} serviceName + * + * The service to point hashtag matches to. See {@link Autolinker#hashtag} + * for available values. + */ + _this.serviceName = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {String} hashtag (required) + * + * The HashtagMatch that was matched, without the '#'. + */ + _this.hashtag = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + _this.serviceName = cfg.serviceName; + _this.hashtag = cfg.hashtag; + return _this; + } + /** + * Returns a string name for the type of match that this class represents. + * For the case of HashtagMatch, returns 'hashtag'. + * + * @return {String} + */ + HashtagMatch.prototype.getType = function () { + return 'hashtag'; + }; + /** + * Returns the configured {@link #serviceName} to point the HashtagMatch to. + * Ex: 'facebook', 'twitter'. + * + * @return {String} + */ + HashtagMatch.prototype.getServiceName = function () { + return this.serviceName; + }; + /** + * Returns the matched hashtag, without the '#' character. + * + * @return {String} + */ + HashtagMatch.prototype.getHashtag = function () { + return this.hashtag; + }; + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + HashtagMatch.prototype.getAnchorHref = function () { + var serviceName = this.serviceName, hashtag = this.hashtag; + switch (serviceName) { + case 'twitter': + return 'https://twitter.com/hashtag/' + hashtag; + case 'facebook': + return 'https://www.facebook.com/hashtag/' + hashtag; + case 'instagram': + return 'https://instagram.com/explore/tags/' + hashtag; + default: // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case. + throw new Error('Unknown service name to point hashtag to: ' + serviceName); + } + }; + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + HashtagMatch.prototype.getAnchorText = function () { + return '#' + this.hashtag; + }; + return HashtagMatch; + }(Match)); + + /** + * @class Autolinker.match.Mention + * @extends Autolinker.match.Match + * + * Represents a Mention match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + var MentionMatch = /** @class */ (function (_super) { + __extends(MentionMatch, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match + * instance, specified in an Object (map). + */ + function MentionMatch(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @cfg {String} serviceName + * + * The service to point mention matches to. See {@link Autolinker#mention} + * for available values. + */ + _this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {String} mention (required) + * + * The Mention that was matched, without the '@' character. + */ + _this.mention = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + _this.mention = cfg.mention; + _this.serviceName = cfg.serviceName; + return _this; + } + /** + * Returns a string name for the type of match that this class represents. + * For the case of MentionMatch, returns 'mention'. + * + * @return {String} + */ + MentionMatch.prototype.getType = function () { + return 'mention'; + }; + /** + * Returns the mention, without the '@' character. + * + * @return {String} + */ + MentionMatch.prototype.getMention = function () { + return this.mention; + }; + /** + * Returns the configured {@link #serviceName} to point the mention to. + * Ex: 'instagram', 'twitter', 'soundcloud'. + * + * @return {String} + */ + MentionMatch.prototype.getServiceName = function () { + return this.serviceName; + }; + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + MentionMatch.prototype.getAnchorHref = function () { + switch (this.serviceName) { + case 'twitter': + return 'https://twitter.com/' + this.mention; + case 'instagram': + return 'https://instagram.com/' + this.mention; + case 'soundcloud': + return 'https://soundcloud.com/' + this.mention; + default: // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case. + throw new Error('Unknown service name to point mention to: ' + this.serviceName); + } + }; + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + MentionMatch.prototype.getAnchorText = function () { + return '@' + this.mention; + }; + /** + * Returns the CSS class suffixes that should be used on a tag built with + * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for + * details. + * + * @return {String[]} + */ + MentionMatch.prototype.getCssClassSuffixes = function () { + var cssClassSuffixes = _super.prototype.getCssClassSuffixes.call(this), serviceName = this.getServiceName(); + if (serviceName) { + cssClassSuffixes.push(serviceName); + } + return cssClassSuffixes; + }; + return MentionMatch; + }(Match)); + + /** + * @class Autolinker.match.Phone + * @extends Autolinker.match.Match + * + * Represents a Phone number match found in an input string which should be + * Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more + * details. + */ + var PhoneMatch = /** @class */ (function (_super) { + __extends(PhoneMatch, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match + * instance, specified in an Object (map). + */ + function PhoneMatch(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @protected + * @property {String} number (required) + * + * The phone number that was matched, without any delimiter characters. + * + * Note: This is a string to allow for prefixed 0's. + */ + _this.number = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @protected + * @property {Boolean} plusSign (required) + * + * `true` if the matched phone number started with a '+' sign. We'll include + * it in the `tel:` URL if so, as this is needed for international numbers. + * + * Ex: '+1 (123) 456 7879' + */ + _this.plusSign = false; // default value just to get the above doc comment in the ES5 output and documentation generator + _this.number = cfg.number; + _this.plusSign = cfg.plusSign; + return _this; + } + /** + * Returns a string name for the type of match that this class represents. + * For the case of PhoneMatch, returns 'phone'. + * + * @return {String} + */ + PhoneMatch.prototype.getType = function () { + return 'phone'; + }; + /** + * Returns the phone number that was matched as a string, without any + * delimiter characters. + * + * Note: This is a string to allow for prefixed 0's. + * + * @return {String} + */ + PhoneMatch.prototype.getPhoneNumber = function () { + return this.number; + }; + /** + * Alias of {@link #getPhoneNumber}, returns the phone number that was + * matched as a string, without any delimiter characters. + * + * Note: This is a string to allow for prefixed 0's. + * + * @return {String} + */ + PhoneMatch.prototype.getNumber = function () { + return this.getPhoneNumber(); + }; + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + PhoneMatch.prototype.getAnchorHref = function () { + return 'tel:' + (this.plusSign ? '+' : '') + this.number; + }; + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + PhoneMatch.prototype.getAnchorText = function () { + return this.matchedText; + }; + return PhoneMatch; + }(Match)); + + /** + * @class Autolinker.match.Url + * @extends Autolinker.match.Match + * + * Represents a Url match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + var UrlMatch = /** @class */ (function (_super) { + __extends(UrlMatch, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match + * instance, specified in an Object (map). + */ + function UrlMatch(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @cfg {String} url (required) + * + * The url that was matched. + */ + _this.url = ''; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {"scheme"/"www"/"tld"} urlMatchType (required) + * + * The type of URL match that this class represents. This helps to determine + * if the match was made in the original text with a prefixed scheme (ex: + * 'http://www.google.com'), a prefixed 'www' (ex: 'www.google.com'), or + * was matched by a known top-level domain (ex: 'google.com'). + */ + _this.urlMatchType = 'scheme'; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Boolean} protocolUrlMatch (required) + * + * `true` if the URL is a match which already has a protocol (i.e. + * 'http://'), `false` if the match was from a 'www' or known TLD match. + */ + _this.protocolUrlMatch = false; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Boolean} protocolRelativeMatch (required) + * + * `true` if the URL is a protocol-relative match. A protocol-relative match + * is a URL that starts with '//', and will be either http:// or https:// + * based on the protocol that the site is loaded under. + */ + _this.protocolRelativeMatch = false; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Object} stripPrefix (required) + * + * The Object form of {@link Autolinker#cfg-stripPrefix}. + */ + _this.stripPrefix = { scheme: true, www: true }; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Boolean} stripTrailingSlash (required) + * @inheritdoc Autolinker#cfg-stripTrailingSlash + */ + _this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Boolean} decodePercentEncoding (required) + * @inheritdoc Autolinker#cfg-decodePercentEncoding + */ + _this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @private + * @property {RegExp} schemePrefixRegex + * + * A regular expression used to remove the 'http://' or 'https://' from + * URLs. + */ + _this.schemePrefixRegex = /^(https?:\/\/)?/i; + /** + * @private + * @property {RegExp} wwwPrefixRegex + * + * A regular expression used to remove the 'www.' from URLs. + */ + _this.wwwPrefixRegex = /^(https?:\/\/)?(www\.)?/i; + /** + * @private + * @property {RegExp} protocolRelativeRegex + * + * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes + * of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com" + */ + _this.protocolRelativeRegex = /^\/\//; + /** + * @private + * @property {Boolean} protocolPrepended + * + * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the + * {@link #url} did not have a protocol) + */ + _this.protocolPrepended = false; + _this.urlMatchType = cfg.urlMatchType; + _this.url = cfg.url; + _this.protocolUrlMatch = cfg.protocolUrlMatch; + _this.protocolRelativeMatch = cfg.protocolRelativeMatch; + _this.stripPrefix = cfg.stripPrefix; + _this.stripTrailingSlash = cfg.stripTrailingSlash; + _this.decodePercentEncoding = cfg.decodePercentEncoding; + return _this; + } + /** + * Returns a string name for the type of match that this class represents. + * For the case of UrlMatch, returns 'url'. + * + * @return {String} + */ + UrlMatch.prototype.getType = function () { + return 'url'; + }; + /** + * Returns a string name for the type of URL match that this class + * represents. + * + * This helps to determine if the match was made in the original text with a + * prefixed scheme (ex: 'http://www.google.com'), a prefixed 'www' (ex: + * 'www.google.com'), or was matched by a known top-level domain (ex: + * 'google.com'). + * + * @return {"scheme"/"www"/"tld"} + */ + UrlMatch.prototype.getUrlMatchType = function () { + return this.urlMatchType; + }; + /** + * Returns the url that was matched, assuming the protocol to be 'http://' if the original + * match was missing a protocol. + * + * @return {String} + */ + UrlMatch.prototype.getUrl = function () { + var url = this.url; + // if the url string doesn't begin with a protocol, assume 'http://' + if (!this.protocolRelativeMatch && !this.protocolUrlMatch && !this.protocolPrepended) { + url = this.url = 'http://' + url; + this.protocolPrepended = true; + } + return url; + }; + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + UrlMatch.prototype.getAnchorHref = function () { + var url = this.getUrl(); + return url.replace(/&/g, '&'); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html + }; + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + UrlMatch.prototype.getAnchorText = function () { + var anchorText = this.getMatchedText(); + if (this.protocolRelativeMatch) { + // Strip off any protocol-relative '//' from the anchor text + anchorText = this.stripProtocolRelativePrefix(anchorText); + } + if (this.stripPrefix.scheme) { + anchorText = this.stripSchemePrefix(anchorText); + } + if (this.stripPrefix.www) { + anchorText = this.stripWwwPrefix(anchorText); + } + if (this.stripTrailingSlash) { + anchorText = this.removeTrailingSlash(anchorText); // remove trailing slash, if there is one + } + if (this.decodePercentEncoding) { + anchorText = this.removePercentEncoding(anchorText); + } + return anchorText; + }; + // --------------------------------------- + // Utility Functionality + /** + * Strips the scheme prefix (such as "http://" or "https://") from the given + * `url`. + * + * @private + * @param {String} url The text of the anchor that is being generated, for + * which to strip off the url scheme. + * @return {String} The `url`, with the scheme stripped. + */ + UrlMatch.prototype.stripSchemePrefix = function (url) { + return url.replace(this.schemePrefixRegex, ''); + }; + /** + * Strips the 'www' prefix from the given `url`. + * + * @private + * @param {String} url The text of the anchor that is being generated, for + * which to strip off the 'www' if it exists. + * @return {String} The `url`, with the 'www' stripped. + */ + UrlMatch.prototype.stripWwwPrefix = function (url) { + return url.replace(this.wwwPrefixRegex, '$1'); // leave any scheme ($1), it one exists + }; + /** + * Strips any protocol-relative '//' from the anchor text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * protocol-relative prefix (such as stripping off "//") + * @return {String} The `anchorText`, with the protocol-relative prefix stripped. + */ + UrlMatch.prototype.stripProtocolRelativePrefix = function (text) { + return text.replace(this.protocolRelativeRegex, ''); + }; + /** + * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed. + * + * @private + * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing + * slash ('/') that may exist. + * @return {String} The `anchorText`, with the trailing slash removed. + */ + UrlMatch.prototype.removeTrailingSlash = function (anchorText) { + if (anchorText.charAt(anchorText.length - 1) === '/') { + anchorText = anchorText.slice(0, -1); + } + return anchorText; + }; + /** + * Decodes percent-encoded characters from the given `anchorText`, in + * preparation for the text to be displayed. + * + * @private + * @param {String} anchorText The text of the anchor that is being + * generated, for which to decode any percent-encoded characters. + * @return {String} The `anchorText`, with the percent-encoded characters + * decoded. + */ + UrlMatch.prototype.removePercentEncoding = function (anchorText) { + // First, convert a few of the known % encodings to the corresponding + // HTML entities that could accidentally be interpretted as special + // HTML characters + var preProcessedEntityAnchorText = anchorText + .replace(/%22/gi, '"') // " char + .replace(/%26/gi, '&') // & char + .replace(/%27/gi, ''') // ' char + .replace(/%3C/gi, '<') // < char + .replace(/%3E/gi, '>'); // > char + try { + // Now attempt to decode the rest of the anchor text + return decodeURIComponent(preProcessedEntityAnchorText); + } + catch (e) { // Invalid % escape sequence in the anchor text + return preProcessedEntityAnchorText; + } + }; + return UrlMatch; + }(Match)); + + /** + * @abstract + * @class Autolinker.matcher.Matcher + * + * An abstract class and interface for individual matchers to find matches in + * an input string with linkified versions of them. + * + * Note that Matchers do not take HTML into account - they must be fed the text + * nodes of any HTML string, which is handled by {@link Autolinker#parse}. + */ + var Matcher = /** @class */ (function () { + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Matcher + * instance, specified in an Object (map). + */ + function Matcher(cfg) { + /** + * @cfg {Autolinker.AnchorTagBuilder} tagBuilder (required) + * + * Reference to the AnchorTagBuilder instance to use to generate HTML tags + * for {@link Autolinker.match.Match Matches}. + */ + this.__jsduckDummyDocProp = null; // property used just to get the above doc comment into the ES5 output and documentation generator + this.tagBuilder = cfg.tagBuilder; + } + return Matcher; + }()); + + /* + * This file builds and stores a library of the common regular expressions used + * by the Autolinker utility. + * + * Other regular expressions may exist ad-hoc, but these are generally the + * regular expressions that are shared between source files. + */ + /** + * Regular expression to match upper and lowercase ASCII letters + */ + var letterRe = /[A-Za-z]/; + /** + * Regular expression to match ASCII digits + */ + var digitRe = /[0-9]/; + /** + * Regular expression to match whitespace + */ + var whitespaceRe = /\s/; + /** + * Regular expression to match quote characters + */ + var quoteRe = /['"]/; + /** + * Regular expression to match the range of ASCII control characters (0-31), and + * the backspace char (127) + */ + var controlCharsRe = /[\x00-\x1F\x7F]/; + /** + * The string form of a regular expression that would match all of the + * alphabetic ("letter") chars in the unicode character set when placed in a + * RegExp character class (`[]`). This includes all international alphabetic + * characters. + * + * These would be the characters matched by unicode regex engines `\p{L}` + * escape ("all letters"). + * + * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan) + * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Letter' + * regex's bmp + * + * VERY IMPORTANT: This set of characters is defined inside of a Regular + * Expression literal rather than a string literal to prevent UglifyJS from + * compressing the unicode escape sequences into their actual unicode + * characters. If Uglify compresses these into the unicode characters + * themselves, this results in the error "Range out of order in character + * class" when these characters are used inside of a Regular Expression + * character class (`[]`). See usages of this const. Alternatively, we can set + * the UglifyJS option `ascii_only` to true for the build, but that doesn't + * help others who are pulling in Autolinker into their own build and running + * UglifyJS themselves. + */ + var alphaCharsStr = /A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/ + .source; // see note in above variable description + /** + * The string form of a regular expression that would match all emoji characters + * Source: https://www.regextester.com/106421 + */ + var emojiStr = /\u00a9\u00ae\u2000-\u3300\ud83c\ud000-\udfff\ud83d\ud000-\udfff\ud83e\ud000-\udfff/ + .source; + /** + * The string form of a regular expression that would match all of the + * combining mark characters in the unicode character set when placed in a + * RegExp character class (`[]`). + * + * These would be the characters matched by unicode regex engines `\p{M}` + * escape ("all marks"). + * + * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan) + * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Mark' + * regex's bmp + * + * VERY IMPORTANT: This set of characters is defined inside of a Regular + * Expression literal rather than a string literal to prevent UglifyJS from + * compressing the unicode escape sequences into their actual unicode + * characters. If Uglify compresses these into the unicode characters + * themselves, this results in the error "Range out of order in character + * class" when these characters are used inside of a Regular Expression + * character class (`[]`). See usages of this const. Alternatively, we can set + * the UglifyJS option `ascii_only` to true for the build, but that doesn't + * help others who are pulling in Autolinker into their own build and running + * UglifyJS themselves. + */ + var marksStr = /\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D4-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFB-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F/ + .source; // see note in above variable description + /** + * The string form of a regular expression that would match all of the + * alphabetic ("letter") chars, emoji, and combining marks in the unicode character set + * when placed in a RegExp character class (`[]`). This includes all + * international alphabetic characters. + * + * These would be the characters matched by unicode regex engines `\p{L}\p{M}` + * escapes and emoji characters. + */ + var alphaCharsAndMarksStr = alphaCharsStr + emojiStr + marksStr; + /** + * The string form of a regular expression that would match all of the + * decimal number chars in the unicode character set when placed in a RegExp + * character class (`[]`). + * + * These would be the characters matched by unicode regex engines `\p{Nd}` + * escape ("all decimal numbers") + * + * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan) + * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Decimal_Number' + * regex's bmp + * + * VERY IMPORTANT: This set of characters is defined inside of a Regular + * Expression literal rather than a string literal to prevent UglifyJS from + * compressing the unicode escape sequences into their actual unicode + * characters. If Uglify compresses these into the unicode characters + * themselves, this results in the error "Range out of order in character + * class" when these characters are used inside of a Regular Expression + * character class (`[]`). See usages of this const. Alternatively, we can set + * the UglifyJS option `ascii_only` to true for the build, but that doesn't + * help others who are pulling in Autolinker into their own build and running + * UglifyJS themselves. + */ + var decimalNumbersStr = /0-9\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19/ + .source; // see note in above variable description + /** + * The string form of a regular expression that would match all of the + * letters and decimal number chars in the unicode character set when placed in + * a RegExp character class (`[]`). + * + * These would be the characters matched by unicode regex engines + * `[\p{L}\p{Nd}]` escape ("all letters and decimal numbers") + */ + var alphaNumericCharsStr = alphaCharsAndMarksStr + decimalNumbersStr; + /** + * The string form of a regular expression that would match all of the + * letters, combining marks, and decimal number chars in the unicode character + * set when placed in a RegExp character class (`[]`). + * + * These would be the characters matched by unicode regex engines + * `[\p{L}\p{M}\p{Nd}]` escape ("all letters, combining marks, and decimal + * numbers") + */ + var alphaNumericAndMarksCharsStr = alphaCharsAndMarksStr + decimalNumbersStr; + // Simplified IP regular expression + var ipStr = '(?:[' + decimalNumbersStr + ']{1,3}\\.){3}[' + decimalNumbersStr + ']{1,3}'; + // Protected domain label which do not allow "-" character on the beginning and the end of a single label + var domainLabelStr = '[' + alphaNumericAndMarksCharsStr + '](?:[' + alphaNumericAndMarksCharsStr + '\\-]{0,61}[' + alphaNumericAndMarksCharsStr + '])?'; + var getDomainLabelStr = function (group) { + return '(?=(' + domainLabelStr + '))\\' + group; + }; + /** + * A function to match domain names of a URL or email address. + * Ex: 'google', 'yahoo', 'some-other-company', etc. + */ + var getDomainNameStr = function (group) { + return '(?:' + getDomainLabelStr(group) + '(?:\\.' + getDomainLabelStr(group + 1) + '){0,126}|' + ipStr + ')'; + }; + /** + * A regular expression that is simply the character class of the characters + * that may be used in a domain name, minus the '-' or '.' + */ + var domainNameCharRegex = new RegExp("[" + alphaNumericAndMarksCharsStr + "]"); + + // NOTE: THIS IS A GENERATED FILE + // To update with the latest TLD list, run `npm run update-tld-regex` or `yarn update-tld-regex` (depending on which you have installed) + var tldRegex = /(?:xn--vermgensberatung-pwb|xn--vermgensberater-ctb|xn--clchc0ea0b2g2a9gcd|xn--w4r85el8fhu5dnra|northwesternmutual|travelersinsurance|vermögensberatung|xn--3oq18vl8pn36a|xn--5su34j936bgsg|xn--bck1b9a5dre4c|xn--mgbai9azgqp6j|xn--mgberp4a5d4ar|xn--xkc2dl3a5ee0h|vermögensberater|xn--fzys8d69uvgm|xn--mgba7c0bbn0a|xn--xkc2al3hye2a|americanexpress|kerryproperties|sandvikcoromant|xn--i1b6b1a6a2e|xn--kcrx77d1x4a|xn--lgbbat1ad8j|xn--mgba3a4f16a|xn--mgbaakc7dvf|xn--mgbc0a9azcg|xn--nqv7fs00ema|afamilycompany|americanfamily|bananarepublic|cancerresearch|cookingchannel|kerrylogistics|weatherchannel|xn--54b7fta0cc|xn--6qq986b3xl|xn--80aqecdr1a|xn--b4w605ferd|xn--fiq228c5hs|xn--h2breg3eve|xn--jlq61u9w7b|xn--mgba3a3ejt|xn--mgbaam7a8h|xn--mgbayh7gpa|xn--mgbb9fbpob|xn--mgbbh1a71e|xn--mgbca7dzdo|xn--mgbi4ecexp|xn--mgbx4cd0ab|xn--rvc1e0am3e|international|lifeinsurance|spreadbetting|travelchannel|wolterskluwer|xn--eckvdtc9d|xn--fpcrj9c3d|xn--fzc2c9e2c|xn--h2brj9c8c|xn--tiq49xqyj|xn--yfro4i67o|xn--ygbi2ammx|construction|lplfinancial|scholarships|versicherung|xn--3e0b707e|xn--45br5cyl|xn--80adxhks|xn--80asehdb|xn--8y0a063a|xn--gckr3f0f|xn--mgb9awbf|xn--mgbab2bd|xn--mgbgu82a|xn--mgbpl2fh|xn--mgbt3dhd|xn--mk1bu44c|xn--ngbc5azd|xn--ngbe9e0a|xn--ogbpf8fl|xn--qcka1pmc|accountants|barclaycard|blackfriday|blockbuster|bridgestone|calvinklein|contractors|creditunion|engineering|enterprises|foodnetwork|investments|kerryhotels|lamborghini|motorcycles|olayangroup|photography|playstation|productions|progressive|redumbrella|rightathome|williamhill|xn--11b4c3d|xn--1ck2e1b|xn--1qqw23a|xn--2scrj9c|xn--3bst00m|xn--3ds443g|xn--3hcrj9c|xn--42c2d9a|xn--45brj9c|xn--55qw42g|xn--6frz82g|xn--80ao21a|xn--9krt00a|xn--cck2b3b|xn--czr694b|xn--d1acj3b|xn--efvy88h|xn--estv75g|xn--fct429k|xn--fjq720a|xn--flw351e|xn--g2xx48c|xn--gecrj9c|xn--gk3at1e|xn--h2brj9c|xn--hxt814e|xn--imr513n|xn--j6w193g|xn--jvr189m|xn--kprw13d|xn--kpry57d|xn--kpu716f|xn--mgbbh1a|xn--mgbtx2b|xn--mix891f|xn--nyqy26a|xn--otu796d|xn--pbt977c|xn--pgbs0dh|xn--q9jyb4c|xn--rhqv96g|xn--rovu88b|xn--s9brj9c|xn--ses554g|xn--t60b56a|xn--vuq861b|xn--w4rs40l|xn--xhq521b|xn--zfr164b|சிங்கப்பூர்|accountant|apartments|associates|basketball|bnpparibas|boehringer|capitalone|consulting|creditcard|cuisinella|eurovision|extraspace|foundation|healthcare|immobilien|industries|management|mitsubishi|nationwide|newholland|nextdirect|onyourside|properties|protection|prudential|realestate|republican|restaurant|schaeffler|swiftcover|tatamotors|technology|telefonica|university|vistaprint|vlaanderen|volkswagen|xn--30rr7y|xn--3pxu8k|xn--45q11c|xn--4gbrim|xn--55qx5d|xn--5tzm5g|xn--80aswg|xn--90a3ac|xn--9dbq2a|xn--9et52u|xn--c2br7g|xn--cg4bki|xn--czrs0t|xn--czru2d|xn--fiq64b|xn--fiqs8s|xn--fiqz9s|xn--io0a7i|xn--kput3i|xn--mxtq1m|xn--o3cw4h|xn--pssy2u|xn--unup4y|xn--wgbh1c|xn--wgbl6a|xn--y9a3aq|accenture|alfaromeo|allfinanz|amsterdam|analytics|aquarelle|barcelona|bloomberg|christmas|community|directory|education|equipment|fairwinds|financial|firestone|fresenius|frontdoor|fujixerox|furniture|goldpoint|hisamitsu|homedepot|homegoods|homesense|honeywell|institute|insurance|kuokgroup|ladbrokes|lancaster|landrover|lifestyle|marketing|marshalls|melbourne|microsoft|panasonic|passagens|pramerica|richardli|scjohnson|shangrila|solutions|statebank|statefarm|stockholm|travelers|vacations|xn--90ais|xn--c1avg|xn--d1alf|xn--e1a4c|xn--fhbei|xn--j1aef|xn--j1amh|xn--l1acc|xn--ngbrx|xn--nqv7f|xn--p1acf|xn--tckwe|xn--vhquv|yodobashi|abudhabi|airforce|allstate|attorney|barclays|barefoot|bargains|baseball|boutique|bradesco|broadway|brussels|budapest|builders|business|capetown|catering|catholic|chrysler|cipriani|cityeats|cleaning|clinique|clothing|commbank|computer|delivery|deloitte|democrat|diamonds|discount|discover|download|engineer|ericsson|esurance|etisalat|everbank|exchange|feedback|fidelity|firmdale|football|frontier|goodyear|grainger|graphics|guardian|hdfcbank|helsinki|holdings|hospital|infiniti|ipiranga|istanbul|jpmorgan|lighting|lundbeck|marriott|maserati|mckinsey|memorial|merckmsd|mortgage|movistar|observer|partners|pharmacy|pictures|plumbing|property|redstone|reliance|saarland|samsclub|security|services|shopping|showtime|softbank|software|stcgroup|supplies|symantec|training|uconnect|vanguard|ventures|verisign|woodside|xn--90ae|xn--node|xn--p1ai|xn--qxam|yokohama|السعودية|abogado|academy|agakhan|alibaba|android|athleta|auction|audible|auspost|avianca|banamex|bauhaus|bentley|bestbuy|booking|brother|bugatti|capital|caravan|careers|cartier|channel|charity|chintai|citadel|clubmed|college|cologne|comcast|company|compare|contact|cooking|corsica|country|coupons|courses|cricket|cruises|dentist|digital|domains|exposed|express|farmers|fashion|ferrari|ferrero|finance|fishing|fitness|flights|florist|flowers|forsale|frogans|fujitsu|gallery|genting|godaddy|grocery|guitars|hamburg|hangout|hitachi|holiday|hosting|hoteles|hotmail|hyundai|iselect|ismaili|jewelry|juniper|kitchen|komatsu|lacaixa|lancome|lanxess|lasalle|latrobe|leclerc|liaison|limited|lincoln|markets|metlife|monster|netbank|netflix|network|neustar|okinawa|oldnavy|organic|origins|philips|pioneer|politie|realtor|recipes|rentals|reviews|rexroth|samsung|sandvik|schmidt|schwarz|science|shiksha|shriram|singles|staples|starhub|storage|support|surgery|systems|temasek|theater|theatre|tickets|tiffany|toshiba|trading|walmart|wanggou|watches|weather|website|wedding|whoswho|windows|winners|xfinity|yamaxun|youtube|zuerich|католик|اتصالات|الجزائر|العليان|پاکستان|كاثوليك|موبايلي|இந்தியா|abarth|abbott|abbvie|active|africa|agency|airbus|airtel|alipay|alsace|alstom|anquan|aramco|author|bayern|beauty|berlin|bharti|blanco|bostik|boston|broker|camera|career|caseih|casino|center|chanel|chrome|church|circle|claims|clinic|coffee|comsec|condos|coupon|credit|cruise|dating|datsun|dealer|degree|dental|design|direct|doctor|dunlop|dupont|durban|emerck|energy|estate|events|expert|family|flickr|futbol|gallup|garden|george|giving|global|google|gratis|health|hermes|hiphop|hockey|hotels|hughes|imamat|insure|intuit|jaguar|joburg|juegos|kaufen|kinder|kindle|kosher|lancia|latino|lawyer|lefrak|living|locker|london|luxury|madrid|maison|makeup|market|mattel|mobile|mobily|monash|mormon|moscow|museum|mutual|nagoya|natura|nissan|nissay|norton|nowruz|office|olayan|online|oracle|orange|otsuka|pfizer|photos|physio|piaget|pictet|quebec|racing|realty|reisen|repair|report|review|rocher|rogers|ryukyu|safety|sakura|sanofi|school|schule|search|secure|select|shouji|soccer|social|stream|studio|supply|suzuki|swatch|sydney|taipei|taobao|target|tattoo|tennis|tienda|tjmaxx|tkmaxx|toyota|travel|unicom|viajes|viking|villas|virgin|vision|voting|voyage|vuelos|walter|warman|webcam|xihuan|yachts|yandex|zappos|москва|онлайн|ابوظبي|ارامكو|الاردن|المغرب|امارات|فلسطين|مليسيا|भारतम्|இலங்கை|ファッション|actor|adult|aetna|amfam|amica|apple|archi|audio|autos|azure|baidu|beats|bible|bingo|black|boats|bosch|build|canon|cards|chase|cheap|cisco|citic|click|cloud|coach|codes|crown|cymru|dabur|dance|deals|delta|dodge|drive|dubai|earth|edeka|email|epost|epson|faith|fedex|final|forex|forum|gallo|games|gifts|gives|glade|glass|globo|gmail|green|gripe|group|gucci|guide|homes|honda|horse|house|hyatt|ikano|intel|irish|iveco|jetzt|koeln|kyoto|lamer|lease|legal|lexus|lilly|linde|lipsy|lixil|loans|locus|lotte|lotto|lupin|macys|mango|media|miami|money|mopar|movie|nadex|nexus|nikon|ninja|nokia|nowtv|omega|osaka|paris|parts|party|phone|photo|pizza|place|poker|praxi|press|prime|promo|quest|radio|rehab|reise|ricoh|rocks|rodeo|rugby|salon|sener|seven|sharp|shell|shoes|skype|sling|smart|smile|solar|space|sport|stada|store|study|style|sucks|swiss|tatar|tires|tirol|tmall|today|tokyo|tools|toray|total|tours|trade|trust|tunes|tushu|ubank|vegas|video|vodka|volvo|wales|watch|weber|weibo|works|world|xerox|yahoo|zippo|ایران|بازار|بھارت|سودان|سورية|همراه|भारोत|संगठन|বাংলা|భారత్|ഭാരതം|嘉里大酒店|aarp|able|adac|aero|aigo|akdn|ally|amex|arab|army|arpa|arte|asda|asia|audi|auto|baby|band|bank|bbva|beer|best|bike|bing|blog|blue|bofa|bond|book|buzz|cafe|call|camp|care|cars|casa|case|cash|cbre|cern|chat|citi|city|club|cool|coop|cyou|data|date|dclk|deal|dell|desi|diet|dish|docs|doha|duck|duns|dvag|erni|fage|fail|fans|farm|fast|fiat|fido|film|fire|fish|flir|food|ford|free|fund|game|gbiz|gent|ggee|gift|gmbh|gold|golf|goog|guge|guru|hair|haus|hdfc|help|here|hgtv|host|hsbc|icbc|ieee|imdb|immo|info|itau|java|jeep|jobs|jprs|kddi|kiwi|kpmg|kred|land|lego|lgbt|lidl|life|like|limo|link|live|loan|loft|love|ltda|luxe|maif|meet|meme|menu|mini|mint|mobi|moda|moto|name|navy|news|next|nico|nike|ollo|open|page|pars|pccw|pics|ping|pink|play|plus|pohl|porn|post|prod|prof|qpon|raid|read|reit|rent|rest|rich|rmit|room|rsvp|ruhr|safe|sale|sarl|save|saxo|scor|scot|seat|seek|sexy|shaw|shia|shop|show|silk|sina|site|skin|sncf|sohu|song|sony|spot|star|surf|talk|taxi|team|tech|teva|tiaa|tips|town|toys|tube|vana|visa|viva|vivo|vote|voto|wang|weir|wien|wiki|wine|work|xbox|yoga|zara|zero|zone|дети|сайт|بارت|بيتك|ڀارت|تونس|شبكة|عراق|عمان|موقع|भारत|ভারত|ভাৰত|ਭਾਰਤ|ભારત|ଭାରତ|ಭಾರತ|ලංකා|グーグル|クラウド|ポイント|大众汽车|组织机构|電訊盈科|香格里拉|aaa|abb|abc|aco|ads|aeg|afl|aig|anz|aol|app|art|aws|axa|bar|bbc|bbt|bcg|bcn|bet|bid|bio|biz|bms|bmw|bnl|bom|boo|bot|box|buy|bzh|cab|cal|cam|car|cat|cba|cbn|cbs|ceb|ceo|cfa|cfd|com|crs|csc|dad|day|dds|dev|dhl|diy|dnp|dog|dot|dtv|dvr|eat|eco|edu|esq|eus|fan|fit|fly|foo|fox|frl|ftr|fun|fyi|gal|gap|gdn|gea|gle|gmo|gmx|goo|gop|got|gov|hbo|hiv|hkt|hot|how|ibm|ice|icu|ifm|inc|ing|ink|int|ist|itv|jcb|jcp|jio|jll|jmp|jnj|jot|joy|kfh|kia|kim|kpn|krd|lat|law|lds|llc|lol|lpl|ltd|man|map|mba|med|men|mil|mit|mlb|mls|mma|moe|moi|mom|mov|msd|mtn|mtr|nab|nba|nec|net|new|nfl|ngo|nhk|now|nra|nrw|ntt|nyc|obi|off|one|ong|onl|ooo|org|ott|ovh|pay|pet|phd|pid|pin|pnc|pro|pru|pub|pwc|qvc|red|ren|ril|rio|rip|run|rwe|sap|sas|sbi|sbs|sca|scb|ses|sew|sex|sfr|ski|sky|soy|srl|srt|stc|tab|tax|tci|tdk|tel|thd|tjx|top|trv|tui|tvs|ubs|uno|uol|ups|vet|vig|vin|vip|wed|win|wme|wow|wtc|wtf|xin|xxx|xyz|you|yun|zip|бел|ком|қаз|мкд|мон|орг|рус|срб|укр|հայ|קום|عرب|قطر|كوم|مصر|कॉम|नेट|คอม|ไทย|ストア|セール|みんな|中文网|天主教|我爱你|新加坡|淡马锡|诺基亚|飞利浦|ac|ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw|ελ|бг|ею|рф|გე|닷넷|닷컴|삼성|한국|コム|世界|中信|中国|中國|企业|佛山|信息|健康|八卦|公司|公益|台湾|台灣|商城|商店|商标|嘉里|在线|大拿|娱乐|家電|工行|广东|微博|慈善|手机|手表|招聘|政务|政府|新闻|时尚|書籍|机构|游戏|澳門|点看|珠宝|移动|网址|网店|网站|网络|联通|谷歌|购物|通販|集团|食品|餐厅|香港)/; + + // For debugging: search for other "For debugging" lines + // import CliTable from 'cli-table'; + /** + * @class Autolinker.matcher.Email + * @extends Autolinker.matcher.Matcher + * + * Matcher to find email matches in an input string. + * + * See this class's superclass ({@link Autolinker.matcher.Matcher}) for more details. + */ + var EmailMatcher = /** @class */ (function (_super) { + __extends(EmailMatcher, _super); + function EmailMatcher() { + var _this = _super !== null && _super.apply(this, arguments) || this; + /** + * Valid characters that can be used in the "local" part of an email address, + * i.e. the "name" part of "name@site.com" + */ + _this.localPartCharRegex = new RegExp("[" + alphaNumericAndMarksCharsStr + "!#$%&'*+/=?^_`{|}~-]"); + /** + * Stricter TLD regex which adds a beginning and end check to ensure + * the string is a valid TLD + */ + _this.strictTldRegex = new RegExp("^" + tldRegex.source + "$"); + return _this; + } + /** + * @inheritdoc + */ + EmailMatcher.prototype.parseMatches = function (text) { + var tagBuilder = this.tagBuilder, localPartCharRegex = this.localPartCharRegex, strictTldRegex = this.strictTldRegex, matches = [], len = text.length, noCurrentEmailMatch = new CurrentEmailMatch(); + // for matching a 'mailto:' prefix + var mailtoTransitions = { + 'm': 'a', + 'a': 'i', + 'i': 'l', + 'l': 't', + 't': 'o', + 'o': ':', + }; + var charIdx = 0, state = 0 /* NonEmailMatch */, currentEmailMatch = noCurrentEmailMatch; + // For debugging: search for other "For debugging" lines + // const table = new CliTable( { + // head: [ 'charIdx', 'char', 'state', 'charIdx', 'currentEmailAddress.idx', 'hasDomainDot' ] + // } ); + while (charIdx < len) { + var char = text.charAt(charIdx); + // For debugging: search for other "For debugging" lines + // table.push( + // [ charIdx, char, State[ state ], charIdx, currentEmailAddress.idx, currentEmailAddress.hasDomainDot ] + // ); + switch (state) { + case 0 /* NonEmailMatch */: + stateNonEmailAddress(char); + break; + case 1 /* Mailto */: + stateMailTo(text.charAt(charIdx - 1), char); + break; + case 2 /* LocalPart */: + stateLocalPart(char); + break; + case 3 /* LocalPartDot */: + stateLocalPartDot(char); + break; + case 4 /* AtSign */: + stateAtSign(char); + break; + case 5 /* DomainChar */: + stateDomainChar(char); + break; + case 6 /* DomainHyphen */: + stateDomainHyphen(char); + break; + case 7 /* DomainDot */: + stateDomainDot(char); + break; + default: + throwUnhandledCaseError(state); + } + // For debugging: search for other "For debugging" lines + // table.push( + // [ charIdx, char, State[ state ], charIdx, currentEmailAddress.idx, currentEmailAddress.hasDomainDot ] + // ); + charIdx++; + } + // Capture any valid match at the end of the string + captureMatchIfValidAndReset(); + // For debugging: search for other "For debugging" lines + //console.log( '\n' + table.toString() ); + return matches; + // Handles the state when we're not in an email address + function stateNonEmailAddress(char) { + if (char === 'm') { + beginEmailMatch(1 /* Mailto */); + } + else if (localPartCharRegex.test(char)) { + beginEmailMatch(); + } + } + // Handles if we're reading a 'mailto:' prefix on the string + function stateMailTo(prevChar, char) { + if (prevChar === ':') { + // We've reached the end of the 'mailto:' prefix + if (localPartCharRegex.test(char)) { + state = 2 /* LocalPart */; + currentEmailMatch = new CurrentEmailMatch(__assign({}, currentEmailMatch, { hasMailtoPrefix: true })); + } + else { + // we've matched 'mailto:' but didn't get anything meaningful + // immediately afterwards (for example, we encountered a + // space character, or an '@' character which formed 'mailto:@' + resetToNonEmailMatchState(); + } + } + else if (mailtoTransitions[prevChar] === char) ; + else if (localPartCharRegex.test(char)) { + // We we're reading a prefix of 'mailto:', but encountered a + // different character that didn't continue the prefix + state = 2 /* LocalPart */; + } + else if (char === '.') { + // We we're reading a prefix of 'mailto:', but encountered a + // dot character + state = 3 /* LocalPartDot */; + } + else if (char === '@') { + // We we're reading a prefix of 'mailto:', but encountered a + // an @ character + state = 4 /* AtSign */; + } + else { + // not an email address character, return to "NonEmailAddress" state + resetToNonEmailMatchState(); + } + } + // Handles the state when we're currently in the "local part" of an + // email address (as opposed to the "domain part") + function stateLocalPart(char) { + if (char === '.') { + state = 3 /* LocalPartDot */; + } + else if (char === '@') { + state = 4 /* AtSign */; + } + else if (localPartCharRegex.test(char)) ; + else { + // not an email address character, return to "NonEmailAddress" state + resetToNonEmailMatchState(); + } + } + // Handles the state where we've read + function stateLocalPartDot(char) { + if (char === '.') { + // We read a second '.' in a row, not a valid email address + // local part + resetToNonEmailMatchState(); + } + else if (char === '@') { + // We read the '@' character immediately after a dot ('.'), not + // an email address + resetToNonEmailMatchState(); + } + else if (localPartCharRegex.test(char)) { + state = 2 /* LocalPart */; + } + else { + // Anything else, not an email address + resetToNonEmailMatchState(); + } + } + function stateAtSign(char) { + if (domainNameCharRegex.test(char)) { + state = 5 /* DomainChar */; + } + else { + // Anything else, not an email address + resetToNonEmailMatchState(); + } + } + function stateDomainChar(char) { + if (char === '.') { + state = 7 /* DomainDot */; + } + else if (char === '-') { + state = 6 /* DomainHyphen */; + } + else if (domainNameCharRegex.test(char)) ; + else { + // Anything else, we potentially matched if the criteria has + // been met + captureMatchIfValidAndReset(); + } + } + function stateDomainHyphen(char) { + if (char === '-' || char === '.') { + // Not valid to have two hyphens ("--") or hypen+dot ("-.") + captureMatchIfValidAndReset(); + } + else if (domainNameCharRegex.test(char)) { + state = 5 /* DomainChar */; + } + else { + // Anything else + captureMatchIfValidAndReset(); + } + } + function stateDomainDot(char) { + if (char === '.' || char === '-') { + // not valid to have two dots ("..") or dot+hypen (".-") + captureMatchIfValidAndReset(); + } + else if (domainNameCharRegex.test(char)) { + state = 5 /* DomainChar */; + // After having read a '.' and then a valid domain character, + // we now know that the domain part of the email is valid, and + // we have found at least a partial EmailMatch (however, the + // email address may have additional characters from this point) + currentEmailMatch = new CurrentEmailMatch(__assign({}, currentEmailMatch, { hasDomainDot: true })); + } + else { + // Anything else + captureMatchIfValidAndReset(); + } + } + function beginEmailMatch(newState) { + if (newState === void 0) { newState = 2 /* LocalPart */; } + state = newState; + currentEmailMatch = new CurrentEmailMatch({ idx: charIdx }); + } + function resetToNonEmailMatchState() { + state = 0 /* NonEmailMatch */; + currentEmailMatch = noCurrentEmailMatch; + } + /* + * Captures the current email address as an EmailMatch if it's valid, + * and resets the state to read another email address. + */ + function captureMatchIfValidAndReset() { + if (currentEmailMatch.hasDomainDot) { // we need at least one dot in the domain to be considered a valid email address + var matchedText = text.slice(currentEmailMatch.idx, charIdx); + // If we read a '.' or '-' char that ended the email address + // (valid domain name characters, but only valid email address + // characters if they are followed by something else), strip + // it off now + if (/[-.]$/.test(matchedText)) { + matchedText = matchedText.slice(0, -1); + } + var emailAddress = currentEmailMatch.hasMailtoPrefix + ? matchedText.slice('mailto:'.length) + : matchedText; + // if the email address has a valid TLD, add it to the list of matches + if (doesEmailHaveValidTld(emailAddress)) { + matches.push(new EmailMatch({ + tagBuilder: tagBuilder, + matchedText: matchedText, + offset: currentEmailMatch.idx, + email: emailAddress + })); + } + } + resetToNonEmailMatchState(); + /** + * Determines if the given email address has a valid TLD or not + * @param {string} emailAddress - email address + * @return {Boolean} - true is email have valid TLD, false otherwise + */ + function doesEmailHaveValidTld(emailAddress) { + var emailAddressTld = emailAddress.split('.').pop() || ''; + var emailAddressNormalized = emailAddressTld.toLowerCase(); + var isValidTld = strictTldRegex.test(emailAddressNormalized); + return isValidTld; + } + } + }; + return EmailMatcher; + }(Matcher)); + var CurrentEmailMatch = /** @class */ (function () { + function CurrentEmailMatch(cfg) { + if (cfg === void 0) { cfg = {}; } + this.idx = cfg.idx !== undefined ? cfg.idx : -1; + this.hasMailtoPrefix = !!cfg.hasMailtoPrefix; + this.hasDomainDot = !!cfg.hasDomainDot; + } + return CurrentEmailMatch; + }()); + + /** + * @private + * @class Autolinker.matcher.UrlMatchValidator + * @singleton + * + * Used by Autolinker to filter out false URL positives from the + * {@link Autolinker.matcher.Url UrlMatcher}. + * + * Due to the limitations of regular expressions (including the missing feature + * of look-behinds in JS regular expressions), we cannot always determine the + * validity of a given match. This class applies a bit of additional logic to + * filter out any false positives that have been matched by the + * {@link Autolinker.matcher.Url UrlMatcher}. + */ + var UrlMatchValidator = /** @class */ (function () { + function UrlMatchValidator() { + } + /** + * Determines if a given URL match found by the {@link Autolinker.matcher.Url UrlMatcher} + * is valid. Will return `false` for: + * + * 1) URL matches which do not have at least have one period ('.') in the + * domain name (effectively skipping over matches like "abc:def"). + * However, URL matches with a protocol will be allowed (ex: 'http://localhost') + * 2) URL matches which do not have at least one word character in the + * domain name (effectively skipping over matches like "git:1.0"). + * 3) A protocol-relative url match (a URL beginning with '//') whose + * previous character is a word character (effectively skipping over + * strings like "abc//google.com") + * + * Otherwise, returns `true`. + * + * @param {String} urlMatch The matched URL, if there was one. Will be an + * empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol + * match. Ex: 'http://yahoo.com'. This is used to match something like + * 'http://localhost', where we won't double check that the domain name + * has at least one '.' in it. + * @return {Boolean} `true` if the match given is valid and should be + * processed, or `false` if the match is invalid and/or should just not be + * processed. + */ + UrlMatchValidator.isValid = function (urlMatch, protocolUrlMatch) { + if ((protocolUrlMatch && !this.isValidUriScheme(protocolUrlMatch)) || + this.urlMatchDoesNotHaveProtocolOrDot(urlMatch, protocolUrlMatch) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL, *unless* it was a full protocol match (like 'http://localhost') + (this.urlMatchDoesNotHaveAtLeastOneWordChar(urlMatch, protocolUrlMatch) && // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0" + !this.isValidIpAddress(urlMatch)) || // Except if it's an IP address + this.containsMultipleDots(urlMatch)) { + return false; + } + return true; + }; + UrlMatchValidator.isValidIpAddress = function (uriSchemeMatch) { + var newRegex = new RegExp(this.hasFullProtocolRegex.source + this.ipRegex.source); + var uriScheme = uriSchemeMatch.match(newRegex); + return uriScheme !== null; + }; + UrlMatchValidator.containsMultipleDots = function (urlMatch) { + var stringBeforeSlash = urlMatch; + if (this.hasFullProtocolRegex.test(urlMatch)) { + stringBeforeSlash = urlMatch.split('://')[1]; + } + return stringBeforeSlash.split('/')[0].indexOf("..") > -1; + }; + /** + * Determines if the URI scheme is a valid scheme to be autolinked. Returns + * `false` if the scheme is 'javascript:' or 'vbscript:' + * + * @private + * @param {String} uriSchemeMatch The match URL string for a full URI scheme + * match. Ex: 'http://yahoo.com' or 'mailto:a@a.com'. + * @return {Boolean} `true` if the scheme is a valid one, `false` otherwise. + */ + UrlMatchValidator.isValidUriScheme = function (uriSchemeMatch) { + var uriSchemeMatchArr = uriSchemeMatch.match(this.uriSchemeRegex), uriScheme = uriSchemeMatchArr && uriSchemeMatchArr[0].toLowerCase(); + return (uriScheme !== 'javascript:' && uriScheme !== 'vbscript:'); + }; + /** + * Determines if a URL match does not have either: + * + * a) a full protocol (i.e. 'http://'), or + * b) at least one dot ('.') in the domain name (for a non-full-protocol + * match). + * + * Either situation is considered an invalid URL (ex: 'git:d' does not have + * either the '://' part, or at least one dot in the domain name. If the + * match was 'git:abc.com', we would consider this valid.) + * + * @private + * @param {String} urlMatch The matched URL, if there was one. Will be an + * empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol + * match. Ex: 'http://yahoo.com'. This is used to match something like + * 'http://localhost', where we won't double check that the domain name + * has at least one '.' in it. + * @return {Boolean} `true` if the URL match does not have a full protocol, + * or at least one dot ('.') in a non-full-protocol match. + */ + UrlMatchValidator.urlMatchDoesNotHaveProtocolOrDot = function (urlMatch, protocolUrlMatch) { + return (!!urlMatch && (!protocolUrlMatch || !this.hasFullProtocolRegex.test(protocolUrlMatch)) && urlMatch.indexOf('.') === -1); + }; + /** + * Determines if a URL match does not have at least one word character after + * the protocol (i.e. in the domain name). + * + * At least one letter character must exist in the domain name after a + * protocol match. Ex: skip over something like "git:1.0" + * + * @private + * @param {String} urlMatch The matched URL, if there was one. Will be an + * empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol + * match. Ex: 'http://yahoo.com'. This is used to know whether or not we + * have a protocol in the URL string, in order to check for a word + * character after the protocol separator (':'). + * @return {Boolean} `true` if the URL match does not have at least one word + * character in it after the protocol, `false` otherwise. + */ + UrlMatchValidator.urlMatchDoesNotHaveAtLeastOneWordChar = function (urlMatch, protocolUrlMatch) { + if (urlMatch && protocolUrlMatch) { + return !this.hasWordCharAfterProtocolRegex.test(urlMatch); + } + else { + return false; + } + }; + /** + * Regex to test for a full protocol, with the two trailing slashes. Ex: 'http://' + * + * @private + * @property {RegExp} hasFullProtocolRegex + */ + UrlMatchValidator.hasFullProtocolRegex = /^[A-Za-z][-.+A-Za-z0-9]*:\/\//; + /** + * Regex to find the URI scheme, such as 'mailto:'. + * + * This is used to filter out 'javascript:' and 'vbscript:' schemes. + * + * @private + * @property {RegExp} uriSchemeRegex + */ + UrlMatchValidator.uriSchemeRegex = /^[A-Za-z][-.+A-Za-z0-9]*:/; + /** + * Regex to determine if at least one word char exists after the protocol (i.e. after the ':') + * + * @private + * @property {RegExp} hasWordCharAfterProtocolRegex + */ + UrlMatchValidator.hasWordCharAfterProtocolRegex = new RegExp(":[^\\s]*?[" + alphaCharsStr + "]"); + /** + * Regex to determine if the string is a valid IP address + * + * @private + * @property {RegExp} ipRegex + */ + UrlMatchValidator.ipRegex = /[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?(:[0-9]*)?\/?$/; + return UrlMatchValidator; + }()); + + /** + * @class Autolinker.matcher.Url + * @extends Autolinker.matcher.Matcher + * + * Matcher to find URL matches in an input string. + * + * See this class's superclass ({@link Autolinker.matcher.Matcher}) for more details. + */ + var UrlMatcher = /** @class */ (function (_super) { + __extends(UrlMatcher, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match instance, + * specified in an Object (map). + */ + function UrlMatcher(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @cfg {Object} stripPrefix (required) + * + * The Object form of {@link Autolinker#cfg-stripPrefix}. + */ + _this.stripPrefix = { scheme: true, www: true }; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Boolean} stripTrailingSlash (required) + * @inheritdoc Autolinker#stripTrailingSlash + */ + _this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @cfg {Boolean} decodePercentEncoding (required) + * @inheritdoc Autolinker#decodePercentEncoding + */ + _this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * @protected + * @property {RegExp} matcherRegex + * + * The regular expression to match URLs with an optional scheme, port + * number, path, query string, and hash anchor. + * + * Example matches: + * + * http://google.com + * www.google.com + * google.com/path/to/file?q1=1&q2=2#myAnchor + * + * + * This regular expression will have the following capturing groups: + * + * 1. Group that matches a scheme-prefixed URL (i.e. 'http://google.com'). + * This is used to match scheme URLs with just a single word, such as + * 'http://localhost', where we won't double check that the domain name + * has at least one dot ('.') in it. + * 2. Group that matches a 'www.' prefixed URL. This is only matched if the + * 'www.' text was not prefixed by a scheme (i.e.: not prefixed by + * 'http://', 'ftp:', etc.) + * 3. A protocol-relative ('//') match for the case of a 'www.' prefixed + * URL. Will be an empty string if it is not a protocol-relative match. + * We need to know the character before the '//' in order to determine + * if it is a valid match or the // was in a string we don't want to + * auto-link. + * 4. Group that matches a known TLD (top level domain), when a scheme + * or 'www.'-prefixed domain is not matched. + * 5. A protocol-relative ('//') match for the case of a known TLD prefixed + * URL. Will be an empty string if it is not a protocol-relative match. + * See #3 for more info. + */ + _this.matcherRegex = (function () { + var schemeRegex = /(?:[A-Za-z][-.+A-Za-z0-9]{0,63}:(?![A-Za-z][-.+A-Za-z0-9]{0,63}:\/\/)(?!\d+\/?)(?:\/\/)?)/, // match protocol, allow in format "http://" or "mailto:". However, do not match the first part of something like 'link:http://www.google.com' (i.e. don't match "link:"). Also, make sure we don't interpret 'google.com:8000' as if 'google.com' was a protocol here (i.e. ignore a trailing port number in this regex) + wwwRegex = /(?:www\.)/, // starting with 'www.' + // Allow optional path, query string, and hash anchor, not ending in the following characters: "?!:,.;" + // http://blog.codinghorror.com/the-problem-with-urls/ + urlSuffixRegex = new RegExp('[/?#](?:[' + alphaNumericAndMarksCharsStr + '\\-+&@#/%=~_()|\'$*\\[\\]?!:,.;\u2713]*[' + alphaNumericAndMarksCharsStr + '\\-+&@#/%=~_()|\'$*\\[\\]\u2713])?'); + return new RegExp([ + '(?:', + '(', + schemeRegex.source, + getDomainNameStr(2), + ')', + '|', + '(', + '(//)?', + wwwRegex.source, + getDomainNameStr(6), + ')', + '|', + '(', + '(//)?', + getDomainNameStr(10) + '\\.', + tldRegex.source, + '(?![-' + alphaNumericCharsStr + '])', + ')', + ')', + '(?::[0-9]+)?', + '(?:' + urlSuffixRegex.source + ')?' // match for path, query string, and/or hash anchor - optional + ].join(""), 'gi'); + })(); + /** + * A regular expression to use to check the character before a protocol-relative + * URL match. We don't want to match a protocol-relative URL if it is part + * of another word. + * + * For example, we want to match something like "Go to: //google.com", + * but we don't want to match something like "abc//google.com" + * + * This regular expression is used to test the character before the '//'. + * + * @protected + * @type {RegExp} wordCharRegExp + */ + _this.wordCharRegExp = new RegExp('[' + alphaNumericAndMarksCharsStr + ']'); + _this.stripPrefix = cfg.stripPrefix; + _this.stripTrailingSlash = cfg.stripTrailingSlash; + _this.decodePercentEncoding = cfg.decodePercentEncoding; + return _this; + } + /** + * @inheritdoc + */ + UrlMatcher.prototype.parseMatches = function (text) { + var matcherRegex = this.matcherRegex, stripPrefix = this.stripPrefix, stripTrailingSlash = this.stripTrailingSlash, decodePercentEncoding = this.decodePercentEncoding, tagBuilder = this.tagBuilder, matches = [], match; + var _loop_1 = function () { + var matchStr = match[0], schemeUrlMatch = match[1], wwwUrlMatch = match[4], wwwProtocolRelativeMatch = match[5], + //tldUrlMatch = match[ 8 ], -- not needed at the moment + tldProtocolRelativeMatch = match[9], offset = match.index, protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch, prevChar = text.charAt(offset - 1); + if (!UrlMatchValidator.isValid(matchStr, schemeUrlMatch)) { + return "continue"; + } + // If the match is preceded by an '@' character, then it is either + // an email address or a username. Skip these types of matches. + if (offset > 0 && prevChar === '@') { + return "continue"; + } + // If it's a protocol-relative '//' match, but the character before the '//' + // was a word character (i.e. a letter/number), then we found the '//' in the + // middle of another word (such as "asdf//asdf.com"). In this case, skip the + // match. + if (offset > 0 && protocolRelativeMatch && this_1.wordCharRegExp.test(prevChar)) { + return "continue"; + } + // If the URL ends with a question mark, don't include the question + // mark as part of the URL. We'll assume the question mark was the + // end of a sentence, such as: "Going to google.com?" + if (/\?$/.test(matchStr)) { + matchStr = matchStr.substr(0, matchStr.length - 1); + } + // Handle a closing parenthesis or square bracket at the end of the + // match, and exclude it if there is not a matching open parenthesis + // or square bracket in the match itself. + if (this_1.matchHasUnbalancedClosingParen(matchStr)) { + matchStr = matchStr.substr(0, matchStr.length - 1); // remove the trailing ")" + } + else { + // Handle an invalid character after the TLD + var pos = this_1.matchHasInvalidCharAfterTld(matchStr, schemeUrlMatch); + if (pos > -1) { + matchStr = matchStr.substr(0, pos); // remove the trailing invalid chars + } + } + // The autolinker accepts many characters in a url's scheme (like `fake://test.com`). + // However, in cases where a URL is missing whitespace before an obvious link, + // (for example: `nowhitespacehttp://www.test.com`), we only want the match to start + // at the http:// part. We will check if the match contains a common scheme and then + // shift the match to start from there. + var foundCommonScheme = ['http://', 'https://'].find(function (commonScheme) { return !!schemeUrlMatch && schemeUrlMatch.indexOf(commonScheme) !== -1; }); + if (foundCommonScheme) { + // If we found an overmatched URL, we want to find the index + // of where the match should start and shift the match to + // start from the beginning of the common scheme + var indexOfSchemeStart = matchStr.indexOf(foundCommonScheme); + matchStr = matchStr.substr(indexOfSchemeStart); + schemeUrlMatch = schemeUrlMatch.substr(indexOfSchemeStart); + offset = offset + indexOfSchemeStart; + } + var urlMatchType = schemeUrlMatch ? 'scheme' : (wwwUrlMatch ? 'www' : 'tld'), protocolUrlMatch = !!schemeUrlMatch; + matches.push(new UrlMatch({ + tagBuilder: tagBuilder, + matchedText: matchStr, + offset: offset, + urlMatchType: urlMatchType, + url: matchStr, + protocolUrlMatch: protocolUrlMatch, + protocolRelativeMatch: !!protocolRelativeMatch, + stripPrefix: stripPrefix, + stripTrailingSlash: stripTrailingSlash, + decodePercentEncoding: decodePercentEncoding, + })); + }; + var this_1 = this; + while ((match = matcherRegex.exec(text)) !== null) { + _loop_1(); + } + return matches; + }; + /** + * Determines if a match found has an unmatched closing parenthesis or + * square bracket. If so, the parenthesis or square bracket will be removed + * from the match itself, and appended after the generated anchor tag. + * + * A match may have an extra closing parenthesis at the end of the match + * because the regular expression must include parenthesis for URLs such as + * "wikipedia.com/something_(disambiguation)", which should be auto-linked. + * + * However, an extra parenthesis *will* be included when the URL itself is + * wrapped in parenthesis, such as in the case of: + * "(wikipedia.com/something_(disambiguation))" + * In this case, the last closing parenthesis should *not* be part of the + * URL itself, and this method will return `true`. + * + * For square brackets in URLs such as in PHP arrays, the same behavior as + * parenthesis discussed above should happen: + * "[http://www.example.com/foo.php?bar[]=1&bar[]=2&bar[]=3]" + * The closing square bracket should not be part of the URL itself, and this + * method will return `true`. + * + * @protected + * @param {String} matchStr The full match string from the {@link #matcherRegex}. + * @return {Boolean} `true` if there is an unbalanced closing parenthesis or + * square bracket at the end of the `matchStr`, `false` otherwise. + */ + UrlMatcher.prototype.matchHasUnbalancedClosingParen = function (matchStr) { + var endChar = matchStr.charAt(matchStr.length - 1); + var startChar; + if (endChar === ')') { + startChar = '('; + } + else if (endChar === ']') { + startChar = '['; + } + else { + return false; // not a close parenthesis or square bracket + } + // Find if there are the same number of open braces as close braces in + // the URL string, minus the last character (which we have already + // determined to be either ')' or ']' + var numOpenBraces = 0; + for (var i = 0, len = matchStr.length - 1; i < len; i++) { + var char = matchStr.charAt(i); + if (char === startChar) { + numOpenBraces++; + } + else if (char === endChar) { + numOpenBraces = Math.max(numOpenBraces - 1, 0); + } + } + // If the number of open braces matches the number of close braces in + // the URL minus the last character, then the match has *unbalanced* + // braces because of the last character. Example of unbalanced braces + // from the regex match: + // "http://example.com?a[]=1]" + if (numOpenBraces === 0) { + return true; + } + return false; + }; + /** + * Determine if there's an invalid character after the TLD in a URL. Valid + * characters after TLD are ':/?#'. Exclude scheme matched URLs from this + * check. + * + * @protected + * @param {String} urlMatch The matched URL, if there was one. Will be an + * empty string if the match is not a URL match. + * @param {String} schemeUrlMatch The match URL string for a scheme + * match. Ex: 'http://yahoo.com'. This is used to match something like + * 'http://localhost', where we won't double check that the domain name + * has at least one '.' in it. + * @return {Number} the position where the invalid character was found. If + * no such character was found, returns -1 + */ + UrlMatcher.prototype.matchHasInvalidCharAfterTld = function (urlMatch, schemeUrlMatch) { + if (!urlMatch) { + return -1; + } + var offset = 0; + if (schemeUrlMatch) { + offset = urlMatch.indexOf(':'); + urlMatch = urlMatch.slice(offset); + } + var re = new RegExp("^((.?\/\/)?[-." + alphaNumericAndMarksCharsStr + "]*[-" + alphaNumericAndMarksCharsStr + "]\\.[-" + alphaNumericAndMarksCharsStr + "]+)"); + var res = re.exec(urlMatch); + if (res === null) { + return -1; + } + offset += res[1].length; + urlMatch = urlMatch.slice(res[1].length); + if (/^[^-.A-Za-z0-9:\/?#]/.test(urlMatch)) { + return offset; + } + return -1; + }; + return UrlMatcher; + }(Matcher)); + + /** + * @class Autolinker.matcher.Hashtag + * @extends Autolinker.matcher.Matcher + * + * Matcher to find HashtagMatch matches in an input string. + */ + var HashtagMatcher = /** @class */ (function (_super) { + __extends(HashtagMatcher, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match instance, + * specified in an Object (map). + */ + function HashtagMatcher(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @cfg {String} serviceName + * + * The service to point hashtag matches to. See {@link Autolinker#hashtag} + * for available values. + */ + _this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * The regular expression to match Hashtags. Example match: + * + * #asdf + * + * @protected + * @property {RegExp} matcherRegex + */ + _this.matcherRegex = new RegExp("#[_" + alphaNumericAndMarksCharsStr + "]{1,139}(?![_" + alphaNumericAndMarksCharsStr + "])", 'g'); // lookahead used to make sure we don't match something above 139 characters + /** + * The regular expression to use to check the character before a username match to + * make sure we didn't accidentally match an email address. + * + * For example, the string "asdf@asdf.com" should not match "@asdf" as a username. + * + * @protected + * @property {RegExp} nonWordCharRegex + */ + _this.nonWordCharRegex = new RegExp('[^' + alphaNumericAndMarksCharsStr + ']'); + _this.serviceName = cfg.serviceName; + return _this; + } + /** + * @inheritdoc + */ + HashtagMatcher.prototype.parseMatches = function (text) { + var matcherRegex = this.matcherRegex, nonWordCharRegex = this.nonWordCharRegex, serviceName = this.serviceName, tagBuilder = this.tagBuilder, matches = [], match; + while ((match = matcherRegex.exec(text)) !== null) { + var offset = match.index, prevChar = text.charAt(offset - 1); + // If we found the match at the beginning of the string, or we found the match + // and there is a whitespace char in front of it (meaning it is not a '#' char + // in the middle of a word), then it is a hashtag match. + if (offset === 0 || nonWordCharRegex.test(prevChar)) { + var matchedText = match[0], hashtag = match[0].slice(1); // strip off the '#' character at the beginning + matches.push(new HashtagMatch({ + tagBuilder: tagBuilder, + matchedText: matchedText, + offset: offset, + serviceName: serviceName, + hashtag: hashtag + })); + } + } + return matches; + }; + return HashtagMatcher; + }(Matcher)); + + /** + * @class Autolinker.matcher.Phone + * @extends Autolinker.matcher.Matcher + * + * Matcher to find Phone number matches in an input string. + * + * See this class's superclass ({@link Autolinker.matcher.Matcher}) for more + * details. + */ + var PhoneMatcher = /** @class */ (function (_super) { + __extends(PhoneMatcher, _super); + function PhoneMatcher() { + var _this = _super !== null && _super.apply(this, arguments) || this; + /** + * The regular expression to match Phone numbers. Example match: + * + * (123) 456-7890 + * + * This regular expression has the following capturing groups: + * + * 1 or 2. The prefixed '+' sign, if there is one. + * + * @protected + * @property {RegExp} matcherRegex + */ + _this.matcherRegex = /(?:(?:(?:(\+)?\d{1,3}[-\040.]?)?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]?\d{4})|(?:(\+)(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-\040.]?(?:\d[-\040.]?){6,12}\d+))([,;]+[0-9]+#?)*/g; + return _this; + } + // ex: (123) 456-7890, 123 456 7890, 123-456-7890, +18004441234,,;,10226420346#, + // +1 (800) 444 1234, 10226420346#, 1-800-444-1234,1022,64,20346# + /** + * @inheritdoc + */ + PhoneMatcher.prototype.parseMatches = function (text) { + var matcherRegex = this.matcherRegex, tagBuilder = this.tagBuilder, matches = [], match; + while ((match = matcherRegex.exec(text)) !== null) { + // Remove non-numeric values from phone number string + var matchedText = match[0], cleanNumber = matchedText.replace(/[^0-9,;#]/g, ''), // strip out non-digit characters exclude comma semicolon and # + plusSign = !!(match[1] || match[2]), // match[ 1 ] or match[ 2 ] is the prefixed plus sign, if there is one + before = match.index == 0 ? '' : text.substr(match.index - 1, 1), after = text.substr(match.index + matchedText.length, 1), contextClear = !before.match(/\d/) && !after.match(/\d/); + if (this.testMatch(match[3]) && this.testMatch(matchedText) && contextClear) { + matches.push(new PhoneMatch({ + tagBuilder: tagBuilder, + matchedText: matchedText, + offset: match.index, + number: cleanNumber, + plusSign: plusSign + })); + } + } + return matches; + }; + PhoneMatcher.prototype.testMatch = function (text) { + return /\D/.test(text); + }; + return PhoneMatcher; + }(Matcher)); + + /** + * @class Autolinker.matcher.Mention + * @extends Autolinker.matcher.Matcher + * + * Matcher to find/replace username matches in an input string. + */ + var MentionMatcher = /** @class */ (function (_super) { + __extends(MentionMatcher, _super); + /** + * @method constructor + * @param {Object} cfg The configuration properties for the Match instance, + * specified in an Object (map). + */ + function MentionMatcher(cfg) { + var _this = _super.call(this, cfg) || this; + /** + * @cfg {'twitter'/'instagram'/'soundcloud'} protected + * + * The name of service to link @mentions to. + * + * Valid values are: 'twitter', 'instagram', or 'soundcloud' + */ + _this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator + /** + * Hash of regular expression to match username handles. Example match: + * + * @asdf + * + * @private + * @property {Object} matcherRegexes + */ + _this.matcherRegexes = { + 'twitter': new RegExp("@[_" + alphaNumericAndMarksCharsStr + "]{1,50}(?![_" + alphaNumericAndMarksCharsStr + "])", 'g'), + 'instagram': new RegExp("@[_." + alphaNumericAndMarksCharsStr + "]{1,30}(?![_" + alphaNumericAndMarksCharsStr + "])", 'g'), + 'soundcloud': new RegExp("@[-_." + alphaNumericAndMarksCharsStr + "]{1,50}(?![-_" + alphaNumericAndMarksCharsStr + "])", 'g') // lookahead used to make sure we don't match something above 50 characters + }; + /** + * The regular expression to use to check the character before a username match to + * make sure we didn't accidentally match an email address. + * + * For example, the string "asdf@asdf.com" should not match "@asdf" as a username. + * + * @private + * @property {RegExp} nonWordCharRegex + */ + _this.nonWordCharRegex = new RegExp('[^' + alphaNumericAndMarksCharsStr + ']'); + _this.serviceName = cfg.serviceName; + return _this; + } + /** + * @inheritdoc + */ + MentionMatcher.prototype.parseMatches = function (text) { + var serviceName = this.serviceName, matcherRegex = this.matcherRegexes[this.serviceName], nonWordCharRegex = this.nonWordCharRegex, tagBuilder = this.tagBuilder, matches = [], match; + if (!matcherRegex) { + return matches; + } + while ((match = matcherRegex.exec(text)) !== null) { + var offset = match.index, prevChar = text.charAt(offset - 1); + // If we found the match at the beginning of the string, or we found the match + // and there is a whitespace char in front of it (meaning it is not an email + // address), then it is a username match. + if (offset === 0 || nonWordCharRegex.test(prevChar)) { + var matchedText = match[0].replace(/\.+$/g, ''), // strip off trailing . + mention = matchedText.slice(1); // strip off the '@' character at the beginning + matches.push(new MentionMatch({ + tagBuilder: tagBuilder, + matchedText: matchedText, + offset: offset, + serviceName: serviceName, + mention: mention + })); + } + } + return matches; + }; + return MentionMatcher; + }(Matcher)); + + // For debugging: search for other "For debugging" lines + // import CliTable from 'cli-table'; + /** + * Parses an HTML string, calling the callbacks to notify of tags and text. + * + * ## History + * + * This file previously used a regular expression to find html tags in the input + * text. Unfortunately, we ran into a bunch of catastrophic backtracking issues + * with certain input text, causing Autolinker to either hang or just take a + * really long time to parse the string. + * + * The current code is intended to be a O(n) algorithm that walks through + * the string in one pass, and tries to be as cheap as possible. We don't need + * to implement the full HTML spec, but rather simply determine where the string + * looks like an HTML tag, and where it looks like text (so that we can autolink + * that). + * + * This state machine parser is intended just to be a simple but performant + * parser of HTML for the subset of requirements we have. We simply need to: + * + * 1. Determine where HTML tags are + * 2. Determine the tag name (Autolinker specifically only cares about , + * - - - -   + + diff --git a/Specs/TestWorkers/.eslintrc.json b/Specs/TestWorkers/.eslintrc.json new file mode 100644 index 00000000000..50d7d84f480 --- /dev/null +++ b/Specs/TestWorkers/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": "../.eslintrc.json", + "env": { + "amd": true + }, + "parserOptions": { + "ecmaVersion": 5, + "sourceType": "script" + } +} diff --git a/Specs/TestWorkers/createBadGeometry.js b/Specs/TestWorkers/createBadGeometry.js index b8eb48b3c5a..e5746f8b33d 100644 --- a/Specs/TestWorkers/createBadGeometry.js +++ b/Specs/TestWorkers/createBadGeometry.js @@ -1,10 +1,7 @@ -define([ - 'Core/RuntimeError' - ], function( - RuntimeError) { +define(function() { 'use strict'; return function() { - throw new RuntimeError('BadGeometry.createGeometry'); + throw new Error('BadGeometry.createGeometry'); }; }); diff --git a/Specs/TestWorkers/throwError.js b/Specs/TestWorkers/throwError.js index c152f2b4fc3..a37c77eb7d9 100644 --- a/Specs/TestWorkers/throwError.js +++ b/Specs/TestWorkers/throwError.js @@ -1,12 +1,10 @@ define([ - 'Core/RuntimeError', 'Workers/createTaskProcessorWorker' ], function( - RuntimeError, createTaskProcessorWorker) { 'use strict'; return createTaskProcessorWorker(function(parameters, transferableObjects) { - throw new RuntimeError(parameters.message); + throw new Error(parameters.message); }); }); diff --git a/Specs/karma-main.js b/Specs/karma-main.js index 664b6402704..44f130e7b53 100644 --- a/Specs/karma-main.js +++ b/Specs/karma-main.js @@ -1,73 +1,19 @@ -(function() { - /*global __karma__*/ - 'use strict'; - - var included = ''; - var excluded = ''; - var webglValidation = false; - var webglStub = false; - var release = false; - - if(__karma__.config.args){ - included = __karma__.config.args[0]; - excluded = __karma__.config.args[1]; - webglValidation = __karma__.config.args[2]; - webglStub = __karma__.config.args[3]; - release = __karma__.config.args[4]; - } - - var toRequire = ['Cesium']; - - if (release) { - require.config({ - baseUrl : '/base/Build/Cesium', - waitSeconds : 0 - }); - toRequire.push('../Stubs/paths'); - } else { - require.config({ - baseUrl : '/base/Source', - waitSeconds : 0 - }); - } - - require(toRequire, function (Cesium, paths) { - if (release) { - paths.Specs = '../../Specs'; - paths.Source = '../../Source'; - paths.Stubs = '../Stubs'; - - require.config({ - paths: paths, - shim: { - 'Cesium': { - exports: 'Cesium' - } - } - }); - } else { - require.config({ - paths: { - 'Specs': '../Specs', - 'Source' : '.' - } - }); - } - - require([ - 'Specs/customizeJasmine' - ], function( - customizeJasmine) { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; - customizeJasmine(jasmine.getEnv(), included, excluded, webglValidation, webglStub, release); - - var specFiles = Object.keys(__karma__.files).filter(function(file) { - return /Spec\.js$/.test(file); - }); - - require(specFiles, function() { - __karma__.start(); - }); - }); - }); -})(); +/*global __karma__*/ +import customizeJasmine from './customizeJasmine.js'; + +var included = ''; +var excluded = ''; +var webglValidation = false; +var webglStub = false; +var release = false; + +if (__karma__.config.args) { + included = __karma__.config.args[0]; + excluded = __karma__.config.args[1]; + webglValidation = __karma__.config.args[2]; + webglStub = __karma__.config.args[3]; + release = __karma__.config.args[4]; +} + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; +customizeJasmine(jasmine.getEnv(), included, excluded, webglValidation, webglStub, release); diff --git a/Specs/karma.conf.js b/Specs/karma.conf.js index f1323de2af6..37393172fb5 100644 --- a/Specs/karma.conf.js +++ b/Specs/karma.conf.js @@ -11,7 +11,7 @@ module.exports = function(config) { // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks : ['jasmine', 'requirejs', 'detectBrowsers'], + frameworks : ['jasmine', 'detectBrowsers'], client: { captureConsole: false, @@ -27,17 +27,30 @@ module.exports = function(config) { // list of files / patterns to load in the browser files : [ - 'Specs/karma-main.js', - {pattern : 'Source/**', included : false}, - {pattern : 'Specs/**', included : false} + { pattern: 'Specs/karma-main.js', included: true, type: 'module' }, + { pattern: 'Source/**', included: false, type: 'module' }, + { pattern: 'Specs/*.js', included: true, type: 'module' }, + { pattern: 'Specs/Core/**', included: true, type: 'module' }, + { pattern: 'Specs/Data/**', included: false }, + { pattern: 'Specs/DataSources/**', included: true, type: 'module' }, + { pattern: 'Specs/Renderer/**', included: true, type: 'module' }, + { pattern: 'Specs/Scene/**', included: true, type: 'module' }, + { pattern: 'Specs/ThirdParty/**', included: true, type: 'module' }, + { pattern: 'Specs/Widgets/**', included: true, type: 'module' }, + { pattern: 'Specs/TestWorkers/**', included: false } ], proxies : { - '/Data' : '/base/Specs/Data' + '/Data' : '/base/Specs/Data', + '/Specs/TestWorkers' : '/base/Specs/TestWorkers' }, // list of files to exclude - exclude : [], + exclude: [ + 'Specs/SpecList.js', + 'Specs/SpecRunner.js', + 'Specs/spec-main.js' + ], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor diff --git a/Specs/spec-main.js b/Specs/spec-main.js index 30a094dcb06..caffe5e7835 100644 --- a/Specs/spec-main.js +++ b/Specs/spec-main.js @@ -1,5 +1,5 @@ /** - This is a version of Jasmine's boot.js modified to work with specs defined with AMD. The original comments from boot.js follow. + This is a version of Jasmine's boot.js modified to work with specs defined with ES6. The original comments from boot.js follow. Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. @@ -10,64 +10,29 @@ [jasmine-gem]: http://github.com/pivotal/jasmine-gem */ -(function() { - 'use strict'; +// Use a pragma so we can remove this code when building specs for running in ES5 - // set this for uniform test resolution across devices - window.devicePixelRatio = 1; +//>>includeStart('debug', pragmas.debug); +import * as Cesium from '../Source/Cesium.js'; +//>>includeEnd('debug'); - function getQueryParameter(name) { - var match = new RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search); - return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); - } +import addDefaultMatchers from './addDefaultMatchers.js'; +import equalsMethodEqualityTester from './equalsMethodEqualityTester.js'; - var built = getQueryParameter('built'); - var release = getQueryParameter('release'); + // set this for uniform test resolution across devices + window.devicePixelRatio = 1; - var toRequire = ['Cesium']; - - if (built) { - require.config({ - waitSeconds: 30, - baseUrl: getQueryParameter('baseUrl') || '../Build/Cesium', - paths: { - 'Stubs': '../Stubs', - 'Specs': '../../Specs', - 'Source' : '../../Source' - }, - shim: { - 'Cesium': { - exports: 'Cesium' - } - } - }); - - toRequire.push('./Stubs/paths'); - } else { - require.config({ - waitSeconds: 30, - baseUrl: getQueryParameter('baseUrl') || '../Source', - paths: { - 'Specs': '../Specs', - 'Source' : '.' - } - }); - } + function getQueryParameter(name) { + var match = new RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search); + return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); + } - require(toRequire, function( - Cesium, - paths) { + var release = getQueryParameter('release'); - /*global jasmineRequire,exports,specs*/ + /*global jasmineRequire,jasmine,exports,specs*/ var when = Cesium.when; - if (typeof paths !== 'undefined') { - require.config({ - paths : paths - }); - } - /** * ## Require & Instantiate * @@ -344,17 +309,17 @@ * * Load the modules via AMD, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. */ - var modules = ['Specs/addDefaultMatchers', 'Specs/equalsMethodEqualityTester'].concat(specs); - require(modules, function(addDefaultMatchers, equalsMethodEqualityTester) { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; - htmlReporter.initialize(); + jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; + + htmlReporter.initialize(); - var release = getQueryParameter('release'); - env.beforeEach(function() { addDefaultMatchers(!release).call(env); }); - env.beforeEach(function() { env.addCustomEqualityTester(equalsMethodEqualityTester); }); + var release = getQueryParameter('release'); + env.beforeEach(function() { addDefaultMatchers(!release).call(env); }); + env.beforeEach(function() { env.addCustomEqualityTester(equalsMethodEqualityTester); }); + //>>includeStart('debug', pragmas.debug); + import('./SpecList.js').then(function() { env.execute(); - }); - }); -})(); + }) + //>>includeEnd('debug'); diff --git a/ThirdParty/requirejs-2.1.20/MIT.LICENSE b/ThirdParty/requirejs-2.1.20/MIT.LICENSE deleted file mode 100644 index edc8ca84dca..00000000000 --- a/ThirdParty/requirejs-2.1.20/MIT.LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2010-2015, The Dojo Foundation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/ThirdParty/requirejs-2.1.20/domReady.js b/ThirdParty/requirejs-2.1.20/domReady.js deleted file mode 100644 index 2b541220981..00000000000 --- a/ThirdParty/requirejs-2.1.20/domReady.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/requirejs/domReady for details - */ -/*jslint */ -/*global require: false, define: false, requirejs: false, - window: false, clearInterval: false, document: false, - self: false, setInterval: false */ - - -define(function () { - 'use strict'; - - var isTop, testDiv, scrollIntervalId, - isBrowser = typeof window !== "undefined" && window.document, - isPageLoaded = !isBrowser, - doc = isBrowser ? document : null, - readyCalls = []; - - function runCallbacks(callbacks) { - var i; - for (i = 0; i < callbacks.length; i += 1) { - callbacks[i](doc); - } - } - - function callReady() { - var callbacks = readyCalls; - - if (isPageLoaded) { - //Call the DOM ready callbacks - if (callbacks.length) { - readyCalls = []; - runCallbacks(callbacks); - } - } - } - - /** - * Sets the page as loaded. - */ - function pageLoaded() { - if (!isPageLoaded) { - isPageLoaded = true; - if (scrollIntervalId) { - clearInterval(scrollIntervalId); - } - - callReady(); - } - } - - if (isBrowser) { - if (document.addEventListener) { - //Standards. Hooray! Assumption here that if standards based, - //it knows about DOMContentLoaded. - document.addEventListener("DOMContentLoaded", pageLoaded, false); - window.addEventListener("load", pageLoaded, false); - } else if (window.attachEvent) { - window.attachEvent("onload", pageLoaded); - - testDiv = document.createElement('div'); - try { - isTop = window.frameElement === null; - } catch (e) {} - - //DOMContentLoaded approximation that uses a doScroll, as found by - //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, - //but modified by other contributors, including jdalton - if (testDiv.doScroll && isTop && window.external) { - scrollIntervalId = setInterval(function () { - try { - testDiv.doScroll(); - pageLoaded(); - } catch (e) {} - }, 30); - } - } - - //Check if document already complete, and if so, just trigger page load - //listeners. Latest webkit browsers also use "interactive", and - //will fire the onDOMContentLoaded before "interactive" but not after - //entering "interactive" or "complete". More details: - //http://dev.w3.org/html5/spec/the-end.html#the-end - //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded - //Hmm, this is more complicated on further use, see "firing too early" - //bug: https://github.com/requirejs/domReady/issues/1 - //so removing the || document.readyState === "interactive" test. - //There is still a window.onload binding that should get fired if - //DOMContentLoaded is missed. - if (document.readyState === "complete") { - pageLoaded(); - } - } - - /** START OF PUBLIC API **/ - - /** - * Registers a callback for DOM ready. If DOM is already ready, the - * callback is called immediately. - * @param {Function} callback - */ - function domReady(callback) { - if (isPageLoaded) { - callback(doc); - } else { - readyCalls.push(callback); - } - return domReady; - } - - domReady.version = '2.0.1'; - - /** - * Loader Plugin API method - */ - domReady.load = function (name, req, onLoad, config) { - if (config.isBuild) { - onLoad(null); - } else { - domReady(onLoad); - } - }; - - /** END OF PUBLIC API **/ - - return domReady; -}); diff --git a/ThirdParty/requirejs-2.1.20/require.js b/ThirdParty/requirejs-2.1.20/require.js deleted file mode 100644 index 5237640495b..00000000000 --- a/ThirdParty/requirejs-2.1.20/require.js +++ /dev/null @@ -1,2103 +0,0 @@ -/** vim: et:ts=4:sw=4:sts=4 - * @license RequireJS 2.1.20 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/jrburke/requirejs for details - */ -//Not using strict: uneven strict support in browsers, #392, and causes -//problems with requirejs.exec()/transpiler plugins that may not be strict. -/*jslint regexp: true, nomen: true, sloppy: true */ -/*global window, navigator, document, importScripts, setTimeout, opera */ - -var requirejs, require, define; -(function (global) { - var req, s, head, baseElement, dataMain, src, - interactiveScript, currentlyAddingScript, mainScript, subPath, - version = '2.1.20', - commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, - cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, - jsSuffixRegExp = /\.js$/, - currDirRegExp = /^\.\//, - op = Object.prototype, - ostring = op.toString, - hasOwn = op.hasOwnProperty, - ap = Array.prototype, - isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document), - isWebWorker = !isBrowser && typeof importScripts !== 'undefined', - //PS3 indicates loaded and complete, but need to wait for complete - //specifically. Sequence is 'loading', 'loaded', execution, - // then 'complete'. The UA check is unfortunate, but not sure how - //to feature test w/o causing perf issues. - readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ? - /^complete$/ : /^(complete|loaded)$/, - defContextName = '_', - //Oh the tragedy, detecting opera. See the usage of isOpera for reason. - isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]', - contexts = {}, - cfg = {}, - globalDefQueue = [], - useInteractive = false; - - function isFunction(it) { - return ostring.call(it) === '[object Function]'; - } - - function isArray(it) { - return ostring.call(it) === '[object Array]'; - } - - /** - * Helper function for iterating over an array. If the func returns - * a true value, it will break out of the loop. - */ - function each(ary, func) { - if (ary) { - var i; - for (i = 0; i < ary.length; i += 1) { - if (ary[i] && func(ary[i], i, ary)) { - break; - } - } - } - } - - /** - * Helper function for iterating over an array backwards. If the func - * returns a true value, it will break out of the loop. - */ - function eachReverse(ary, func) { - if (ary) { - var i; - for (i = ary.length - 1; i > -1; i -= 1) { - if (ary[i] && func(ary[i], i, ary)) { - break; - } - } - } - } - - function hasProp(obj, prop) { - return hasOwn.call(obj, prop); - } - - function getOwn(obj, prop) { - return hasProp(obj, prop) && obj[prop]; - } - - /** - * Cycles over properties in an object and calls a function for each - * property value. If the function returns a truthy value, then the - * iteration is stopped. - */ - function eachProp(obj, func) { - var prop; - for (prop in obj) { - if (hasProp(obj, prop)) { - if (func(obj[prop], prop)) { - break; - } - } - } - } - - /** - * Simple function to mix in properties from source into target, - * but only if target does not already have a property of the same name. - */ - function mixin(target, source, force, deepStringMixin) { - if (source) { - eachProp(source, function (value, prop) { - if (force || !hasProp(target, prop)) { - if (deepStringMixin && typeof value === 'object' && value && - !isArray(value) && !isFunction(value) && - !(value instanceof RegExp)) { - - if (!target[prop]) { - target[prop] = {}; - } - mixin(target[prop], value, force, deepStringMixin); - } else { - target[prop] = value; - } - } - }); - } - return target; - } - - //Similar to Function.prototype.bind, but the 'this' object is specified - //first, since it is easier to read/figure out what 'this' will be. - function bind(obj, fn) { - return function () { - return fn.apply(obj, arguments); - }; - } - - function scripts() { - return document.getElementsByTagName('script'); - } - - function defaultOnError(err) { - throw err; - } - - //Allow getting a global that is expressed in - //dot notation, like 'a.b.c'. - function getGlobal(value) { - if (!value) { - return value; - } - var g = global; - each(value.split('.'), function (part) { - g = g[part]; - }); - return g; - } - - /** - * Constructs an error with a pointer to an URL with more information. - * @param {String} id the error ID that maps to an ID on a web page. - * @param {String} message human readable error. - * @param {Error} [err] the original error, if there is one. - * - * @returns {Error} - */ - function makeError(id, msg, err, requireModules) { - var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id); - e.requireType = id; - e.requireModules = requireModules; - if (err) { - e.originalError = err; - } - return e; - } - - if (typeof define !== 'undefined') { - //If a define is already in play via another AMD loader, - //do not overwrite. - return; - } - - if (typeof requirejs !== 'undefined') { - if (isFunction(requirejs)) { - //Do not overwrite an existing requirejs instance. - return; - } - cfg = requirejs; - requirejs = undefined; - } - - //Allow for a require config object - if (typeof require !== 'undefined' && !isFunction(require)) { - //assume it is a config object. - cfg = require; - require = undefined; - } - - function newContext(contextName) { - var inCheckLoaded, Module, context, handlers, - checkLoadedTimeoutId, - config = { - //Defaults. Do not set a default for map - //config to speed up normalize(), which - //will run faster if there is no default. - waitSeconds: 7, - baseUrl: './', - paths: {}, - bundles: {}, - pkgs: {}, - shim: {}, - config: {} - }, - registry = {}, - //registry of just enabled modules, to speed - //cycle breaking code when lots of modules - //are registered, but not activated. - enabledRegistry = {}, - undefEvents = {}, - defQueue = [], - defined = {}, - urlFetched = {}, - bundlesMap = {}, - requireCounter = 1, - unnormalizedCounter = 1; - - /** - * Trims the . and .. from an array of path segments. - * It will keep a leading path segment if a .. will become - * the first path segment, to help with module name lookups, - * which act like paths, but can be remapped. But the end result, - * all paths that use this function should look normalized. - * NOTE: this method MODIFIES the input array. - * @param {Array} ary the array of path segments. - */ - function trimDots(ary) { - var i, part; - for (i = 0; i < ary.length; i++) { - part = ary[i]; - if (part === '.') { - ary.splice(i, 1); - i -= 1; - } else if (part === '..') { - // If at the start, or previous value is still .., - // keep them so that when converted to a path it may - // still work when converted to a path, even though - // as an ID it is less than ideal. In larger point - // releases, may be better to just kick out an error. - if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') { - continue; - } else if (i > 0) { - ary.splice(i - 1, 2); - i -= 2; - } - } - } - } - - /** - * Given a relative module name, like ./something, normalize it to - * a real name that can be mapped to a path. - * @param {String} name the relative name - * @param {String} baseName a real name that the name arg is relative - * to. - * @param {Boolean} applyMap apply the map config to the value. Should - * only be done if this normalization is for a dependency ID. - * @returns {String} normalized name - */ - function normalize(name, baseName, applyMap) { - var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex, - foundMap, foundI, foundStarMap, starI, normalizedBaseParts, - baseParts = (baseName && baseName.split('/')), - map = config.map, - starMap = map && map['*']; - - //Adjust any relative paths. - if (name) { - name = name.split('/'); - lastIndex = name.length - 1; - - // If wanting node ID compatibility, strip .js from end - // of IDs. Have to do this here, and not in nameToUrl - // because node allows either .js or non .js to map - // to same file. - if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { - name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); - } - - // Starts with a '.' so need the baseName - if (name[0].charAt(0) === '.' && baseParts) { - //Convert baseName to array, and lop off the last part, - //so that . matches that 'directory' and not name of the baseName's - //module. For instance, baseName of 'one/two/three', maps to - //'one/two/three.js', but we want the directory, 'one/two' for - //this normalization. - normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); - name = normalizedBaseParts.concat(name); - } - - trimDots(name); - name = name.join('/'); - } - - //Apply map config if available. - if (applyMap && map && (baseParts || starMap)) { - nameParts = name.split('/'); - - outerLoop: for (i = nameParts.length; i > 0; i -= 1) { - nameSegment = nameParts.slice(0, i).join('/'); - - if (baseParts) { - //Find the longest baseName segment match in the config. - //So, do joins on the biggest to smallest lengths of baseParts. - for (j = baseParts.length; j > 0; j -= 1) { - mapValue = getOwn(map, baseParts.slice(0, j).join('/')); - - //baseName segment has config, find if it has one for - //this name. - if (mapValue) { - mapValue = getOwn(mapValue, nameSegment); - if (mapValue) { - //Match, update name to the new value. - foundMap = mapValue; - foundI = i; - break outerLoop; - } - } - } - } - - //Check for a star map match, but just hold on to it, - //if there is a shorter segment match later in a matching - //config, then favor over this star map. - if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { - foundStarMap = getOwn(starMap, nameSegment); - starI = i; - } - } - - if (!foundMap && foundStarMap) { - foundMap = foundStarMap; - foundI = starI; - } - - if (foundMap) { - nameParts.splice(0, foundI, foundMap); - name = nameParts.join('/'); - } - } - - // If the name points to a package's name, use - // the package main instead. - pkgMain = getOwn(config.pkgs, name); - - return pkgMain ? pkgMain : name; - } - - function removeScript(name) { - if (isBrowser) { - each(scripts(), function (scriptNode) { - if (scriptNode.getAttribute('data-requiremodule') === name && - scriptNode.getAttribute('data-requirecontext') === context.contextName) { - scriptNode.parentNode.removeChild(scriptNode); - return true; - } - }); - } - } - - function hasPathFallback(id) { - var pathConfig = getOwn(config.paths, id); - if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { - //Pop off the first array value, since it failed, and - //retry - pathConfig.shift(); - context.require.undef(id); - - //Custom require that does not do map translation, since - //ID is "absolute", already mapped/resolved. - context.makeRequire(null, { - skipMap: true - })([id]); - - return true; - } - } - - //Turns a plugin!resource to [plugin, resource] - //with the plugin being undefined if the name - //did not have a plugin prefix. - function splitPrefix(name) { - var prefix, - index = name ? name.indexOf('!') : -1; - if (index > -1) { - prefix = name.substring(0, index); - name = name.substring(index + 1, name.length); - } - return [prefix, name]; - } - - /** - * Creates a module mapping that includes plugin prefix, module - * name, and path. If parentModuleMap is provided it will - * also normalize the name via require.normalize() - * - * @param {String} name the module name - * @param {String} [parentModuleMap] parent module map - * for the module name, used to resolve relative names. - * @param {Boolean} isNormalized: is the ID already normalized. - * This is true if this call is done for a define() module ID. - * @param {Boolean} applyMap: apply the map config to the ID. - * Should only be true if this map is for a dependency. - * - * @returns {Object} - */ - function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { - var url, pluginModule, suffix, nameParts, - prefix = null, - parentName = parentModuleMap ? parentModuleMap.name : null, - originalName = name, - isDefine = true, - normalizedName = ''; - - //If no name, then it means it is a require call, generate an - //internal name. - if (!name) { - isDefine = false; - name = '_@r' + (requireCounter += 1); - } - - nameParts = splitPrefix(name); - prefix = nameParts[0]; - name = nameParts[1]; - - if (prefix) { - prefix = normalize(prefix, parentName, applyMap); - pluginModule = getOwn(defined, prefix); - } - - //Account for relative paths if there is a base name. - if (name) { - if (prefix) { - if (pluginModule && pluginModule.normalize) { - //Plugin is loaded, use its normalize method. - normalizedName = pluginModule.normalize(name, function (name) { - return normalize(name, parentName, applyMap); - }); - } else { - // If nested plugin references, then do not try to - // normalize, as it will not normalize correctly. This - // places a restriction on resourceIds, and the longer - // term solution is not to normalize until plugins are - // loaded and all normalizations to allow for async - // loading of a loader plugin. But for now, fixes the - // common uses. Details in #1131 - normalizedName = name.indexOf('!') === -1 ? - normalize(name, parentName, applyMap) : - name; - } - } else { - //A regular module. - normalizedName = normalize(name, parentName, applyMap); - - //Normalized name may be a plugin ID due to map config - //application in normalize. The map config values must - //already be normalized, so do not need to redo that part. - nameParts = splitPrefix(normalizedName); - prefix = nameParts[0]; - normalizedName = nameParts[1]; - isNormalized = true; - - url = context.nameToUrl(normalizedName); - } - } - - //If the id is a plugin id that cannot be determined if it needs - //normalization, stamp it with a unique ID so two matching relative - //ids that may conflict can be separate. - suffix = prefix && !pluginModule && !isNormalized ? - '_unnormalized' + (unnormalizedCounter += 1) : - ''; - - return { - prefix: prefix, - name: normalizedName, - parentMap: parentModuleMap, - unnormalized: !!suffix, - url: url, - originalName: originalName, - isDefine: isDefine, - id: (prefix ? - prefix + '!' + normalizedName : - normalizedName) + suffix - }; - } - - function getModule(depMap) { - var id = depMap.id, - mod = getOwn(registry, id); - - if (!mod) { - mod = registry[id] = new context.Module(depMap); - } - - return mod; - } - - function on(depMap, name, fn) { - var id = depMap.id, - mod = getOwn(registry, id); - - if (hasProp(defined, id) && - (!mod || mod.defineEmitComplete)) { - if (name === 'defined') { - fn(defined[id]); - } - } else { - mod = getModule(depMap); - if (mod.error && name === 'error') { - fn(mod.error); - } else { - mod.on(name, fn); - } - } - } - - function onError(err, errback) { - var ids = err.requireModules, - notified = false; - - if (errback) { - errback(err); - } else { - each(ids, function (id) { - var mod = getOwn(registry, id); - if (mod) { - //Set error on module, so it skips timeout checks. - mod.error = err; - if (mod.events.error) { - notified = true; - mod.emit('error', err); - } - } - }); - - if (!notified) { - req.onError(err); - } - } - } - - /** - * Internal method to transfer globalQueue items to this context's - * defQueue. - */ - function takeGlobalQueue() { - //Push all the globalDefQueue items into the context's defQueue - if (globalDefQueue.length) { - each(globalDefQueue, function(queueItem) { - var id = queueItem[0]; - if (typeof id === 'string') { - context.defQueueMap[id] = true; - } - defQueue.push(queueItem); - }); - globalDefQueue = []; - } - } - - handlers = { - 'require': function (mod) { - if (mod.require) { - return mod.require; - } else { - return (mod.require = context.makeRequire(mod.map)); - } - }, - 'exports': function (mod) { - mod.usingExports = true; - if (mod.map.isDefine) { - if (mod.exports) { - return (defined[mod.map.id] = mod.exports); - } else { - return (mod.exports = defined[mod.map.id] = {}); - } - } - }, - 'module': function (mod) { - if (mod.module) { - return mod.module; - } else { - return (mod.module = { - id: mod.map.id, - uri: mod.map.url, - config: function () { - return getOwn(config.config, mod.map.id) || {}; - }, - exports: mod.exports || (mod.exports = {}) - }); - } - } - }; - - function cleanRegistry(id) { - //Clean up machinery used for waiting modules. - delete registry[id]; - delete enabledRegistry[id]; - } - - function breakCycle(mod, traced, processed) { - var id = mod.map.id; - - if (mod.error) { - mod.emit('error', mod.error); - } else { - traced[id] = true; - each(mod.depMaps, function (depMap, i) { - var depId = depMap.id, - dep = getOwn(registry, depId); - - //Only force things that have not completed - //being defined, so still in the registry, - //and only if it has not been matched up - //in the module already. - if (dep && !mod.depMatched[i] && !processed[depId]) { - if (getOwn(traced, depId)) { - mod.defineDep(i, defined[depId]); - mod.check(); //pass false? - } else { - breakCycle(dep, traced, processed); - } - } - }); - processed[id] = true; - } - } - - function checkLoaded() { - var err, usingPathFallback, - waitInterval = config.waitSeconds * 1000, - //It is possible to disable the wait interval by using waitSeconds of 0. - expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), - noLoads = [], - reqCalls = [], - stillLoading = false, - needCycleCheck = true; - - //Do not bother if this call was a result of a cycle break. - if (inCheckLoaded) { - return; - } - - inCheckLoaded = true; - - //Figure out the state of all the modules. - eachProp(enabledRegistry, function (mod) { - var map = mod.map, - modId = map.id; - - //Skip things that are not enabled or in error state. - if (!mod.enabled) { - return; - } - - if (!map.isDefine) { - reqCalls.push(mod); - } - - if (!mod.error) { - //If the module should be executed, and it has not - //been inited and time is up, remember it. - if (!mod.inited && expired) { - if (hasPathFallback(modId)) { - usingPathFallback = true; - stillLoading = true; - } else { - noLoads.push(modId); - removeScript(modId); - } - } else if (!mod.inited && mod.fetched && map.isDefine) { - stillLoading = true; - if (!map.prefix) { - //No reason to keep looking for unfinished - //loading. If the only stillLoading is a - //plugin resource though, keep going, - //because it may be that a plugin resource - //is waiting on a non-plugin cycle. - return (needCycleCheck = false); - } - } - } - }); - - if (expired && noLoads.length) { - //If wait time expired, throw error of unloaded modules. - err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads); - err.contextName = context.contextName; - return onError(err); - } - - //Not expired, check for a cycle. - if (needCycleCheck) { - each(reqCalls, function (mod) { - breakCycle(mod, {}, {}); - }); - } - - //If still waiting on loads, and the waiting load is something - //other than a plugin resource, or there are still outstanding - //scripts, then just try back later. - if ((!expired || usingPathFallback) && stillLoading) { - //Something is still waiting to load. Wait for it, but only - //if a timeout is not already in effect. - if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) { - checkLoadedTimeoutId = setTimeout(function () { - checkLoadedTimeoutId = 0; - checkLoaded(); - }, 50); - } - } - - inCheckLoaded = false; - } - - Module = function (map) { - this.events = getOwn(undefEvents, map.id) || {}; - this.map = map; - this.shim = getOwn(config.shim, map.id); - this.depExports = []; - this.depMaps = []; - this.depMatched = []; - this.pluginMaps = {}; - this.depCount = 0; - - /* this.exports this.factory - this.depMaps = [], - this.enabled, this.fetched - */ - }; - - Module.prototype = { - init: function (depMaps, factory, errback, options) { - options = options || {}; - - //Do not do more inits if already done. Can happen if there - //are multiple define calls for the same module. That is not - //a normal, common case, but it is also not unexpected. - if (this.inited) { - return; - } - - this.factory = factory; - - if (errback) { - //Register for errors on this module. - this.on('error', errback); - } else if (this.events.error) { - //If no errback already, but there are error listeners - //on this module, set up an errback to pass to the deps. - errback = bind(this, function (err) { - this.emit('error', err); - }); - } - - //Do a copy of the dependency array, so that - //source inputs are not modified. For example - //"shim" deps are passed in here directly, and - //doing a direct modification of the depMaps array - //would affect that config. - this.depMaps = depMaps && depMaps.slice(0); - - this.errback = errback; - - //Indicate this module has be initialized - this.inited = true; - - this.ignore = options.ignore; - - //Could have option to init this module in enabled mode, - //or could have been previously marked as enabled. However, - //the dependencies are not known until init is called. So - //if enabled previously, now trigger dependencies as enabled. - if (options.enabled || this.enabled) { - //Enable this module and dependencies. - //Will call this.check() - this.enable(); - } else { - this.check(); - } - }, - - defineDep: function (i, depExports) { - //Because of cycles, defined callback for a given - //export can be called more than once. - if (!this.depMatched[i]) { - this.depMatched[i] = true; - this.depCount -= 1; - this.depExports[i] = depExports; - } - }, - - fetch: function () { - if (this.fetched) { - return; - } - this.fetched = true; - - context.startTime = (new Date()).getTime(); - - var map = this.map; - - //If the manager is for a plugin managed resource, - //ask the plugin to load it now. - if (this.shim) { - context.makeRequire(this.map, { - enableBuildCallback: true - })(this.shim.deps || [], bind(this, function () { - return map.prefix ? this.callPlugin() : this.load(); - })); - } else { - //Regular dependency. - return map.prefix ? this.callPlugin() : this.load(); - } - }, - - load: function () { - var url = this.map.url; - - //Regular dependency. - if (!urlFetched[url]) { - urlFetched[url] = true; - context.load(this.map.id, url); - } - }, - - /** - * Checks if the module is ready to define itself, and if so, - * define it. - */ - check: function () { - if (!this.enabled || this.enabling) { - return; - } - - var err, cjsModule, - id = this.map.id, - depExports = this.depExports, - exports = this.exports, - factory = this.factory; - - if (!this.inited) { - // Only fetch if not already in the defQueue. - if (!hasProp(context.defQueueMap, id)) { - this.fetch(); - } - } else if (this.error) { - this.emit('error', this.error); - } else if (!this.defining) { - //The factory could trigger another require call - //that would result in checking this module to - //define itself again. If already in the process - //of doing that, skip this work. - this.defining = true; - - if (this.depCount < 1 && !this.defined) { - if (isFunction(factory)) { - //If there is an error listener, favor passing - //to that instead of throwing an error. However, - //only do it for define()'d modules. require - //errbacks should not be called for failures in - //their callbacks (#699). However if a global - //onError is set, use that. - if ((this.events.error && this.map.isDefine) || - req.onError !== defaultOnError) { - try { - exports = context.execCb(id, factory, depExports, exports); - } catch (e) { - err = e; - } - } else { - exports = context.execCb(id, factory, depExports, exports); - } - - // Favor return value over exports. If node/cjs in play, - // then will not have a return value anyway. Favor - // module.exports assignment over exports object. - if (this.map.isDefine && exports === undefined) { - cjsModule = this.module; - if (cjsModule) { - exports = cjsModule.exports; - } else if (this.usingExports) { - //exports already set the defined value. - exports = this.exports; - } - } - - if (err) { - err.requireMap = this.map; - err.requireModules = this.map.isDefine ? [this.map.id] : null; - err.requireType = this.map.isDefine ? 'define' : 'require'; - return onError((this.error = err)); - } - - } else { - //Just a literal value - exports = factory; - } - - this.exports = exports; - - if (this.map.isDefine && !this.ignore) { - defined[id] = exports; - - if (req.onResourceLoad) { - req.onResourceLoad(context, this.map, this.depMaps); - } - } - - //Clean up - cleanRegistry(id); - - this.defined = true; - } - - //Finished the define stage. Allow calling check again - //to allow define notifications below in the case of a - //cycle. - this.defining = false; - - if (this.defined && !this.defineEmitted) { - this.defineEmitted = true; - this.emit('defined', this.exports); - this.defineEmitComplete = true; - } - - } - }, - - callPlugin: function () { - var map = this.map, - id = map.id, - //Map already normalized the prefix. - pluginMap = makeModuleMap(map.prefix); - - //Mark this as a dependency for this plugin, so it - //can be traced for cycles. - this.depMaps.push(pluginMap); - - on(pluginMap, 'defined', bind(this, function (plugin) { - var load, normalizedMap, normalizedMod, - bundleId = getOwn(bundlesMap, this.map.id), - name = this.map.name, - parentName = this.map.parentMap ? this.map.parentMap.name : null, - localRequire = context.makeRequire(map.parentMap, { - enableBuildCallback: true - }); - - //If current map is not normalized, wait for that - //normalized name to load instead of continuing. - if (this.map.unnormalized) { - //Normalize the ID if the plugin allows it. - if (plugin.normalize) { - name = plugin.normalize(name, function (name) { - return normalize(name, parentName, true); - }) || ''; - } - - //prefix and name should already be normalized, no need - //for applying map config again either. - normalizedMap = makeModuleMap(map.prefix + '!' + name, - this.map.parentMap); - on(normalizedMap, - 'defined', bind(this, function (value) { - this.init([], function () { return value; }, null, { - enabled: true, - ignore: true - }); - })); - - normalizedMod = getOwn(registry, normalizedMap.id); - if (normalizedMod) { - //Mark this as a dependency for this plugin, so it - //can be traced for cycles. - this.depMaps.push(normalizedMap); - - if (this.events.error) { - normalizedMod.on('error', bind(this, function (err) { - this.emit('error', err); - })); - } - normalizedMod.enable(); - } - - return; - } - - //If a paths config, then just load that file instead to - //resolve the plugin, as it is built into that paths layer. - if (bundleId) { - this.map.url = context.nameToUrl(bundleId); - this.load(); - return; - } - - load = bind(this, function (value) { - this.init([], function () { return value; }, null, { - enabled: true - }); - }); - - load.error = bind(this, function (err) { - this.inited = true; - this.error = err; - err.requireModules = [id]; - - //Remove temp unnormalized modules for this module, - //since they will never be resolved otherwise now. - eachProp(registry, function (mod) { - if (mod.map.id.indexOf(id + '_unnormalized') === 0) { - cleanRegistry(mod.map.id); - } - }); - - onError(err); - }); - - //Allow plugins to load other code without having to know the - //context or how to 'complete' the load. - load.fromText = bind(this, function (text, textAlt) { - /*jslint evil: true */ - var moduleName = map.name, - moduleMap = makeModuleMap(moduleName), - hasInteractive = useInteractive; - - //As of 2.1.0, support just passing the text, to reinforce - //fromText only being called once per resource. Still - //support old style of passing moduleName but discard - //that moduleName in favor of the internal ref. - if (textAlt) { - text = textAlt; - } - - //Turn off interactive script matching for IE for any define - //calls in the text, then turn it back on at the end. - if (hasInteractive) { - useInteractive = false; - } - - //Prime the system by creating a module instance for - //it. - getModule(moduleMap); - - //Transfer any config to this other module. - if (hasProp(config.config, id)) { - config.config[moduleName] = config.config[id]; - } - - try { - req.exec(text); - } catch (e) { - return onError(makeError('fromtexteval', - 'fromText eval for ' + id + - ' failed: ' + e, - e, - [id])); - } - - if (hasInteractive) { - useInteractive = true; - } - - //Mark this as a dependency for the plugin - //resource - this.depMaps.push(moduleMap); - - //Support anonymous modules. - context.completeLoad(moduleName); - - //Bind the value of that module to the value for this - //resource ID. - localRequire([moduleName], load); - }); - - //Use parentName here since the plugin's name is not reliable, - //could be some weird string with no path that actually wants to - //reference the parentName's path. - plugin.load(map.name, localRequire, load, config); - })); - - context.enable(pluginMap, this); - this.pluginMaps[pluginMap.id] = pluginMap; - }, - - enable: function () { - enabledRegistry[this.map.id] = this; - this.enabled = true; - - //Set flag mentioning that the module is enabling, - //so that immediate calls to the defined callbacks - //for dependencies do not trigger inadvertent load - //with the depCount still being zero. - this.enabling = true; - - //Enable each dependency - each(this.depMaps, bind(this, function (depMap, i) { - var id, mod, handler; - - if (typeof depMap === 'string') { - //Dependency needs to be converted to a depMap - //and wired up to this module. - depMap = makeModuleMap(depMap, - (this.map.isDefine ? this.map : this.map.parentMap), - false, - !this.skipMap); - this.depMaps[i] = depMap; - - handler = getOwn(handlers, depMap.id); - - if (handler) { - this.depExports[i] = handler(this); - return; - } - - this.depCount += 1; - - on(depMap, 'defined', bind(this, function (depExports) { - if (this.undefed) { - return; - } - this.defineDep(i, depExports); - this.check(); - })); - - if (this.errback) { - on(depMap, 'error', bind(this, this.errback)); - } else if (this.events.error) { - // No direct errback on this module, but something - // else is listening for errors, so be sure to - // propagate the error correctly. - on(depMap, 'error', bind(this, function(err) { - this.emit('error', err); - })); - } - } - - id = depMap.id; - mod = registry[id]; - - //Skip special modules like 'require', 'exports', 'module' - //Also, don't call enable if it is already enabled, - //important in circular dependency cases. - if (!hasProp(handlers, id) && mod && !mod.enabled) { - context.enable(depMap, this); - } - })); - - //Enable each plugin that is used in - //a dependency - eachProp(this.pluginMaps, bind(this, function (pluginMap) { - var mod = getOwn(registry, pluginMap.id); - if (mod && !mod.enabled) { - context.enable(pluginMap, this); - } - })); - - this.enabling = false; - - this.check(); - }, - - on: function (name, cb) { - var cbs = this.events[name]; - if (!cbs) { - cbs = this.events[name] = []; - } - cbs.push(cb); - }, - - emit: function (name, evt) { - each(this.events[name], function (cb) { - cb(evt); - }); - if (name === 'error') { - //Now that the error handler was triggered, remove - //the listeners, since this broken Module instance - //can stay around for a while in the registry. - delete this.events[name]; - } - } - }; - - function callGetModule(args) { - //Skip modules already defined. - if (!hasProp(defined, args[0])) { - getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); - } - } - - function removeListener(node, func, name, ieName) { - //Favor detachEvent because of IE9 - //issue, see attachEvent/addEventListener comment elsewhere - //in this file. - if (node.detachEvent && !isOpera) { - //Probably IE. If not it will throw an error, which will be - //useful to know. - if (ieName) { - node.detachEvent(ieName, func); - } - } else { - node.removeEventListener(name, func, false); - } - } - - /** - * Given an event from a script node, get the requirejs info from it, - * and then removes the event listeners on the node. - * @param {Event} evt - * @returns {Object} - */ - function getScriptData(evt) { - //Using currentTarget instead of target for Firefox 2.0's sake. Not - //all old browsers will be supported, but this one was easy enough - //to support and still makes sense. - var node = evt.currentTarget || evt.srcElement; - - //Remove the listeners once here. - removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange'); - removeListener(node, context.onScriptError, 'error'); - - return { - node: node, - id: node && node.getAttribute('data-requiremodule') - }; - } - - function intakeDefines() { - var args; - - //Any defined modules in the global queue, intake them now. - takeGlobalQueue(); - - //Make sure any remaining defQueue items get properly processed. - while (defQueue.length) { - args = defQueue.shift(); - if (args[0] === null) { - return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + - args[args.length - 1])); - } else { - //args are id, deps, factory. Should be normalized by the - //define() function. - callGetModule(args); - } - } - context.defQueueMap = {}; - } - - context = { - config: config, - contextName: contextName, - registry: registry, - defined: defined, - urlFetched: urlFetched, - defQueue: defQueue, - defQueueMap: {}, - Module: Module, - makeModuleMap: makeModuleMap, - nextTick: req.nextTick, - onError: onError, - - /** - * Set a configuration for the context. - * @param {Object} cfg config object to integrate. - */ - configure: function (cfg) { - //Make sure the baseUrl ends in a slash. - if (cfg.baseUrl) { - if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { - cfg.baseUrl += '/'; - } - } - - //Save off the paths since they require special processing, - //they are additive. - var shim = config.shim, - objs = { - paths: true, - bundles: true, - config: true, - map: true - }; - - eachProp(cfg, function (value, prop) { - if (objs[prop]) { - if (!config[prop]) { - config[prop] = {}; - } - mixin(config[prop], value, true, true); - } else { - config[prop] = value; - } - }); - - //Reverse map the bundles - if (cfg.bundles) { - eachProp(cfg.bundles, function (value, prop) { - each(value, function (v) { - if (v !== prop) { - bundlesMap[v] = prop; - } - }); - }); - } - - //Merge shim - if (cfg.shim) { - eachProp(cfg.shim, function (value, id) { - //Normalize the structure - if (isArray(value)) { - value = { - deps: value - }; - } - if ((value.exports || value.init) && !value.exportsFn) { - value.exportsFn = context.makeShimExports(value); - } - shim[id] = value; - }); - config.shim = shim; - } - - //Adjust packages if necessary. - if (cfg.packages) { - each(cfg.packages, function (pkgObj) { - var location, name; - - pkgObj = typeof pkgObj === 'string' ? {name: pkgObj} : pkgObj; - - name = pkgObj.name; - location = pkgObj.location; - if (location) { - config.paths[name] = pkgObj.location; - } - - //Save pointer to main module ID for pkg name. - //Remove leading dot in main, so main paths are normalized, - //and remove any trailing .js, since different package - //envs have different conventions: some use a module name, - //some use a file name. - config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main') - .replace(currDirRegExp, '') - .replace(jsSuffixRegExp, ''); - }); - } - - //If there are any "waiting to execute" modules in the registry, - //update the maps for them, since their info, like URLs to load, - //may have changed. - eachProp(registry, function (mod, id) { - //If module already has init called, since it is too - //late to modify them, and ignore unnormalized ones - //since they are transient. - if (!mod.inited && !mod.map.unnormalized) { - mod.map = makeModuleMap(id, null, true); - } - }); - - //If a deps array or a config callback is specified, then call - //require with those args. This is useful when require is defined as a - //config object before require.js is loaded. - if (cfg.deps || cfg.callback) { - context.require(cfg.deps || [], cfg.callback); - } - }, - - makeShimExports: function (value) { - function fn() { - var ret; - if (value.init) { - ret = value.init.apply(global, arguments); - } - return ret || (value.exports && getGlobal(value.exports)); - } - return fn; - }, - - makeRequire: function (relMap, options) { - options = options || {}; - - function localRequire(deps, callback, errback) { - var id, map, requireMod; - - if (options.enableBuildCallback && callback && isFunction(callback)) { - callback.__requireJsBuild = true; - } - - if (typeof deps === 'string') { - if (isFunction(callback)) { - //Invalid call - return onError(makeError('requireargs', 'Invalid require call'), errback); - } - - //If require|exports|module are requested, get the - //value for them from the special handlers. Caveat: - //this only works while module is being defined. - if (relMap && hasProp(handlers, deps)) { - return handlers[deps](registry[relMap.id]); - } - - //Synchronous access to one module. If require.get is - //available (as in the Node adapter), prefer that. - if (req.get) { - return req.get(context, deps, relMap, localRequire); - } - - //Normalize module name, if it contains . or .. - map = makeModuleMap(deps, relMap, false, true); - id = map.id; - - if (!hasProp(defined, id)) { - return onError(makeError('notloaded', 'Module name "' + - id + - '" has not been loaded yet for context: ' + - contextName + - (relMap ? '' : '. Use require([])'))); - } - return defined[id]; - } - - //Grab defines waiting in the global queue. - intakeDefines(); - - //Mark all the dependencies as needing to be loaded. - context.nextTick(function () { - //Some defines could have been added since the - //require call, collect them. - intakeDefines(); - - requireMod = getModule(makeModuleMap(null, relMap)); - - //Store if map config should be applied to this require - //call for dependencies. - requireMod.skipMap = options.skipMap; - - requireMod.init(deps, callback, errback, { - enabled: true - }); - - checkLoaded(); - }); - - return localRequire; - } - - mixin(localRequire, { - isBrowser: isBrowser, - - /** - * Converts a module name + .extension into an URL path. - * *Requires* the use of a module name. It does not support using - * plain URLs like nameToUrl. - */ - toUrl: function (moduleNamePlusExt) { - var ext, - index = moduleNamePlusExt.lastIndexOf('.'), - segment = moduleNamePlusExt.split('/')[0], - isRelative = segment === '.' || segment === '..'; - - //Have a file extension alias, and it is not the - //dots from a relative path. - if (index !== -1 && (!isRelative || index > 1)) { - ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); - moduleNamePlusExt = moduleNamePlusExt.substring(0, index); - } - - return context.nameToUrl(normalize(moduleNamePlusExt, - relMap && relMap.id, true), ext, true); - }, - - defined: function (id) { - return hasProp(defined, makeModuleMap(id, relMap, false, true).id); - }, - - specified: function (id) { - id = makeModuleMap(id, relMap, false, true).id; - return hasProp(defined, id) || hasProp(registry, id); - } - }); - - //Only allow undef on top level require calls - if (!relMap) { - localRequire.undef = function (id) { - //Bind any waiting define() calls to this context, - //fix for #408 - takeGlobalQueue(); - - var map = makeModuleMap(id, relMap, true), - mod = getOwn(registry, id); - - mod.undefed = true; - removeScript(id); - - delete defined[id]; - delete urlFetched[map.url]; - delete undefEvents[id]; - - //Clean queued defines too. Go backwards - //in array so that the splices do not - //mess up the iteration. - eachReverse(defQueue, function(args, i) { - if (args[0] === id) { - defQueue.splice(i, 1); - } - }); - delete context.defQueueMap[id]; - - if (mod) { - //Hold on to listeners in case the - //module will be attempted to be reloaded - //using a different config. - if (mod.events.defined) { - undefEvents[id] = mod.events; - } - - cleanRegistry(id); - } - }; - } - - return localRequire; - }, - - /** - * Called to enable a module if it is still in the registry - * awaiting enablement. A second arg, parent, the parent module, - * is passed in for context, when this method is overridden by - * the optimizer. Not shown here to keep code compact. - */ - enable: function (depMap) { - var mod = getOwn(registry, depMap.id); - if (mod) { - getModule(depMap).enable(); - } - }, - - /** - * Internal method used by environment adapters to complete a load event. - * A load event could be a script load or just a load pass from a synchronous - * load call. - * @param {String} moduleName the name of the module to potentially complete. - */ - completeLoad: function (moduleName) { - var found, args, mod, - shim = getOwn(config.shim, moduleName) || {}, - shExports = shim.exports; - - takeGlobalQueue(); - - while (defQueue.length) { - args = defQueue.shift(); - if (args[0] === null) { - args[0] = moduleName; - //If already found an anonymous module and bound it - //to this name, then this is some other anon module - //waiting for its completeLoad to fire. - if (found) { - break; - } - found = true; - } else if (args[0] === moduleName) { - //Found matching define call for this script! - found = true; - } - - callGetModule(args); - } - context.defQueueMap = {}; - - //Do this after the cycle of callGetModule in case the result - //of those calls/init calls changes the registry. - mod = getOwn(registry, moduleName); - - if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { - if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { - if (hasPathFallback(moduleName)) { - return; - } else { - return onError(makeError('nodefine', - 'No define call for ' + moduleName, - null, - [moduleName])); - } - } else { - //A script that does not call define(), so just simulate - //the call for it. - callGetModule([moduleName, (shim.deps || []), shim.exportsFn]); - } - } - - checkLoaded(); - }, - - /** - * Converts a module name to a file path. Supports cases where - * moduleName may actually be just an URL. - * Note that it **does not** call normalize on the moduleName, - * it is assumed to have already been normalized. This is an - * internal API, not a public one. Use toUrl for the public API. - */ - nameToUrl: function (moduleName, ext, skipExt) { - var paths, syms, i, parentModule, url, - parentPath, bundleId, - pkgMain = getOwn(config.pkgs, moduleName); - - if (pkgMain) { - moduleName = pkgMain; - } - - bundleId = getOwn(bundlesMap, moduleName); - - if (bundleId) { - return context.nameToUrl(bundleId, ext, skipExt); - } - - //If a colon is in the URL, it indicates a protocol is used and it is just - //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) - //or ends with .js, then assume the user meant to use an url and not a module id. - //The slash is important for protocol-less URLs as well as full paths. - if (req.jsExtRegExp.test(moduleName)) { - //Just a plain path, not module name lookup, so just return it. - //Add extension if it is included. This is a bit wonky, only non-.js things pass - //an extension, this method probably needs to be reworked. - url = moduleName + (ext || ''); - } else { - //A module that needs to be converted to a path. - paths = config.paths; - - syms = moduleName.split('/'); - //For each module name segment, see if there is a path - //registered for it. Start with most specific name - //and work up from it. - for (i = syms.length; i > 0; i -= 1) { - parentModule = syms.slice(0, i).join('/'); - - parentPath = getOwn(paths, parentModule); - if (parentPath) { - //If an array, it means there are a few choices, - //Choose the one that is desired - if (isArray(parentPath)) { - parentPath = parentPath[0]; - } - syms.splice(0, i, parentPath); - break; - } - } - - //Join the path parts together, then figure out if baseUrl is needed. - url = syms.join('/'); - url += (ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js')); - url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; - } - - return config.urlArgs ? url + - ((url.indexOf('?') === -1 ? '?' : '&') + - config.urlArgs) : url; - }, - - //Delegates to req.load. Broken out as a separate function to - //allow overriding in the optimizer. - load: function (id, url) { - req.load(context, id, url); - }, - - /** - * Executes a module callback function. Broken out as a separate function - * solely to allow the build system to sequence the files in the built - * layer in the right sequence. - * - * @private - */ - execCb: function (name, callback, args, exports) { - return callback.apply(exports, args); - }, - - /** - * callback for script loads, used to check status of loading. - * - * @param {Event} evt the event from the browser for the script - * that was loaded. - */ - onScriptLoad: function (evt) { - //Using currentTarget instead of target for Firefox 2.0's sake. Not - //all old browsers will be supported, but this one was easy enough - //to support and still makes sense. - if (evt.type === 'load' || - (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { - //Reset interactive script so a script node is not held onto for - //to long. - interactiveScript = null; - - //Pull out the name of the module and the context. - var data = getScriptData(evt); - context.completeLoad(data.id); - } - }, - - /** - * Callback for script errors. - */ - onScriptError: function (evt) { - var data = getScriptData(evt); - if (!hasPathFallback(data.id)) { - return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id])); - } - } - }; - - context.require = context.makeRequire(); - return context; - } - - /** - * Main entry point. - * - * If the only argument to require is a string, then the module that - * is represented by that string is fetched for the appropriate context. - * - * If the first argument is an array, then it will be treated as an array - * of dependency string names to fetch. An optional function callback can - * be specified to execute when all of those dependencies are available. - * - * Make a local req variable to help Caja compliance (it assumes things - * on a require that are not standardized), and to give a short - * name for minification/local scope use. - */ - req = requirejs = function (deps, callback, errback, optional) { - - //Find the right context, use default - var context, config, - contextName = defContextName; - - // Determine if have config object in the call. - if (!isArray(deps) && typeof deps !== 'string') { - // deps is a config object - config = deps; - if (isArray(callback)) { - // Adjust args if there are dependencies - deps = callback; - callback = errback; - errback = optional; - } else { - deps = []; - } - } - - if (config && config.context) { - contextName = config.context; - } - - context = getOwn(contexts, contextName); - if (!context) { - context = contexts[contextName] = req.s.newContext(contextName); - } - - if (config) { - context.configure(config); - } - - return context.require(deps, callback, errback); - }; - - /** - * Support require.config() to make it easier to cooperate with other - * AMD loaders on globally agreed names. - */ - req.config = function (config) { - return req(config); - }; - - /** - * Execute something after the current tick - * of the event loop. Override for other envs - * that have a better solution than setTimeout. - * @param {Function} fn function to execute later. - */ - req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) { - setTimeout(fn, 4); - } : function (fn) { fn(); }; - - /** - * Export require as a global, but only if it does not already exist. - */ - if (!require) { - require = req; - } - - req.version = version; - - //Used to filter out dependencies that are already paths. - req.jsExtRegExp = /^\/|:|\?|\.js$/; - req.isBrowser = isBrowser; - s = req.s = { - contexts: contexts, - newContext: newContext - }; - - //Create default context. - req({}); - - //Exports some context-sensitive methods on global require. - each([ - 'toUrl', - 'undef', - 'defined', - 'specified' - ], function (prop) { - //Reference from contexts instead of early binding to default context, - //so that during builds, the latest instance of the default context - //with its config gets used. - req[prop] = function () { - var ctx = contexts[defContextName]; - return ctx.require[prop].apply(ctx, arguments); - }; - }); - - if (isBrowser) { - head = s.head = document.getElementsByTagName('head')[0]; - //If BASE tag is in play, using appendChild is a problem for IE6. - //When that browser dies, this can be removed. Details in this jQuery bug: - //http://dev.jquery.com/ticket/2709 - baseElement = document.getElementsByTagName('base')[0]; - if (baseElement) { - head = s.head = baseElement.parentNode; - } - } - - /** - * Any errors that require explicitly generates will be passed to this - * function. Intercept/override it if you want custom error handling. - * @param {Error} err the error object. - */ - req.onError = defaultOnError; - - /** - * Creates the node for the load command. Only used in browser envs. - */ - req.createNode = function (config, moduleName, url) { - var node = config.xhtml ? - document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : - document.createElement('script'); - node.type = config.scriptType || 'text/javascript'; - node.charset = 'utf-8'; - node.async = true; - return node; - }; - - /** - * Does the request to load a module for the browser case. - * Make this a separate function to allow other environments - * to override it. - * - * @param {Object} context the require context to find state. - * @param {String} moduleName the name of the module. - * @param {Object} url the URL to the module. - */ - req.load = function (context, moduleName, url) { - var config = (context && context.config) || {}, - node; - if (isBrowser) { - //In the browser so use a script tag - node = req.createNode(config, moduleName, url); - if (config.onNodeCreated) { - config.onNodeCreated(node, config, moduleName, url); - } - - node.setAttribute('data-requirecontext', context.contextName); - node.setAttribute('data-requiremodule', moduleName); - - //Set up load listener. Test attachEvent first because IE9 has - //a subtle issue in its addEventListener and script onload firings - //that do not match the behavior of all other browsers with - //addEventListener support, which fire the onload event for a - //script right after the script execution. See: - //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution - //UNFORTUNATELY Opera implements attachEvent but does not follow the script - //script execution mode. - if (node.attachEvent && - //Check if node.attachEvent is artificially added by custom script or - //natively supported by browser - //read https://github.com/jrburke/requirejs/issues/187 - //if we can NOT find [native code] then it must NOT natively supported. - //in IE8, node.attachEvent does not have toString() - //Note the test for "[native code" with no closing brace, see: - //https://github.com/jrburke/requirejs/issues/273 - !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && - !isOpera) { - //Probably IE. IE (at least 6-8) do not fire - //script onload right after executing the script, so - //we cannot tie the anonymous define call to a name. - //However, IE reports the script as being in 'interactive' - //readyState at the time of the define call. - useInteractive = true; - - node.attachEvent('onreadystatechange', context.onScriptLoad); - //It would be great to add an error handler here to catch - //404s in IE9+. However, onreadystatechange will fire before - //the error handler, so that does not help. If addEventListener - //is used, then IE will fire error before load, but we cannot - //use that pathway given the connect.microsoft.com issue - //mentioned above about not doing the 'script execute, - //then fire the script load event listener before execute - //next script' that other browsers do. - //Best hope: IE10 fixes the issues, - //and then destroys all installs of IE 6-9. - //node.attachEvent('onerror', context.onScriptError); - } else { - node.addEventListener('load', context.onScriptLoad, false); - node.addEventListener('error', context.onScriptError, false); - } - node.src = url; - - //For some cache cases in IE 6-8, the script executes before the end - //of the appendChild execution, so to tie an anonymous define - //call to the module name (which is stored on the node), hold on - //to a reference to this node, but clear after the DOM insertion. - currentlyAddingScript = node; - if (baseElement) { - head.insertBefore(node, baseElement); - } else { - head.appendChild(node); - } - currentlyAddingScript = null; - - return node; - } else if (isWebWorker) { - try { - //In a web worker, use importScripts. This is not a very - //efficient use of importScripts, importScripts will block until - //its script is downloaded and evaluated. However, if web workers - //are in play, the expectation that a build has been done so that - //only one script needs to be loaded anyway. This may need to be - //reevaluated if other use cases become common. - importScripts(url); - - //Account for anonymous modules - context.completeLoad(moduleName); - } catch (e) { - context.onError(makeError('importscripts', - 'importScripts failed for ' + - moduleName + ' at ' + url, - e, - [moduleName])); - } - } - }; - - function getInteractiveScript() { - if (interactiveScript && interactiveScript.readyState === 'interactive') { - return interactiveScript; - } - - eachReverse(scripts(), function (script) { - if (script.readyState === 'interactive') { - return (interactiveScript = script); - } - }); - return interactiveScript; - } - - //Look for a data-main script attribute, which could also adjust the baseUrl. - if (isBrowser && !cfg.skipDataMain) { - //Figure out baseUrl. Get it from the script tag with require.js in it. - eachReverse(scripts(), function (script) { - //Set the 'head' where we can append children by - //using the script's parent. - if (!head) { - head = script.parentNode; - } - - //Look for a data-main attribute to set main script for the page - //to load. If it is there, the path to data main becomes the - //baseUrl, if it is not already set. - dataMain = script.getAttribute('data-main'); - if (dataMain) { - //Preserve dataMain in case it is a path (i.e. contains '?') - mainScript = dataMain; - - //Set final baseUrl if there is not already an explicit one. - if (!cfg.baseUrl) { - //Pull off the directory of data-main for use as the - //baseUrl. - src = mainScript.split('/'); - mainScript = src.pop(); - subPath = src.length ? src.join('/') + '/' : './'; - - cfg.baseUrl = subPath; - } - - //Strip off any trailing .js since mainScript is now - //like a module name. - mainScript = mainScript.replace(jsSuffixRegExp, ''); - - //If mainScript is still a path, fall back to dataMain - if (req.jsExtRegExp.test(mainScript)) { - mainScript = dataMain; - } - - //Put the data-main script in the files to load. - cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript]; - - return true; - } - }); - } - - /** - * The function that handles definitions of modules. Differs from - * require() in that a string for the module should be the first argument, - * and the function to execute after dependencies are loaded should - * return a value to define the module corresponding to the first argument's - * name. - */ - define = function (name, deps, callback) { - var node, context; - - //Allow for anonymous modules - if (typeof name !== 'string') { - //Adjust args appropriately - callback = deps; - deps = name; - name = null; - } - - //This module may not have dependencies - if (!isArray(deps)) { - callback = deps; - deps = null; - } - - //If no name, and callback is a function, then figure out if it a - //CommonJS thing with dependencies. - if (!deps && isFunction(callback)) { - deps = []; - //Remove comments from the callback string, - //look for require calls, and pull them into the dependencies, - //but only if there are function args. - if (callback.length) { - callback - .toString() - .replace(commentRegExp, '') - .replace(cjsRequireRegExp, function (match, dep) { - deps.push(dep); - }); - - //May be a CommonJS thing even without require calls, but still - //could use exports, and module. Avoid doing exports and module - //work though if it just needs require. - //REQUIRES the function to expect the CommonJS variables in the - //order listed below. - deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps); - } - } - - //If in IE 6-8 and hit an anonymous define() call, do the interactive - //work. - if (useInteractive) { - node = currentlyAddingScript || getInteractiveScript(); - if (node) { - if (!name) { - name = node.getAttribute('data-requiremodule'); - } - context = contexts[node.getAttribute('data-requirecontext')]; - } - } - - //Always save off evaluating the def call until the script onload handler. - //This allows multiple modules to be in a file without prematurely - //tracing dependencies, and allows for anonymous module support, - //where the module name is not known until the script onload event - //occurs. If no context, use the global queue, and get it processed - //in the onscript load callback. - if (context) { - context.defQueue.push([name, deps, callback]); - context.defQueueMap[name] = true; - } else { - globalDefQueue.push([name, deps, callback]); - } - }; - - define.amd = { - jQuery: true - }; - - /** - * Executes the text. Normally just uses eval, but can be modified - * to use a better, environment-specific call. Only used for transpiling - * loader plugins, not for plain JS modules. - * @param {String} text the text to execute/evaluate. - */ - req.exec = function (text) { - /*jslint evil: true */ - return eval(text); - }; - - //Set up with config info. - req(cfg); -}(this)); diff --git a/ThirdParty/requirejs-2.1.20/require.min.js b/ThirdParty/requirejs-2.1.20/require.min.js deleted file mode 100644 index 693164afc8d..00000000000 --- a/ThirdParty/requirejs-2.1.20/require.min.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - RequireJS 2.1.20 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved. - Available via the MIT or new BSD license. - see: http://github.com/jrburke/requirejs for details -*/ -var requirejs,require,define; -(function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||e.onError!==ca)try{f=h.execCb(c,l,b,f)}catch(d){a=d}else f=h.execCb(c,l,b,f);this.map.isDefine&& -void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(q[c]=f,e.onResourceLoad))e.onResourceLoad(h,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete= -!0)}}else t(h.defQueueMap,c)||this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=i(a.prefix);this.depMaps.push(d);s(d,"defined",u(this,function(f){var l,d;d=n(aa,this.map.id);var g=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,p=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(g=f.normalize(g,function(a){return c(a,P,!0)})||""),f=i(a.prefix+"!"+g,this.map.parentMap),s(f,"defined",u(this,function(a){this.init([],function(){return a}, -null,{enabled:!0,ignore:!0})})),d=n(m,f.id)){this.depMaps.push(f);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=h.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];A(m,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,g=i(d),P=M;c&&(f=c);P&& -(M=!1);r(g);t(k.config,b)&&(k.config[d]=k.config[b]);try{e.exec(f)}catch(m){return w(B("fromtexteval","fromText eval for "+b+" failed: "+m,m,[b]))}P&&(M=!0);this.depMaps.push(g);h.completeLoad(d);p([d],l)}),f.load(a.name,p,l,k))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=i(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c= -n(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?s(a,"error",u(this,this.errback)):this.events.error&&s(a,"error",u(this,function(a){this.emit("error",a)}))}c=a.id;f=m[c];!t(L,c)&&(f&&!f.enabled)&&h.enable(a,this)}));A(this.pluginMaps,u(this,function(a){var b=n(m,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]= -[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:k,contextName:b,registry:m,defined:q,urlFetched:S,defQueue:C,defQueueMap:{},Module:Z,makeModuleMap:i,nextTick:e.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.shim,c={paths:!0,bundles:!0,config:!0,map:!0};A(a,function(a,b){c[b]?(k[b]||(k[b]={}),U(k[b],a,!0,!0)):k[b]=a});a.bundles&&A(a.bundles,function(a,b){v(a, -function(a){a!==b&&(aa[a]=b)})});a.shim&&(A(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=h.makeShimExports(a);b[c]=a}),k.shim=b);a.packages&&v(a.packages,function(a){var b,a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(k.paths[b]=a.location);k.pkgs[b]=a.name+"/"+(a.main||"main").replace(ha,"").replace(Q,"")});A(m,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=i(b,null,!0))});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b; -a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,j){function g(c,d,p){var k,n;j.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=!0);if("string"===typeof c){if(G(d))return w(B("requireargs","Invalid require call"),p);if(a&&t(L,c))return L[c](m[a.id]);if(e.get)return e.get(h,c,a,g);k=i(c,a,!1,!0);k=k.id;return!t(q,k)?w(B("notloaded",'Module name "'+k+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):q[k]}J();h.nextTick(function(){J(); -n=r(i(null,a));n.skipMap=j.skipMap;n.init(c,d,p,{enabled:!0});D()});return g}j=j||{};U(g,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),j=b.split("/")[0];if(-1!==e&&(!("."===j||".."===j)||1g.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1));g.src=d;J=g;D?y.insertBefore(g,D):y.appendChild(g);J=null;return g}if(ea)try{importScripts(d),b.completeLoad(c)}catch(i){b.onError(B("importscripts", -"importScripts failed for "+c+" at "+d,i,[c]))}};z&&!s.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return r=I,s.baseUrl||(E=r.split("/"),r=E.pop(),O=E.length?E.join("/")+"/":"./",s.baseUrl=O),r=r.replace(Q,""),e.jsExtRegExp.test(r)&&(r=I),s.deps=s.deps?s.deps.concat(r):[r],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ja,"").replace(ka, -function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b||(b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}g?(g.defQueue.push([b,c,d]),g.defQueueMap[b]=!0):R.push([b,c,d])};define.amd={jQuery:!0};e.exec=function(b){return eval(b)};e(s)}})(this); diff --git a/Tools/eslint-config-cesium/browser.js b/Tools/eslint-config-cesium/browser.js index cf55ad4ea15..d418dd36d90 100644 --- a/Tools/eslint-config-cesium/browser.js +++ b/Tools/eslint-config-cesium/browser.js @@ -3,11 +3,14 @@ module.exports = { extends: './index.js', env: { - amd: true, browser: true }, + parserOptions: { + ecmaVersion: "2015", + sourceType: "module" + }, rules: { 'no-implicit-globals': 'error', - "no-prototype-builtins": "off" + 'no-prototype-builtins': 'off' } }; diff --git a/Tools/jsdoc/conf.json b/Tools/jsdoc/conf.json index e8fbccf885c..64d6a409e94 100644 --- a/Tools/jsdoc/conf.json +++ b/Tools/jsdoc/conf.json @@ -4,7 +4,7 @@ }, "source": { "include": ["Source"], - "exclude": ["Source/ThirdParty", "Source/Workers/cesiumWorkerBootstrapper.js"], + "exclude": ["Source/ThirdParty", "Source/Workers"], "includePattern": ".+\\.js(doc)?$", "excludePattern": "(^|\\/|\\\\)_" }, diff --git a/Tools/rollup-plugin-strip-pragma/README.md b/Tools/rollup-plugin-strip-pragma/README.md new file mode 100644 index 00000000000..bd50428ae5e --- /dev/null +++ b/Tools/rollup-plugin-strip-pragma/README.md @@ -0,0 +1,33 @@ +A [Rollup](https://rollupjs.org) plugin to strip [requirejs](https://requirejs.org/) build pragmas from your code. + +## Installation + +`npm install rollup-plugin-strip-pragma --save-dev` + +## Usage + +Given source code with a requirejs build pragma, such as: + +```js +Cartesian3.fromSpherical = function(spherical, result) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('spherical', spherical); + //>>includeEnd('debug'); + ... +} +``` + +The following rollup usage will produce code with pragmas stripped. + +```js +const rollup = require('rollup'); +const rollupStripPragma = require('./rollup-plugin-strip-pragma'); +const bundle = await rollup.rollup({ + input: 'source.js', + plugins: [ + rollupStripPragma({ + pragmas: ['debug'] + }) + ] +}); +``` diff --git a/Tools/rollup-plugin-strip-pragma/index.js b/Tools/rollup-plugin-strip-pragma/index.js new file mode 100644 index 00000000000..eea52faf13c --- /dev/null +++ b/Tools/rollup-plugin-strip-pragma/index.js @@ -0,0 +1,71 @@ +'use strict'; + +const MagicString = require('magic-string'); +const { createFilter } = require('rollup-pluginutils'); + +function escapeCharacters(token) { + return token.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); +} + +function constructRegex(pragma) { + const prefix = 'include'; + pragma = escapeCharacters(pragma); + + const s = '[\\t ]*\\/\\/>>\\s?' + + prefix + + 'Start\\s?\\(\\s?(["\'])' + + pragma + + '\\1\\s?,\\s?pragmas\\.' + + pragma + + '\\s?\\)\\s?;?' + + + // multiline code block + '[\\s\\S]*?' + + + // end comment + '[\\t ]*\\/\\/>>\\s?' + + prefix + + 'End\\s?\\(\\s?(["\'])' + + pragma + + '\\2\\s?\\)\\s?;?\\s?[\\t]*\\n?'; + + return new RegExp(s, 'gm'); +} + +function stripPragma(options = {}) { + const filter = createFilter(options.include, options.exclude); + const patterns = options.pragmas.map(pragma => constructRegex(pragma)); + + return { + name: 'replace', + + // code: The contents of the file + // id: the file path + transform(code, id) { + // Filters out includes and excluded files + if (!filter(id)) { + return null; + } + + const magicString = new MagicString(code); + + let match; + let start; + let end; + + for (let i = 0; i < patterns.length; i++) { + const pattern = patterns[i]; + while ((match = pattern.exec(code))) { + start = match.index; + end = start + match[0].length; + magicString.overwrite(start, end, ''); + } + } + + const result = { code: magicString.toString() }; + return result; + } + }; +} + +module.exports = stripPragma; diff --git a/Tools/rollup-plugin-strip-pragma/package.json b/Tools/rollup-plugin-strip-pragma/package.json new file mode 100644 index 00000000000..e9c125eb9ed --- /dev/null +++ b/Tools/rollup-plugin-strip-pragma/package.json @@ -0,0 +1,25 @@ +{ + "name": "rollup-plugin-strip-pragma", + "version": "1.0.0", + "description": "A Rollup plugin to strip requirejs build pragmas from your code.", + "homepage": "http://cesiumjs.org", + "license": "Apache-2.0", + "author": { + "name": "Cesium GS, Inc.", + "url": "https://cesium.com" + }, + "contributors": [ + { + "name": "Cesium community", + "url": "https://github.com/AnalyticalGraphicsInc/cesium/blob/master/CONTRIBUTORS.md" + } + ], + "keywords": [ + "rollup", + "cesium" + ], + "dependencies": { + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.2" + } +} diff --git a/gulpfile.js b/gulpfile.js index 45229639a9a..cafa90b76c1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -12,6 +12,7 @@ var request = require('request'); var globby = require('globby'); var gulpTap = require('gulp-tap'); +var gulpUglify = require('gulp-uglify'); var open = require('open'); var rimraf = require('rimraf'); var glslStripComments = require('glsl-strip-comments'); @@ -25,11 +26,16 @@ var gulpRename = require('gulp-rename'); var gulpReplace = require('gulp-replace'); var gulpJsonTransform = require('gulp-json-transform'); var Promise = require('bluebird'); -var requirejs = require('requirejs'); var Karma = require('karma'); var yargs = require('yargs'); var AWS = require('aws-sdk'); var mime = require('mime'); +var rollup = require('rollup'); +var rollupPluginBanner = require('rollup-plugin-banner'); +var rollupPluginStripPragma = require('rollup-plugin-strip-pragma'); +var rollupPluginExternalGlobals = require('rollup-plugin-external-globals'); +var rollupPluginUglify = require('rollup-plugin-uglify'); +var cleanCSS = require('gulp-clean-css'); var packageJson = require('./package.json'); var version = packageJson.version; @@ -56,50 +62,34 @@ if (!concurrency) { var sourceFiles = ['Source/**/*.js', '!Source/*.js', '!Source/Workers/**', + '!Source/WorkersES6/**', + 'Source/WorkersES6/createTaskProcessorWorker.js', '!Source/ThirdParty/Workers/**', '!Source/ThirdParty/google-earth-dbroot-parser.js', '!Source/ThirdParty/pako_inflate.js', - '!Source/ThirdParty/crunch.js', - 'Source/Workers/createTaskProcessorWorker.js']; + '!Source/ThirdParty/crunch.js']; -var buildFiles = ['Specs/**/*.js', +var buildFiles = ['Source/**/*.js', '!Specs/SpecList.js', 'Source/Shaders/**/*.glsl']; var filesToClean = ['Source/Cesium.js', - 'Build', 'Source/Shaders/**/*.js', + 'Source/Workers/**', + '!Source/Workers/cesiumWorkerBootstrapper.js', + '!Source/Workers/transferTypedArrayTest.js', 'Source/ThirdParty/Shaders/*.js', 'Specs/SpecList.js', 'Apps/Sandcastle/jsHintOptions.js', 'Apps/Sandcastle/gallery/gallery-index.js', 'Apps/Sandcastle/templates/bucket.css', - 'Cesium-*.zip']; - -var filesToSortRequires = ['Source/**/*.js', - '!Source/Shaders/**', - '!Source/ThirdParty/**', - '!Source/Workers/cesiumWorkerBootstrapper.js', - '!Source/copyrightHeader.js', - '!Source/Workers/transferTypedArrayTest.js', - 'Apps/**/*.js', - '!Apps/Sandcastle/ThirdParty/**', - '!Apps/Sandcastle/jsHintOptions.js', - 'Specs/**/*.js', - '!Specs/spec-main.js', - '!Specs/SpecRunner.js', - '!Specs/SpecList.js', - '!Specs/karma.conf.js', - '!Apps/Sandcastle/Sandcastle-client.js', - '!Apps/Sandcastle/Sandcastle-header.js', - '!Apps/Sandcastle/Sandcastle-warn.js', - '!Apps/Sandcastle/gallery/gallery-index.js']; + 'Cesium-*.zip', + 'cesium-*.tgz']; var filesToConvertES6 = ['Source/**/*.js', 'Specs/**/*.js', '!Source/ThirdParty/**', '!Source/Cesium.js', - '!Source/main.js', '!Source/copyrightHeader.js', '!Source/Shaders/**', '!Source/Workers/cesiumWorkerBootstrapper.js', @@ -111,13 +101,45 @@ var filesToConvertES6 = ['Source/**/*.js', '!Specs/TestWorkers/**' ]; -gulp.task('build', function(done) { +function createWorkers() { + rimraf.sync('Build/createWorkers'); + + globby.sync([ + 'Source/Workers/**', + '!Source/Workers/cesiumWorkerBootstrapper.js', + '!Source/Workers/transferTypedArrayTest.js' + ]).forEach(function(file) { + rimraf.sync(file); + }); + + var workers = globby.sync([ + 'Source/WorkersES6/**' + ]); + + return rollup.rollup({ + input: workers, + plugins: [rollupPluginBanner.default('This file is automatically rebuilt by the Cesium build process.')] + }).then(function(bundle) { + return bundle.write({ + dir: 'Build/createWorkers', + format: 'amd' + }); + }).then(function(){ + return streamToPromise( + gulp.src('Build/createWorkers/**').pipe(gulp.dest('Source/Workers')) + ); + }).then(function() { + rimraf.sync('Build/createWorkers'); + }); +} + +gulp.task('build', function() { mkdirp.sync('Build'); glslToJavaScript(minifyShaders, 'Build/minifyShaders.state'); createCesiumJs(); createSpecList(); createJsHintOptions(); - createGalleryList(done); + return Promise.join(createWorkers(), createGalleryList()); }); gulp.task('build-watch', function() { @@ -131,22 +153,60 @@ gulp.task('buildApps', function() { ); }); -gulp.task('clean', function(done) { - filesToClean.forEach(function(file) { - rimraf.sync(file); +gulp.task('build-specs', function buildSpecs() { + var externalCesium = rollupPluginExternalGlobals({ + '../Source/Cesium.js': 'Cesium', + '../../Source/Cesium.js': 'Cesium', + '../../../Source/Cesium.js': 'Cesium', + '../../../../Source/Cesium.js': 'Cesium' }); - done(); -}); -gulp.task('requirejs', function(done) { - var config = JSON.parse(Buffer.from(process.argv[3].substring(2), 'base64').toString('utf8')); + var removePragmas = rollupPluginStripPragma({ + pragmas: ['debug'] + }); - // Disable module load timeout - config.waitSeconds = 0; + var promise = Promise.join( + rollup.rollup({ + input: 'Specs/SpecList.js', + plugins: [externalCesium] + }).then(function(bundle) { + return bundle.write({ + file: 'Build/Specs/Specs.js', + format: 'iife' + }); + }).then(function(){ + return rollup.rollup({ + input: 'Specs/spec-main.js', + plugins: [removePragmas, externalCesium] + }).then(function(bundle) { + return bundle.write({ + file: 'Build/Specs/spec-main.js', + format: 'iife' + }); + }); + }).then(function(){ + return rollup.rollup({ + input: 'Specs/karma-main.js', + plugins: [removePragmas, externalCesium] + }).then(function(bundle) { + return bundle.write({ + file: 'Build/Specs/karma-main.js', + name: 'karmaMain', + format: 'iife' + }); + }); + }) + ); - requirejs.optimize(config, function() { - done(); - }, done); + return promise; +}); + +gulp.task('clean', function(done) { + rimraf.sync('Build'); + globby.sync(filesToClean).forEach(function(file) { + rimraf.sync(file); + }); + done(); }); // optimizeApproximateTerrainHeights can be used to regenerate the approximateTerrainHeights @@ -177,7 +237,7 @@ function cloc() { //Run cloc on primary Source files only var source = new Promise(function(resolve, reject) { cmdLine = 'perl ' + clocPath + ' --quiet --progress-rate=0' + - ' Source/ --exclude-dir=Assets,ThirdParty --not-match-f=copyrightHeader.js'; + ' Source/ --exclude-dir=Assets,ThirdParty,Workers --not-match-f=copyrightHeader.js'; child_process.exec(cmdLine, function(error, stdout, stderr) { if (error) { @@ -210,43 +270,6 @@ function cloc() { gulp.task('cloc', gulp.series('clean', cloc)); -function generateStubs(done) { - mkdirp.sync(path.join('Build', 'Stubs')); - - var contents = '\ -/*global define,Cesium*/\n\ -(function() {\n\ -\'use strict\';\n'; - var modulePathMappings = []; - - globby.sync(sourceFiles).forEach(function(file) { - file = path.relative('Source', file); - var moduleId = filePathToModuleId(file); - - contents += '\ -define(\'' + moduleId + '\', function() {\n\ - return Cesium[\'' + path.basename(file, path.extname(file)) + '\'];\n\ -});\n\n'; - - modulePathMappings.push(' \'' + moduleId + '\' : \'../Stubs/Cesium\''); - }); - - contents += '})();\n'; - - var paths = '\ -define(function() {\n\ - \'use strict\';\n\ - return {\n' + modulePathMappings.join(',\n') + '\n\ - };\n\ -});'; - - fs.writeFileSync(path.join('Build', 'Stubs', 'Cesium.js'), contents); - fs.writeFileSync(path.join('Build', 'Stubs', 'paths.js'), paths); - done(); -} - -gulp.task('generateStubs', gulp.series('build', generateStubs)); - function combine() { var outputDirectory = path.join('Build', 'CesiumUnminified'); return combineJavaScript({ @@ -256,7 +279,7 @@ function combine() { }); } -gulp.task('combine', gulp.series('generateStubs', combine)); +gulp.task('combine', gulp.series('build', combine)); gulp.task('default', gulp.series('combine')); function combineRelease() { @@ -268,7 +291,7 @@ function combineRelease() { }); } -gulp.task('combineRelease', gulp.series('generateStubs', combineRelease)); +gulp.task('combineRelease', gulp.series('build', combineRelease)); //Builds the documentation function generateDocumentation() { @@ -300,7 +323,7 @@ gulp.task('generateDocumentation-watch', function() { }); }); -gulp.task('release', gulp.series('generateStubs', combine, minifyRelease, generateDocumentation)); +gulp.task('release', gulp.series('build', combine, minifyRelease, generateDocumentation)); gulp.task('makeZipFile', gulp.series('release', function() { //For now we regenerate the JS glsl to force it to be unminified in the release zip @@ -308,7 +331,6 @@ gulp.task('makeZipFile', gulp.series('release', function() { glslToJavaScript(false, 'Build/minifyShaders.state'); var builtSrc = gulp.src([ - 'Build/Apps/**', 'Build/Cesium/**', 'Build/CesiumUnminified/**', 'Build/Documentation/**' @@ -349,7 +371,7 @@ gulp.task('makeZipFile', gulp.series('release', function() { .pipe(gulp.dest('.')); })); -gulp.task('minify', gulp.series('generateStubs', function() { +gulp.task('minify', gulp.series('build', function() { return combineJavaScript({ removePragmas : false, optimizer : 'uglify2', @@ -365,7 +387,7 @@ function minifyRelease() { }); } -gulp.task('minifyRelease', gulp.series('generateStubs', minifyRelease)); +gulp.task('minifyRelease', gulp.series('build', minifyRelease)); function isTravisPullRequest() { return process.env.TRAVIS_PULL_REQUEST !== undefined && process.env.TRAVIS_PULL_REQUEST !== 'false'; @@ -719,13 +741,16 @@ gulp.task('coverage', function(done) { suppressSkipped: true }, preprocessors: { - 'Source/Core/**/*.js': ['coverage'], - 'Source/DataSources/**/*.js': ['coverage'], - 'Source/Renderer/**/*.js': ['coverage'], - 'Source/Scene/**/*.js': ['coverage'], - 'Source/Shaders/**/*.js': ['coverage'], - 'Source/Widgets/**/*.js': ['coverage'], - 'Source/Workers/**/*.js': ['coverage'] + 'Source/Core/**/*.js': ['karma-coverage-istanbul-instrumenter'], + 'Source/DataSources/**/*.js': ['karma-coverage-istanbul-instrumenter'], + 'Source/Renderer/**/*.js': ['karma-coverage-istanbul-instrumenter'], + 'Source/Scene/**/*.js': ['karma-coverage-istanbul-instrumenter'], + 'Source/Shaders/**/*.js': ['karma-coverage-istanbul-instrumenter'], + 'Source/Widgets/**/*.js': ['karma-coverage-istanbul-instrumenter'], + 'Source/Workers/**/*.js': ['karma-coverage-istanbul-instrumenter'] + }, + coverageIstanbulInstrumenter: { + esModules: true }, reporters: ['spec', 'coverage'], coverageReporter: { @@ -776,13 +801,29 @@ gulp.task('test', function(done) { } var files = [ - 'Specs/karma-main.js', - {pattern : 'Source/**', included : false}, - {pattern : 'Specs/**', included : false} + { pattern: 'Specs/karma-main.js', included: true, type: 'module' }, + { pattern: 'Source/**', included: false, type: 'module' }, + { pattern: 'Specs/*.js', included: true, type: 'module' }, + { pattern: 'Specs/Core/**', included: true, type: 'module' }, + { pattern: 'Specs/Data/**', included: false }, + { pattern: 'Specs/DataSources/**', included: true, type: 'module' }, + { pattern: 'Specs/Renderer/**', included: true, type: 'module' }, + { pattern: 'Specs/Scene/**', included: true, type: 'module' }, + { pattern: 'Specs/ThirdParty/**', included: true, type: 'module' }, + { pattern: 'Specs/Widgets/**', included: true, type: 'module' }, + { pattern: 'Specs/TestWorkers/**', included: false } ]; if (release) { - files.push({pattern : 'Build/**', included : false}); + files = [ + { pattern: 'Specs/Data/**', included: false }, + { pattern: 'Specs/ThirdParty/**', included: true, type: 'module' }, + { pattern: 'Specs/TestWorkers/**', included: false }, + { pattern: 'Build/Cesium/Cesium.js', included: true }, + { pattern: 'Build/Cesium/**', included: false }, + { pattern: 'Build/Specs/karma-main.js', included: true }, + { pattern: 'Build/Specs/Specs.js', included: true } + ]; } var karma = new Karma.Server({ @@ -809,114 +850,6 @@ gulp.task('test', function(done) { karma.start(); }); -gulp.task('sortRequires', function() { - var noModulesRegex = /[\s\S]*?define\(function\(\)/; - var requiresRegex = /([\s\S]*?(define|require)\((?:{[\s\S]*}, )?\[)([\S\s]*?)]([\s\S]*?function\s*)\(([\S\s]*?)\) {([\s\S]*)/; - var splitRegex = /,\s*/; - - var fsReadFile = Promise.promisify(fs.readFile); - var fsWriteFile = Promise.promisify(fs.writeFile); - - var files = globby.sync(filesToSortRequires); - return Promise.map(files, function(file) { - - return fsReadFile(file).then(function(contents) { - - var result = requiresRegex.exec(contents); - - if (result === null) { - if (!noModulesRegex.test(contents)) { - console.log(file + ' does not have the expected syntax.'); - } - return; - } - - var names = result[3].split(splitRegex); - if (names.length === 1 && names[0].trim() === '') { - names.length = 0; - } - - var i; - for (i = 0; i < names.length; ++i) { - if (names[i].indexOf('//') >= 0 || names[i].indexOf('/*') >= 0) { - console.log(file + ' contains comments in the require list. Skipping so nothing gets broken.'); - return; - } - } - - var identifiers = result[5].split(splitRegex); - if (identifiers.length === 1 && identifiers[0].trim() === '') { - identifiers.length = 0; - } - - for (i = 0; i < identifiers.length; ++i) { - if (identifiers[i].indexOf('//') >= 0 || identifiers[i].indexOf('/*') >= 0) { - console.log(file + ' contains comments in the require list. Skipping so nothing gets broken.'); - return; - } - } - - var requires = []; - - for (i = 0; i < names.length && i < identifiers.length; ++i) { - requires.push({ - name : names[i].trim(), - identifier : identifiers[i].trim() - }); - } - - requires.sort(function(a, b) { - var aName = a.name.toLowerCase(); - var bName = b.name.toLowerCase(); - if (aName < bName) { - return -1; - } else if (aName > bName) { - return 1; - } - return 0; - }); - - // Convert back to separate lists for the names and identifiers, and add - // any additional names or identifiers that don't have a corresponding pair. - var sortedNames = requires.map(function(item) { - return item.name; - }); - for (i = sortedNames.length; i < names.length; ++i) { - sortedNames.push(names[i].trim()); - } - - var sortedIdentifiers = requires.map(function(item) { - return item.identifier; - }); - for (i = sortedIdentifiers.length; i < identifiers.length; ++i) { - sortedIdentifiers.push(identifiers[i].trim()); - } - - var outputNames = ']'; - if (sortedNames.length > 0) { - outputNames = os.EOL + ' ' + - sortedNames.join(',' + os.EOL + ' ') + - os.EOL + ' ]'; - } - - var outputIdentifiers = '('; - if (sortedIdentifiers.length > 0) { - outputIdentifiers = '(' + os.EOL + ' ' + - sortedIdentifiers.join(',' + os.EOL + ' '); - } - - contents = result[1] + - outputNames + - result[4].replace(/^[,\s]+/, ', ').trim() + - outputIdentifiers + - ') {' + - result[6]; - - return fsWriteFile(file, contents); - }); - }); -}); - gulp.task('convertToModules', function() { var requiresRegex = /([\s\S]*?(define|defineSuite|require)\((?:{[\s\S]*}, )?\[)([\S\s]*?)]([\s\S]*?function\s*)\(([\S\s]*?)\) {([\s\S]*)/; var noModulesRegex = /([\s\S]*?(define|defineSuite|require)\((?:{[\s\S]*}, )?\[?)([\S\s]*?)]?([\s\S]*?function\s*)\(([\S\s]*?)\) {([\s\S]*)/; @@ -1020,7 +953,7 @@ gulp.task('convertToModules', function() { } } } else { - contents += 'import { ' + sortedIdentifiers[q] + ' } from ' + modulePath + ';' + os.EOL; + contents += 'import ' + sortedIdentifiers[q] + ' from ' + modulePath + ';' + os.EOL; } } } @@ -1049,19 +982,26 @@ gulp.task('convertToModules', function() { }); function combineCesium(debug, optimizer, combineOutput) { - return requirejsOptimize('Cesium.js', { - wrap : true, - useStrict : true, - optimize : optimizer, - optimizeCss : 'standard', - pragmas : { - debug : debug - }, - baseUrl : 'Source', - skipModuleInsertion : true, - name : removeExtension(path.relative('Source', require.resolve('almond'))), - include : 'main', - out : path.join(combineOutput, 'Cesium.js') + var plugins = []; + + if (!debug) { + plugins.push(rollupPluginStripPragma({ + pragmas: ['debug'] + })); + } + if (optimizer === 'uglify2') { + plugins.push(rollupPluginUglify.uglify()); + } + + return rollup.rollup({ + input: 'Source/Cesium.js', + plugins: plugins + }).then(function(bundle) { + return bundle.write({ + format: 'umd', + name: 'Cesium', + file: path.join(combineOutput, 'Cesium.js') + }); }); } @@ -1083,65 +1023,46 @@ function combineWorkers(debug, optimizer, combineOutput) { }) .then(function(files) { return Promise.map(files, function(file) { - return requirejsOptimize(file, { - wrap : false, - useStrict : true, - optimize : optimizer, - optimizeCss : 'standard', - pragmas : { - debug : debug - }, - baseUrl : 'Source', - skipModuleInsertion : true, - include : filePathToModuleId(path.relative('Source', file)), - out : path.join(combineOutput, path.relative('Source', file)) - }); + return streamToPromise(gulp.src(file) + .pipe(gulpUglify()) + .pipe(gulp.dest(path.dirname(path.join(combineOutput, path.relative('Source', file)))))); }, {concurrency : concurrency}); }) .then(function() { - return globby(['Source/Workers/*.js', - '!Source/Workers/cesiumWorkerBootstrapper.js', - '!Source/Workers/transferTypedArrayTest.js', - '!Source/Workers/createTaskProcessorWorker.js', - '!Source/ThirdParty/Workers/*.js']); + return globby(['Source/WorkersES6/*.js']); }) .then(function(files) { - return Promise.map(files, function(file) { - return requirejsOptimize(file, { - wrap : true, - useStrict : true, - optimize : optimizer, - optimizeCss : 'standard', - pragmas : { - debug : debug - }, - baseUrl : 'Source', - include : filePathToModuleId(path.relative('Source', file)), - out : path.join(combineOutput, path.relative('Source', file)) + var plugins = []; + + if (!debug) { + plugins.push(rollupPluginStripPragma({ + pragmas: ['debug'] + })); + } + if (optimizer === 'uglify2') { + plugins.push(rollupPluginUglify.uglify()); + } + + return rollup.rollup({ + input: files, + plugins: plugins + }).then(function(bundle) { + return bundle.write({ + dir: path.join(combineOutput, 'Workers'), + format: 'amd' }); - }, {concurrency : concurrency}); + }); }); } function minifyCSS(outputDirectory) { - return globby('Source/**/*.css').then(function(files) { - return Promise.map(files, function(file) { - return requirejsOptimize(file, { - wrap : true, - useStrict : true, - optimizeCss : 'standard', - pragmas : { - debug : true - }, - cssIn : file, - out : path.join(outputDirectory, path.relative('Source', file)) - }); - }, {concurrency : concurrency}); - }); + streamToPromise( + gulp.src('Source/**/*.css') + .pipe(cleanCSS()) + .pipe(gulp.dest(outputDirectory)) + ); } -var gulpUglify = require('gulp-uglify'); - function minifyModules(outputDirectory) { return streamToPromise(gulp.src('Source/ThirdParty/google-earth-dbroot-parser.js') .pipe(gulpUglify()) @@ -1173,7 +1094,6 @@ function combineJavaScript(options) { promises.push(streamToPromise(stream)); var everythingElse = ['Source/**', '!**/*.js', '!**/*.glsl']; - if (optimizer === 'uglify2') { promises.push(minifyCSS(outputDirectory)); everythingElse.push('!**/*.css'); @@ -1250,10 +1170,7 @@ function glslToJavaScript(minify, minifyStateFilePath) { contents = contents.split('"').join('\\"').replace(/\n/gm, '\\n\\\n'); contents = copyrightComments + '\ //This file is automatically rebuilt by the Cesium build process.\n\ -define(function() {\n\ - \'use strict\';\n\ - return "' + contents + '";\n\ -});'; +export default "' + contents + '";\n'; fs.writeFileSync(jsFile, contents); }); @@ -1264,95 +1181,60 @@ define(function() {\n\ }); var generateBuiltinContents = function(contents, builtins, path) { - var amdPath = contents.amdPath; - var amdClassName = contents.amdClassName; - var builtinLookup = contents.builtinLookup; for (var i = 0; i < builtins.length; i++) { var builtin = builtins[i]; - amdPath = amdPath + ',\n \'./' + path + '/' + builtin + '\''; - amdClassName = amdClassName + ',\n ' + 'czm_' + builtin; - builtinLookup = builtinLookup + ',\n ' + 'czm_' + builtin + ' : ' + 'czm_' + builtin; + contents.imports.push('import czm_' + builtin + ' from \'./' + path + '/' + builtin + '.js\''); + contents.builtinLookup.push('czm_' + builtin + ' : ' + 'czm_' + builtin); } - contents.amdPath = amdPath; - contents.amdClassName = amdClassName; - contents.builtinLookup = builtinLookup; }; -//generate the JS file for Built-in GLSL Functions, Structs, and Constants - var contents = {amdPath : '', amdClassName : '', builtinLookup : ''}; + //generate the JS file for Built-in GLSL Functions, Structs, and Constants + var contents = { + imports : [], + builtinLookup: [] + }; generateBuiltinContents(contents, builtinConstants, 'Constants'); generateBuiltinContents(contents, builtinStructs, 'Structs'); generateBuiltinContents(contents, builtinFunctions, 'Functions'); - contents.amdPath = contents.amdPath.replace(',\n', ''); - contents.amdClassName = contents.amdClassName.replace(',\n', ''); - contents.builtinLookup = contents.builtinLookup.replace(',\n', ''); - - var fileContents = '\ -//This file is automatically rebuilt by the Cesium build process.\n\ -define([\n' + - contents.amdPath + - '\n ], function(\n' + - contents.amdClassName + - ') {\n\ - \'use strict\';\n\ - return {\n' + contents.builtinLookup + '};\n\ -});'; + var fileContents = '//This file is automatically rebuilt by the Cesium build process.\n' + + contents.imports.join('\n') + + '\n\nexport default {\n ' + contents.builtinLookup.join(',\n ') + '\n};\n'; fs.writeFileSync(path.join('Source', 'Shaders', 'Builtin', 'CzmBuiltins.js'), fileContents); } function createCesiumJs() { - var moduleIds = []; - var parameters = []; - var assignments = []; - - var nonIdentifierRegexp = /[^0-9a-zA-Z_$]/g; - + var contents = `export var VERSION = '${version}';\n`; globby.sync(sourceFiles).forEach(function(file) { file = path.relative('Source', file); + var moduleId = file; moduleId = filePathToModuleId(moduleId); - var assignmentName = "['" + path.basename(file, path.extname(file)) + "']"; + var assignmentName = path.basename(file, path.extname(file)); if (moduleId.indexOf('Shaders/') === 0) { - assignmentName = '._shaders' + assignmentName; + assignmentName = '_shaders' + assignmentName; } - - var parameterName = moduleId.replace(nonIdentifierRegexp, '_'); - - moduleIds.push("'./" + moduleId + "'"); - parameters.push(parameterName); - assignments.push('Cesium' + assignmentName + ' = ' + parameterName + ';'); + assignmentName = assignmentName.replace(/(\.|-)/g, '_'); + contents += 'export { default as ' + assignmentName + " } from './" + moduleId + ".js';" + os.EOL; }); - var contents = '\ -define([' + moduleIds.join(', ') + '], function(' + parameters.join(', ') + ') {\n\ - \'use strict\';\n\ - var Cesium = {\n\ - VERSION : \'' + version + '\',\n\ - _shaders : {}\n\ - };\n\ - ' + assignments.join('\n ') + '\n\ - return Cesium;\n\ -});\n'; - fs.writeFileSync('Source/Cesium.js', contents); } function createSpecList() { - var specFiles = globby.sync(['Specs/**/*.js', '!Specs/*.js']); - var specs = []; + var specFiles = globby.sync(['Specs/**/*Spec.js']); + var contents = ''; specFiles.forEach(function(file) { - specs.push("'" + filePathToModuleId(file) + "'"); + contents += "import './" + filePathToModuleId(file).replace('Specs/', '') + ".js';\n"; }); - var contents = '/*eslint-disable no-unused-vars*/\n/*eslint-disable no-implicit-globals*/\nvar specs = [' + specs.join(',') + '];\n'; fs.writeFileSync(path.join('Specs', 'SpecList.js'), contents); } -function createGalleryList(done) { +function createGalleryList() { var demoObjects = []; var demoJSONs = []; var output = path.join('Apps', 'Sandcastle', 'gallery', 'gallery-index.js'); @@ -1417,29 +1299,18 @@ function createGalleryList(done) { var contents = '\ // This file is automatically rebuilt by the Cesium build process.\n\ var hello_world_index = ' + helloWorldIndex + ';\n\ +var VERSION = \'' + version + '\';\n\ var gallery_demos = [' + demoJSONs.join(', ') + '];\n\ var has_new_gallery_demos = ' + (newDemos.length > 0 ? 'true;' : 'false;') + '\n'; fs.writeFileSync(output, contents); // Compile CSS for Sandcastle - var outputFile = path.join('Apps', 'Sandcastle', 'templates', 'bucket.css'); - - requirejs.optimize({ - cssIn : path.join('Apps', 'Sandcastle', 'templates', 'bucketRaw.css'), - out : outputFile, - waitSeconds : 0 - }, function() { - var data = fs.readFileSync(outputFile); //read existing contents into data - var fd = fs.openSync(outputFile, 'w+'); - var buffer = Buffer.from('/* This file is automatically rebuilt by the Cesium build process. */\n'); - - fs.writeSync(fd, buffer, 0, buffer.length, 0); //write new data - fs.writeSync(fd, data, 0, data.length, buffer.length); //append old data - - fs.close(fd); - done(); - }, done); + return streamToPromise(gulp.src(path.join('Apps', 'Sandcastle', 'templates', 'bucketRaw.css')) + .pipe(cleanCSS()) + .pipe(gulpRename('bucket.css')) + .pipe(gulpInsert.prepend('/* This file is automatically rebuilt by the Cesium build process. */\n')) + .pipe(gulp.dest(path.join('Apps', 'Sandcastle', 'templates')))); } function createJsHintOptions() { @@ -1459,15 +1330,16 @@ var sandcastleJsHintOptions = ' + JSON.stringify(primary, null, 4) + ';\n'; function buildSandcastle() { var appStream = gulp.src([ 'Apps/Sandcastle/**', + '!Apps/Sandcastle/load-cesium-es6.js', '!Apps/Sandcastle/standalone.html', '!Apps/Sandcastle/images/**', '!Apps/Sandcastle/gallery/**.jpg' ]) - // Replace require Source with pre-built Cesium - .pipe(gulpReplace('../../../ThirdParty/requirejs-2.1.20/require.js', '../../../CesiumUnminified/Cesium.js')) - // Use unminified cesium instead of source - .pipe(gulpReplace('Source/Cesium', 'CesiumUnminified')) + // Remove dev-only ES6 module loading for unbuilt Cesium + .pipe(gulpReplace(' ', '')) + .pipe(gulpReplace('nomodule', '')) // Fix relative paths for new location + .pipe(gulpReplace('../../../Build', '../../..')) .pipe(gulpReplace('../../Source', '../../../Source')) .pipe(gulpReplace('../../ThirdParty', '../../../ThirdParty')) .pipe(gulpReplace('../../SampleData', '../../../../Apps/SampleData')) @@ -1486,8 +1358,9 @@ function buildSandcastle() { var standaloneStream = gulp.src([ 'Apps/Sandcastle/standalone.html' ]) - .pipe(gulpReplace('../../ThirdParty/requirejs-2.1.20/require.js', '../../../ThirdParty/requirejs-2.1.20/require.js')) - .pipe(gulpReplace('Source/Cesium', 'CesiumUnminified')) + .pipe(gulpReplace(' ', '')) + .pipe(gulpReplace('nomodule', '')) + .pipe(gulpReplace('../../Build', '../..')) .pipe(gulp.dest('Build/Apps/Sandcastle')); return streamToPromise(mergeStream(appStream, imageStream, standaloneStream)); @@ -1495,58 +1368,49 @@ function buildSandcastle() { function buildCesiumViewer() { var cesiumViewerOutputDirectory = 'Build/Apps/CesiumViewer'; - var cesiumViewerStartup = path.join(cesiumViewerOutputDirectory, 'CesiumViewerStartup.js'); - var cesiumViewerCss = path.join(cesiumViewerOutputDirectory, 'CesiumViewer.css'); mkdirp.sync(cesiumViewerOutputDirectory); var promise = Promise.join( - requirejsOptimize('CesiumViewer', { - wrap : true, - useStrict : true, - optimizeCss : 'standard', - pragmas : { - debug : false + rollup.rollup({ + input: 'Apps/CesiumViewer/CesiumViewer.js', + treeshake: { + moduleSideEffects: false }, - optimize : 'uglify2', - mainConfigFile : 'Apps/CesiumViewer/CesiumViewerStartup.js', - name : 'CesiumViewerStartup', - out : cesiumViewerStartup - }), - requirejsOptimize('CesiumViewer CSS', { - wrap : true, - useStrict : true, - optimizeCss : 'standard', - pragmas : { - debug : false - }, - cssIn : 'Apps/CesiumViewer/CesiumViewer.css', - out : cesiumViewerCss + plugins: [ + rollupPluginStripPragma({ + pragmas: ['debug'] + }), + rollupPluginUglify.uglify() + ] + }).then(function(bundle) { + return bundle.write({ + file: 'Build/Apps/CesiumViewer/CesiumViewer.js', + format: 'iife' + }); }) ); promise = promise.then(function() { var copyrightHeader = fs.readFileSync(path.join('Source', 'copyrightHeader.js')); - var stream = mergeStream( - gulp.src(cesiumViewerStartup) + gulp.src('Build/Apps/CesiumViewer/CesiumViewer.js') .pipe(gulpInsert.prepend(copyrightHeader)) - .pipe(gulpReplace('../../Source', '.')) - .pipe(gulpReplace('../../ThirdParty/requirejs-2.1.20', '.')), + .pipe(gulp.dest(cesiumViewerOutputDirectory)), - gulp.src(cesiumViewerCss) - .pipe(gulpReplace('../../Source', '.')), + gulp.src('Apps/CesiumViewer/CesiumViewer.css') + .pipe(cleanCSS()) + .pipe(gulpReplace('../../Source', '.')) + .pipe(gulp.dest(cesiumViewerOutputDirectory)), - gulp.src(['Apps/CesiumViewer/index.html']) - .pipe(gulpReplace('../../ThirdParty/requirejs-2.1.20', '.')), + gulp.src('Apps/CesiumViewer/index.html') + .pipe(gulpReplace('type="module"', '')) + .pipe(gulp.dest(cesiumViewerOutputDirectory)), gulp.src(['Apps/CesiumViewer/**', '!Apps/CesiumViewer/index.html', '!Apps/CesiumViewer/**/*.js', '!Apps/CesiumViewer/**/*.css']), - gulp.src(['ThirdParty/requirejs-2.1.20/require.min.js']) - .pipe(gulpRename('require.js')), - gulp.src(['Build/Cesium/Assets/**', 'Build/Cesium/Workers/**', 'Build/Cesium/ThirdParty/**', @@ -1573,27 +1437,3 @@ function buildCesiumViewer() { function filePathToModuleId(moduleId) { return moduleId.substring(0, moduleId.lastIndexOf('.')).replace(/\\/g, '/'); } - -function removeExtension(p) { - return p.slice(0, -path.extname(p).length); -} - -function requirejsOptimize(name, config) { - if (verbose) { - console.log('Building ' + name); - } - return new Promise(function(resolve, reject) { - var cmd = 'npm run requirejs -- --' + Buffer.from(JSON.stringify(config)).toString('base64') + ' --silent'; - child_process.exec(cmd, function(e) { - if (e) { - console.log('Error ' + name); - reject(e); - return; - } - if (verbose) { - console.log('Finished ' + name); - } - resolve(); - }); - }); -} diff --git a/index.js b/index.js index 4fef4286713..5e242695937 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -/*eslint-env node,es6*/ +/*eslint-env node*/ 'use strict'; var path = require('path'); @@ -8,14 +8,4 @@ if (process.env.NODE_ENV === 'production') { module.exports = require(path.join(__dirname, 'Build/Cesium/Cesium')); return; } - -// Otherwise, use un-optimized requirejs modules for improved error checking. For example 'development' mode -var requirejs = require('requirejs'); -requirejs.config({ - paths: { - 'Cesium': path.join(__dirname, 'Source') - }, - nodeRequire: require -}); - -module.exports = requirejs('Cesium/Cesium'); +module.exports = require('esm')(module)('./Source/Cesium.js'); diff --git a/package.json b/package.json index 952d37f6c51..3bbd6391c61 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,9 @@ "email": "cesium-dev@googlegroups.com" }, "dependencies": { - "requirejs": "^2.3.2" + "esm": "^3.2.25" }, "devDependencies": { - "almond": "^0.3.3", "aws-sdk": "^2.531.0", "bluebird": "^3.4.6", "cloc": "^2.3.3", @@ -44,6 +43,7 @@ "globby": "^10.0.0", "glsl-strip-comments": "^1.0.0", "gulp": "^4.0.0", + "gulp-clean-css": "^4.2.0", "gulp-insert": "^0.5.0", "gulp-json-transform": "^0.4.6", "gulp-rename": "^1.2.2", @@ -56,13 +56,13 @@ "karma": "^4.0.0", "karma-chrome-launcher": "^3.1.0", "karma-coverage": "^2.0.1", + "karma-coverage-istanbul-instrumenter": "^1.0.1", "karma-detect-browsers": "^2.2.3", "karma-edge-launcher": "^0.4.2", "karma-firefox-launcher": "^1.0.0", "karma-ie-launcher": "^1.0.0", "karma-jasmine": "^2.0.0", "karma-longest-reporter": "^1.1.0", - "karma-requirejs": "^1.1.0", "karma-safari-launcher": "^1.0.0", "karma-spec-reporter": "^0.0.32", "merge-stream": "^2.0.0", @@ -71,6 +71,11 @@ "open": "^6.4.0", "request": "^2.79.0", "rimraf": "^3.0.0", + "rollup": "^1.21.4", + "rollup-plugin-banner": "^0.2.1", + "rollup-plugin-external-globals": "^0.4.0", + "rollup-plugin-strip-pragma": "^1.0.0", + "rollup-plugin-uglify": "^6.0.3", "stream-to-promise": "^2.2.0", "yargs": "^14.0.0" }, @@ -86,7 +91,6 @@ "combine": "gulp combine", "combineRelease": "gulp combineRelease", "coverage": "gulp coverage", - "requirejs": "gulp requirejs", "generateDocumentation": "gulp generateDocumentation", "generateDocumentation-watch": "gulp generateDocumentation-watch", "eslint": "eslint \"./**/*.js\" \"./**/*.html\" --cache --quiet", @@ -95,6 +99,7 @@ "minifyRelease": "gulp minifyRelease", "optimizeApproximateTerrainHeights": "gulp optimizeApproximateTerrainHeights -p 2", "release": "gulp release", + "build-specs": "gulp build-specs", "test": "gulp test", "test-all": "gulp test --all", "test-webgl": "gulp test --include WebGL", @@ -102,7 +107,6 @@ "test-webgl-validation": "gulp test --webglValidation", "test-webgl-stub": "gulp test --webglStub", "test-release": "gulp test --release", - "generateStubs": "gulp generateStubs", "sortRequires": "gulp sortRequires", "deploy-s3": "gulp deploy-s3", "deploy-status": "gulp deploy-status",