UNPKG

node-red

Version:

A visual tool for wiring the Internet of Things

1,365 lines (1,269 loc) 1.26 MB
/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ var RED = {}; ;/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.events = (function() { var handlers = {}; function on(evt,func) { handlers[evt] = handlers[evt]||[]; handlers[evt].push(func); } function off(evt,func) { var handler = handlers[evt]; if (handler) { for (var i=0;i<handler.length;i++) { if (handler[i] === func) { handler.splice(i,1); return; } } } } function emit(evt,arg) { if (handlers[evt]) { for (var i=0;i<handlers[evt].length;i++) { try { handlers[evt][i](arg); } catch(err) { console.log("RED.events.emit error: ["+evt+"] "+(err.toString())); console.log(err); } } } } return { on: on, off: off, emit: emit } })(); ;/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.i18n = (function() { return { init: function(done) { i18n.init({ resGetPath: 'locales/__ns__?lng=__lng__', dynamicLoad: false, load:'current', ns: { namespaces: ["editor","node-red","jsonata","infotips"], defaultNs: "editor" }, fallbackLng: ['en-US'], useCookie: false },function() { done(); }); RED["_"] = function() { return i18n.t.apply(null,arguments); } }, loadCatalog: function(namespace,done) { var languageList = i18n.functions.toLanguages(i18n.detectLanguage()); var toLoad = languageList.length; languageList.forEach(function(lang) { $.ajax({ headers: { "Accept":"application/json" }, cache: false, url: 'locales/'+namespace+'?lng='+lang, success: function(data) { i18n.addResourceBundle(lang,namespace,data); toLoad--; if (toLoad === 0) { done(); } } }); }) }, loadNodeCatalogs: function(done) { var languageList = i18n.functions.toLanguages(i18n.detectLanguage()); var toLoad = languageList.length; languageList.forEach(function(lang) { $.ajax({ headers: { "Accept":"application/json" }, cache: false, url: 'locales/nodes?lng='+lang, success: function(data) { var namespaces = Object.keys(data); namespaces.forEach(function(ns) { i18n.addResourceBundle(lang,ns,data[ns]); }); toLoad--; if (toLoad === 0) { done(); } } }); }) } } })(); ;/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.settings = (function () { var loadedSettings = {}; var userSettings = {}; var settingsDirty = false; var pendingSave; var hasLocalStorage = function () { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } }; var set = function (key, value) { if (!hasLocalStorage()) { return; } if (key === "auth-tokens") { localStorage.setItem(key, JSON.stringify(value)); } else { userSettings[key] = value; saveUserSettings(); } }; /** * If the key is not set in the localStorage it returns <i>undefined</i> * Else return the JSON parsed value * @param key * @returns {*} */ var get = function (key) { if (!hasLocalStorage()) { return undefined; } if (key === "auth-tokens") { return JSON.parse(localStorage.getItem(key)); } else { return userSettings[key]; } }; var remove = function (key) { if (!hasLocalStorage()) { return; } if (key === "auth-tokens") { localStorage.removeItem(key); } else { delete userSettings[key]; saveUserSettings(); } }; var setProperties = function(data) { for (var prop in loadedSettings) { if (loadedSettings.hasOwnProperty(prop) && RED.settings.hasOwnProperty(prop)) { delete RED.settings[prop]; } } for (prop in data) { if (data.hasOwnProperty(prop)) { RED.settings[prop] = data[prop]; } } loadedSettings = data; }; var setUserSettings = function(data) { userSettings = data; } var init = function (done) { var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search); if (accessTokenMatch) { var accessToken = accessTokenMatch[1]; RED.settings.set("auth-tokens",{access_token: accessToken}); window.location.search = ""; } $.ajaxSetup({ beforeSend: function(jqXHR,settings) { // Only attach auth header for requests to relative paths if (!/^\s*(https?:|\/|\.)/.test(settings.url)) { var auth_tokens = RED.settings.get("auth-tokens"); if (auth_tokens) { jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token); } jqXHR.setRequestHeader("Node-RED-API-Version","v2"); } } }); load(done); } var load = function(done) { $.ajax({ headers: { "Accept": "application/json" }, dataType: "json", cache: false, url: 'settings', success: function (data) { setProperties(data); if (!RED.settings.user || RED.settings.user.anonymous) { RED.settings.remove("auth-tokens"); } console.log("Node-RED: " + data.version); loadUserSettings(done); }, error: function(jqXHR,textStatus,errorThrown) { if (jqXHR.status === 401) { if (/[?&]access_token=(.*?)(?:$|&)/.test(window.location.search)) { window.location.search = ""; } RED.user.login(function() { load(done); }); } else { console.log("Unexpected error loading settings:",jqXHR.status,textStatus); } } }); }; function loadUserSettings(done) { $.ajax({ headers: { "Accept": "application/json" }, dataType: "json", cache: false, url: 'settings/user', success: function (data) { setUserSettings(data); done(); }, error: function(jqXHR,textStatus,errorThrown) { console.log("Unexpected error loading user settings:",jqXHR.status,textStatus); } }); } function saveUserSettings() { if (RED.user.hasPermission("settings.write")) { if (pendingSave) { clearTimeout(pendingSave); } pendingSave = setTimeout(function() { pendingSave = null; $.ajax({ method: 'POST', contentType: 'application/json', url: 'settings/user', data: JSON.stringify(userSettings), success: function (data) { }, error: function(jqXHR,textStatus,errorThrown) { console.log("Unexpected error saving user settings:",jqXHR.status,textStatus); } }); },300); } } function theme(property,defaultValue) { if (!RED.settings.editorTheme) { return defaultValue; } var parts = property.split("."); var v = RED.settings.editorTheme; try { for (var i=0;i<parts.length;i++) { v = v[parts[i]]; } if (v === undefined) { return defaultValue; } return v; } catch(err) { return defaultValue; } } return { init: init, load: load, loadUserSettings: loadUserSettings, set: set, get: get, remove: remove, theme: theme } })(); ;/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.user = (function() { function login(opts,done) { if (typeof opts == 'function') { done = opts; opts = {}; } var dialog = $('<div id="node-dialog-login" class="hide">'+ '<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+ '<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+ '<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+ '</div>'+ '</div>'); dialog.dialog({ autoOpen: false, dialogClass: "ui-dialog-no-close", modal: true, closeOnEscape: !!opts.cancelable, width: 600, resizable: false, draggable: false }); $("#node-dialog-login-fields").empty(); $.ajax({ dataType: "json", url: "auth/login", success: function(data) { var i=0; if (data.type == "credentials") { for (;i<data.prompts.length;i++) { var field = data.prompts[i]; var row = $("<div/>",{class:"form-row"}); $('<label for="node-dialog-login-'+field.id+'">'+RED._(field.label)+':</label><br/>').appendTo(row); var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row); if (i<data.prompts.length-1) { input.keypress( (function() { var r = row; return function(event) { if (event.keyCode == 13) { r.next("div").find("input").focus(); event.preventDefault(); } } })() ); } row.appendTo("#node-dialog-login-fields"); } $('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">'+RED._("user.loginFailed")+'</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+ (opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">'+RED._("common.label.cancel")+'</a>':'')+ '<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="'+RED._("user.login")+'"></div>').appendTo("#node-dialog-login-fields"); $("#node-dialog-login-submit").button(); $("#node-dialog-login-fields").submit(function(event) { $("#node-dialog-login-submit").button("option","disabled",true); $("#node-dialog-login-failed").hide(); $(".login-spinner").show(); var body = { client_id: "node-red-editor", grant_type: "password", scope:"" } for (var i=0;i<data.prompts.length;i++) { var field = data.prompts[i]; body[field.id] = $("#node-dialog-login-"+field.id).val(); } $.ajax({ url:"auth/token", type: "POST", data: body }).done(function(data,textStatus,xhr) { RED.settings.set("auth-tokens",data); $("#node-dialog-login").dialog('destroy').remove(); if (opts.updateMenu) { updateUserMenu(); } done(); }).fail(function(jqXHR,textStatus,errorThrown) { RED.settings.remove("auth-tokens"); $("#node-dialog-login-failed").show(); }).always(function() { $("#node-dialog-login-submit").button("option","disabled",false); $(".login-spinner").hide(); }); event.preventDefault(); }); } else if (data.type == "strategy") { i = 0; for (;i<data.prompts.length;i++) { var field = data.prompts[i]; var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields"); var loginButton = $('<a href="#"></a>',{style: "padding: 10px"}).appendTo(row).click(function() { document.location = field.url; }); if (field.image) { $("<img>",{src:field.image}).appendTo(loginButton); } else if (field.label) { var label = $('<span></span>').text(field.label); if (field.icon) { $('<i></i>',{class: "fa fa-2x "+field.icon, style:"vertical-align: middle"}).appendTo(loginButton); label.css({ "verticalAlign":"middle", "marginLeft":"8px" }); } label.appendTo(loginButton); } loginButton.button(); } } if (opts.cancelable) { $("#node-dialog-login-cancel").button().click(function( event ) { $("#node-dialog-login").dialog('destroy').remove(); }); } var loginImageSrc = data.image || "red/images/node-red-256.png"; $("#node-dialog-login-image").load(function() { dialog.dialog("open"); }).attr("src",loginImageSrc); } }); } function logout() { var tokens = RED.settings.get("auth-tokens"); var token = tokens?tokens.access_token:""; $.ajax({ url: "auth/revoke", type: "POST", data: {token:token} }).done(function(data,textStatus,xhr) { RED.settings.remove("auth-tokens"); if (data && data.redirect) { document.location.href = data.redirect; } else { document.location.reload(true); } }).fail(function(jqXHR,textStatus,errorThrown) { if (jqXHR.status === 401) { document.location.reload(true); } else { console.log(textStatus); } }) } function updateUserMenu() { $("#btn-usermenu-submenu li").remove(); if (RED.settings.user.anonymous) { RED.menu.addItem("btn-usermenu",{ id:"usermenu-item-login", label:RED._("menu.label.login"), onselect: function() { RED.user.login({cancelable:true},function() { RED.settings.load(function() { RED.notify(RED._("user.loggedInAs",{name:RED.settings.user.username}),"success"); updateUserMenu(); RED.events.emit("login",RED.settings.user.username); }); }); } }); } else { RED.menu.addItem("btn-usermenu",{ id:"usermenu-item-username", label:"<b>"+RED.settings.user.username+"</b>" }); RED.menu.addItem("btn-usermenu",{ id:"usermenu-item-logout", label:RED._("menu.label.logout"), onselect: function() { RED.user.logout(); } }); } } function init() { if (RED.settings.user) { if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) { var userMenu = $('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"></a></li>') .prependTo(".header-toolbar"); if (RED.settings.user.image) { $('<span class="user-profile"></span>').css({ backgroundImage: "url("+RED.settings.user.image+")", }).appendTo(userMenu.find("a")); } else { $('<i class="fa fa-user"></i>').appendTo(userMenu.find("a")); } RED.menu.init({id:"btn-usermenu", options: [] }); updateUserMenu(); } } } var readRE = /^((.+)\.)?read$/ var writeRE = /^((.+)\.)?write$/ function hasPermission(permission) { if (permission === "") { return true; } if (!RED.settings.user) { return true; } return checkPermission(RED.settings.user.permissions||"",permission); } function checkPermission(userScope,permission) { if (permission === "") { return true; } var i; if (Array.isArray(permission)) { // Multiple permissions requested - check each one for (i=0;i<permission.length;i++) { if (!checkPermission(userScope,permission[i])) { return false; } } // All permissions check out return true; } if (Array.isArray(userScope)) { if (userScope.length === 0) { return false; } for (i=0;i<userScope.length;i++) { if (checkPermission(userScope[i],permission)) { return true; } } return false; } if (userScope === "*" || userScope === permission) { return true; } if (userScope === "read" || userScope === "*.read") { return readRE.test(permission); } else if (userScope === "write" || userScope === "*.write") { return writeRE.test(permission); } return false; } return { init: init, login: login, logout: logout, hasPermission: hasPermission } })(); ;/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.comms = (function() { var errornotification = null; var clearErrorTimer = null; var connectCountdownTimer = null; var connectCountdown = 10; var subscriptions = {}; var ws; var pendingAuth = false; var reconnectAttempts = 0; var active = false; function connectWS() { active = true; var path = location.hostname; var port = location.port; if (port.length !== 0) { path = path+":"+port; } path = path+document.location.pathname; path = path+(path.slice(-1) == "/"?"":"/")+"comms"; path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path; var auth_tokens = RED.settings.get("auth-tokens"); pendingAuth = (auth_tokens!=null); function completeConnection() { for (var t in subscriptions) { if (subscriptions.hasOwnProperty(t)) { ws.send(JSON.stringify({subscribe:t})); } } } ws = new WebSocket(path); ws.onopen = function() { reconnectAttempts = 0; if (errornotification) { clearErrorTimer = setTimeout(function() { errornotification.close(); errornotification = null; },1000); } if (pendingAuth) { ws.send(JSON.stringify({auth:auth_tokens.access_token})); } else { completeConnection(); } } ws.onmessage = function(event) { var message = JSON.parse(event.data); for (var m = 0; m < message.length; m++) { var msg = message[m]; if (pendingAuth && msg.auth) { if (msg.auth === "ok") { pendingAuth = false; completeConnection(); } else if (msg.auth === "fail") { // anything else is an error... active = false; RED.user.login({updateMenu:true},function() { connectWS(); }) } } else if (msg.topic) { for (var t in subscriptions) { if (subscriptions.hasOwnProperty(t)) { var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$"); if (re.test(msg.topic)) { var subscribers = subscriptions[t]; if (subscribers) { for (var i=0;i<subscribers.length;i++) { subscribers[i](msg.topic,msg.data); } } } } } } } }; ws.onclose = function() { if (!active) { return; } if (clearErrorTimer) { clearTimeout(clearErrorTimer); clearErrorTimer = null; } reconnectAttempts++; if (reconnectAttempts < 10) { setTimeout(connectWS,1000); if (reconnectAttempts > 5 && errornotification == null) { errornotification = RED.notify(RED._("notification.errors.lostConnection"),"error",true); } } else if (reconnectAttempts < 20) { setTimeout(connectWS,2000); } else { connectCountdown = 60; connectCountdownTimer = setInterval(function() { connectCountdown--; if (connectCountdown === 0) { errornotification.update(RED._("notification.errors.lostConnection")); clearInterval(connectCountdownTimer); connectWS(); } else { var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>'; errornotification.update(msg); $(errornotification).find("a").click(function(e) { e.preventDefault(); errornotification.update(RED._("notification.errors.lostConnection")); clearInterval(connectCountdownTimer); connectWS(); }) } },1000); } } } function subscribe(topic,callback) { if (subscriptions[topic] == null) { subscriptions[topic] = []; } subscriptions[topic].push(callback); if (ws && ws.readyState == 1) { ws.send(JSON.stringify({subscribe:topic})); } } function unsubscribe(topic,callback) { if (subscriptions[topic]) { for (var i=0;i<subscriptions[topic].length;i++) { if (subscriptions[topic][i] === callback) { subscriptions[topic].splice(i,1); break; } } if (subscriptions[topic].length === 0) { delete subscriptions[topic]; } } } return { connect: connectWS, subscribe: subscribe, unsubscribe:unsubscribe } })(); ;/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.text = {}; RED.text.bidi = (function() { var textDir = ""; var LRE = "\u202A", RLE = "\u202B", PDF = "\u202C"; function isRTLValue(stringValue) { var length = stringValue.length; for (var i=0;i<length;i++) { if (isBidiChar(stringValue.charCodeAt(i))) { return true; } else if(isLatinChar(stringValue.charCodeAt(i))) { return false; } } return false; } function isBidiChar(c) { return (c >= 0x05d0 && c <= 0x05ff)|| (c >= 0x0600 && c <= 0x065f)|| (c >= 0x066a && c <= 0x06ef)|| (c >= 0x06fa && c <= 0x07ff)|| (c >= 0xfb1d && c <= 0xfdff)|| (c >= 0xfe70 && c <= 0xfefc); } function isLatinChar(c){ return (c > 64 && c < 91)||(c > 96 && c < 123) } /** * Determines the text direction of a given string. * @param value - the string */ function resolveBaseTextDir(value) { if (textDir == "auto") { if (isRTLValue(value)) { return "rtl"; } else { return "ltr"; } } else { return textDir; } } function onInputChange() { $(this).attr("dir", resolveBaseTextDir($(this).val())); } /** * Adds event listeners to the Input to ensure its text-direction attribute * is properly set based on its content. * @param input - the input field */ function prepareInput(input) { input.on("keyup",onInputChange).on("paste",onInputChange).on("cut",onInputChange); // Set the initial text direction onInputChange.call(input); } /** * Enforces the text direction of a given string by adding * UCC (Unicode Control Characters) * @param value - the string */ function enforceTextDirectionWithUCC(value) { if (value) { var dir = resolveBaseTextDir(value); if (dir == "ltr") { return LRE + value + PDF; } else if (dir == "rtl") { return RLE + value + PDF; } } return value; } /** * Enforces the text direction for all the spans with style bidiAware under * workspace or sidebar div */ function enforceTextDirectionOnPage() { $("#workspace").find('span.bidiAware').each(function() { $(this).attr("dir", resolveBaseTextDir($(this).html())); }); $("#sidebar").find('span.bidiAware').each(function() { $(this).attr("dir", resolveBaseTextDir($(this).text())); }); } /** * Sets the text direction preference * @param dir - the text direction preference */ function setTextDirection(dir) { textDir = dir; RED.nodes.eachNode(function(n) { n.dirty = true;}); RED.view.redraw(); RED.palette.refresh(); enforceTextDirectionOnPage(); } return { setTextDirection: setTextDirection, enforceTextDirectionWithUCC: enforceTextDirectionWithUCC, resolveBaseTextDir: resolveBaseTextDir, prepareInput: prepareInput } })(); ;/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.text.format = (function() { var TextSegment = (function() { var TextSegment = function (obj) { this.content = ""; this.actual = ""; this.textDirection = ""; this.localGui = ""; this.isVisible = true; this.isSeparator = false; this.isParsed = false; this.keep = false; this.inBounds = false; this.inPoints = false; var prop = ""; for (prop in obj) { if (obj.hasOwnProperty(prop)) { this[prop] = obj[prop]; } } }; return TextSegment; })(); var tools = (function() { function initBounds(bounds) { if (!bounds) { return false; } if (typeof(bounds.start) === "undefined") { bounds.start = ""; } if (typeof(bounds.end) === "undefined") { bounds.end = ""; } if (typeof(bounds.startAfter) !== "undefined") { bounds.start = bounds.startAfter; bounds.after = true; } else { bounds.after = false; } if (typeof(bounds.endBefore) !== "undefined") { bounds.end = bounds.endBefore; bounds.before = true; } else { bounds.before = false; } var startPos = parseInt(bounds.startPos, 10); if (!isNaN(startPos)) { bounds.usePos = true; } else { bounds.usePos = false; } var bLength = parseInt(bounds.length, 10); if (!isNaN(bLength)) { bounds.useLength = true; } else { bounds.useLength = false; } bounds.loops = typeof(bounds.loops) !== "undefined" ? !!bounds.loops : true; return true; } function getBounds(segment, src) { var bounds = {}; for (var prop in src) { if (src.hasOwnProperty(prop)) { bounds[prop] = src[prop]; } } var content = segment.content; var usePos = bounds.usePos && bounds.startPos < content.length; if (usePos) { bounds.start = ""; bounds.loops = false; } bounds.bStart = usePos ? bounds.startPos : bounds.start.length > 0 ? content.indexOf(bounds.start) : 0; var useLength = bounds.useLength && bounds.length > 0 && bounds.bStart + bounds.length < content.length; if (useLength) { bounds.end = ""; } bounds.bEnd = useLength ? bounds.bStart + bounds.length : bounds.end.length > 0 ? content.indexOf(bounds.end, bounds.bStart + bounds.start.length) + 1 : content.length; if (!bounds.after) { bounds.start = ""; } if (!bounds.before) { bounds.end = ""; } return bounds; } return { handleSubcontents: function (segments, args, subs, origContent, locale) { // jshint unused: false if (!subs.content || typeof(subs.content) !== "string" || subs.content.length === 0) { return segments; } var sLoops = true; if (typeof(subs.loops) !== "undefined") { sLoops = !!subs.loops; } for (var j = 0; true; j++) { if (j >= segments.length) { break; } if (segments[j].isParsed || segments.keep || segments[j].isSeparator) { continue; } var content = segments[j].content; var start = content.indexOf(subs.content); if (start < 0) { continue; } var end; var length = 0; if (subs.continued) { do { length++; end = content.indexOf(subs.content, start + length * subs.content.length); } while (end === 0); } else { length = 1; } end = start + length * subs.content.length; segments.splice(j, 1); if (start > 0) { segments.splice(j, 0, new TextSegment({ content: content.substring(0, start), localGui: args.dir, keep: true })); j++; } segments.splice(j, 0, new TextSegment({ content: content.substring(start, end), textDirection: subs.subDir, localGui: args.dir })); if (end < content.length) { segments.splice(j + 1, 0, new TextSegment({ content: content.substring(end, content.length), localGui: args.dir, keep: true })); } if (!sLoops) { break; } } }, handleBounds: function (segments, args, aBounds, origContent, locale) { for (var i = 0; i < aBounds.length; i++) { if (!initBounds(aBounds[i])) { continue; } for (var j = 0; true; j++) { if (j >= segments.length) { break; } if (segments[j].isParsed || segments[j].inBounds || segments.keep || segments[j].isSeparator) { continue; } var bounds = getBounds(segments[j], aBounds[i]); var start = bounds.bStart; var end = bounds.bEnd; if (start < 0 || end < 0) { continue; } var content = segments[j].content; segments.splice(j, 1); if (start > 0) { segments.splice(j, 0, new TextSegment({ content: content.substring(0, start), localGui: args.dir, keep: true })); j++; } if (bounds.start) { segments.splice(j, 0, new TextSegment({ content: bounds.start, localGui: args.dir, isSeparator: true })); j++; } segments.splice(j, 0, new TextSegment({ content: content.substring(start + bounds.start.length, end - bounds.end.length), textDirection: bounds.subDir, localGui: args.dir, inBounds: true })); if (bounds.end) { j++; segments.splice(j, 0, new TextSegment({ content: bounds.end, localGui: args.dir, isSeparator: true })); } if (end + bounds.end.length < content.length) { segments.splice(j + 1, 0, new TextSegment({ content: content.substring(end + bounds.end.length, content.length), localGui: args.dir, keep: true })); } if (!bounds.loops) { break; } } } for (i = 0; i < segments.length; i++) { segments[i].inBounds = false; } return segments; }, handleCases: function (segments, args, cases, origContent, locale) { if (cases.length === 0) { return segments; } var hArgs = {}; for (var prop in args) { if (args.hasOwnProperty(prop)) { hArgs[prop] = args[prop]; } } for (var i = 0; i < cases.length; i++) { if (!cases[i].handler || typeof(cases[i].handler.handle) !== "function") { cases[i].handler = args.commonHandler; } if (cases[i].args) { hArgs.cases = cases[i].args.cases; hArgs.points = cases[i].args.points; hArgs.bounds = cases[i].args.bounds; hArgs.subs = cases[i].args.subs; } else { hArgs.cases = []; hArgs.points = []; hArgs.bounds = []; hArgs.subs = {}; } cases[i].handler.handle(origContent, segments, hArgs, locale); } return segments; }, handlePoints: function (segments, args, points, origContent, locale) { //jshint unused: false for (var i = 0; i < points.length; i++) { for (var j = 0; true; j++) { if (j >= segments.length) { break; } if (segments[j].isParsed || segments[j].keep || segments[j].isSeparator) { continue; } var content = segments[j].content; var pos = content.indexOf(points[i]); if (pos >= 0) { segments.splice(j, 1); if (pos > 0) { segments.splice(j, 0, new TextSegment({ content: content.substring(0, pos), textDirection: args.subDir, localGui: args.dir, inPoints: true })); j++; } segments.splice(j, 0, new TextSegment({ content: points[i], localGui: args.dir, isSeparator: true })); if (pos + points[i].length + 1 <= content.length) { segments.splice(j + 1, 0, new TextSegment({ content: content.substring(pos + points[i].length), textDirection: args.subDir, localGui: args.dir, inPoints: true })); } } } } for (i = 0; i < segments.length; i++) { if (segments[i].keep) { segments[i].keep = false; } else if(segments[i].inPoints){ segments[i].isParsed = true; segments[i].inPoints = false; } } return segments; } }; })(); var common = (function() { return { handle: function (content, segments, args, locale) { var cases = []; if (Array.isArray(args.cases)) { cases = args.cases; } var points = []; if (typeof(args.points) !== "undefined") { if (Array.isArray(args.points)) { points = args.points; } else if (typeof(args.points) === "string") { points = args.points.split(""); } } var subs = {}; if (typeof(args.subs) === "object") { subs = args.subs; } var aBounds = []; if (Array.isArray(args.bounds)) { aBounds = args.bounds; } tools.handleBounds(segments, args, aBounds, content, locale); tools.handleSubcontents(segments, args, subs, content, locale); tools.handleCases(segments, args, cases, content, locale); tools.handlePoints(segments, args, points, content, locale); return segments; } }; })(); var misc = (function() { var isBidiLocale = function (locale) { var lang = !locale ? "" : locale.split("-")[0]; if (!lang || lang.length < 2) { return false; } return ["iw", "he", "ar", "fa", "ur"].some(function (bidiLang) { return bidiLang === lang; }); }; var LRE = "\u202A"; var RLE = "\u202B"; var PDF = "\u202C"; var LRM = "\u200E"; var RLM = "\u200F"; var LRO = "\u202D"; var RLO = "\u202E"; return { LRE: LRE, RLE: RLE, PDF: PDF, LRM: LRM, RLM: RLM, LRO: LRO, RLO: RLO, getLocaleDetails: function (locale) { if (!locale) { locale = typeof navigator === "undefined" ? "" : (navigator.language || navigator.userLanguage || ""); } locale = locale.toLowerCase(); if (isBidiLocale(locale)) { var full = locale.split("-"); return {lang: full[0], country: full[1] ? full[1] : ""}; } return {lang: "not-bidi"}; }, removeUcc: function (text) { if (text) { return text.replace(/[\u200E\u200F\u202A-\u202E]/g, ""); } return text; }, removeTags: function (text) { if (text) { return text.replace(/<[^<]*>/g, ""); } ret