2013-10-30 14:46:03 -07:00
|
|
|
/**
|
2014-01-28 03:00:09 -08:00
|
|
|
* @license
|
2013-10-30 14:46:03 -07:00
|
|
|
* Visual Blocks Editor
|
|
|
|
*
|
|
|
|
* Copyright 2012 Google Inc.
|
2014-10-07 13:09:55 -07:00
|
|
|
* https://developers.google.com/blockly/
|
2013-10-30 14:46:03 -07:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @fileoverview Utility methods.
|
2015-04-28 13:51:25 -07:00
|
|
|
* These methods are not specific to Blockly, and could be factored out into
|
|
|
|
* a JavaScript framework such as Closure.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @author fraser@google.com (Neil Fraser)
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
goog.provide('Blockly.utils');
|
|
|
|
|
2015-02-06 15:27:25 -08:00
|
|
|
goog.require('goog.events.BrowserFeature');
|
2015-02-23 16:04:18 -08:00
|
|
|
goog.require('goog.userAgent');
|
2015-02-06 15:27:25 -08:00
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a CSS class to a element.
|
|
|
|
* Similar to Closure's goog.dom.classes.add, except it handles SVG elements.
|
|
|
|
* @param {!Element} element DOM element to add class to.
|
|
|
|
* @param {string} className Name of class to add.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.addClass_ = function(element, className) {
|
|
|
|
var classes = element.getAttribute('class') || '';
|
|
|
|
if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) {
|
|
|
|
if (classes) {
|
|
|
|
classes += ' ';
|
|
|
|
}
|
|
|
|
element.setAttribute('class', classes + className);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove a CSS class from a element.
|
|
|
|
* Similar to Closure's goog.dom.classes.remove, except it handles SVG elements.
|
|
|
|
* @param {!Element} element DOM element to remove class from.
|
|
|
|
* @param {string} className Name of class to remove.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.removeClass_ = function(element, className) {
|
|
|
|
var classes = element.getAttribute('class');
|
|
|
|
if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) {
|
|
|
|
var classList = classes.split(/\s+/);
|
|
|
|
for (var i = 0; i < classList.length; i++) {
|
|
|
|
if (!classList[i] || classList[i] == className) {
|
|
|
|
classList.splice(i, 1);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (classList.length) {
|
|
|
|
element.setAttribute('class', classList.join(' '));
|
|
|
|
} else {
|
|
|
|
element.removeAttribute('class');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-01-08 13:38:40 -08:00
|
|
|
/**
|
|
|
|
* Checks if an element has the specified CSS class.
|
|
|
|
* Similar to Closure's goog.dom.classes.has, except it handles SVG elements.
|
|
|
|
* @param {!Element} element DOM element to check.
|
|
|
|
* @param {string} className Name of class to check.
|
|
|
|
* @return {boolean} True if class exists, false otherwise.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.hasClass_ = function(element, className) {
|
|
|
|
var classes = element.getAttribute('class');
|
|
|
|
return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1;
|
|
|
|
};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Bind an event to a function call.
|
2014-09-08 14:26:52 -07:00
|
|
|
* @param {!Node} node Node upon which to listen.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
|
|
|
* @param {Object} thisObject The value of 'this' in the function.
|
|
|
|
* @param {!Function} func Function to call when event is triggered.
|
|
|
|
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
|
|
|
* @private
|
|
|
|
*/
|
2014-09-08 14:26:52 -07:00
|
|
|
Blockly.bindEvent_ = function(node, name, thisObject, func) {
|
2015-04-28 13:51:25 -07:00
|
|
|
if (thisObject) {
|
|
|
|
var wrapFunc = function(e) {
|
|
|
|
func.call(thisObject, e);
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
var wrapFunc = func;
|
|
|
|
}
|
2014-09-08 14:26:52 -07:00
|
|
|
node.addEventListener(name, wrapFunc, false);
|
|
|
|
var bindData = [[node, name, wrapFunc]];
|
2013-10-30 14:46:03 -07:00
|
|
|
// Add equivalent touch event.
|
|
|
|
if (name in Blockly.bindEvent_.TOUCH_MAP) {
|
|
|
|
wrapFunc = function(e) {
|
|
|
|
// Punt on multitouch events.
|
|
|
|
if (e.changedTouches.length == 1) {
|
|
|
|
// Map the touch event's properties to the event.
|
|
|
|
var touchPoint = e.changedTouches[0];
|
|
|
|
e.clientX = touchPoint.clientX;
|
|
|
|
e.clientY = touchPoint.clientY;
|
|
|
|
}
|
2015-01-27 20:28:33 -08:00
|
|
|
func.call(thisObject, e);
|
|
|
|
// Stop the browser from scrolling/zooming the page.
|
2013-10-30 14:46:03 -07:00
|
|
|
e.preventDefault();
|
|
|
|
};
|
2014-09-08 14:26:52 -07:00
|
|
|
for (var i = 0, eventName;
|
|
|
|
eventName = Blockly.bindEvent_.TOUCH_MAP[name][i]; i++) {
|
|
|
|
node.addEventListener(eventName, wrapFunc, false);
|
|
|
|
bindData.push([node, eventName, wrapFunc]);
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
return bindData;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The TOUCH_MAP lookup dictionary specifies additional touch events to fire,
|
|
|
|
* in conjunction with mouse events.
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
Blockly.bindEvent_.TOUCH_MAP = {};
|
2015-01-04 03:42:01 +00:00
|
|
|
if (goog.events.BrowserFeature.TOUCH_ENABLED) {
|
2013-10-30 14:46:03 -07:00
|
|
|
Blockly.bindEvent_.TOUCH_MAP = {
|
2014-09-08 14:26:52 -07:00
|
|
|
'mousedown': ['touchstart'],
|
|
|
|
'mousemove': ['touchmove'],
|
|
|
|
'mouseup': ['touchend', 'touchcancel']
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unbind one or more events event from a function call.
|
|
|
|
* @param {!Array.<!Array>} bindData Opaque data from bindEvent_. This list is
|
|
|
|
* emptied during the course of calling this function.
|
|
|
|
* @return {!Function} The function call.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.unbindEvent_ = function(bindData) {
|
|
|
|
while (bindData.length) {
|
|
|
|
var bindDatum = bindData.pop();
|
2014-09-08 14:26:52 -07:00
|
|
|
var node = bindDatum[0];
|
2013-10-30 14:46:03 -07:00
|
|
|
var name = bindDatum[1];
|
|
|
|
var func = bindDatum[2];
|
2014-09-08 14:26:52 -07:00
|
|
|
node.removeEventListener(name, func, false);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
return func;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2014-09-08 14:26:52 -07:00
|
|
|
* Fire a synthetic event synchronously.
|
|
|
|
* @param {!EventTarget} node The event's target node.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @param {string} eventName Name of event (e.g. 'click').
|
|
|
|
*/
|
2014-09-08 14:26:52 -07:00
|
|
|
Blockly.fireUiEventNow = function(node, eventName) {
|
2015-01-27 15:57:45 -08:00
|
|
|
// Remove the event from the anti-duplicate database.
|
|
|
|
var list = Blockly.fireUiEvent.DB_[eventName];
|
|
|
|
if (list) {
|
|
|
|
var i = list.indexOf(node);
|
|
|
|
if (i != -1) {
|
|
|
|
list.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Fire the event in a browser-compatible way.
|
|
|
|
if (document.createEvent) {
|
2013-10-30 14:46:03 -07:00
|
|
|
// W3
|
2015-01-27 15:57:45 -08:00
|
|
|
var evt = document.createEvent('UIEvents');
|
2013-10-30 14:46:03 -07:00
|
|
|
evt.initEvent(eventName, true, true); // event type, bubbling, cancelable
|
2014-09-08 14:26:52 -07:00
|
|
|
node.dispatchEvent(evt);
|
2015-01-27 15:57:45 -08:00
|
|
|
} else if (document.createEventObject) {
|
2013-10-30 14:46:03 -07:00
|
|
|
// MSIE
|
2015-01-27 15:57:45 -08:00
|
|
|
var evt = document.createEventObject();
|
2014-09-08 14:26:52 -07:00
|
|
|
node.fireEvent('on' + eventName, evt);
|
2013-10-30 14:46:03 -07:00
|
|
|
} else {
|
|
|
|
throw 'FireEvent: No event creation mechanism.';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-09-08 14:26:52 -07:00
|
|
|
/**
|
2015-01-27 15:57:45 -08:00
|
|
|
* Fire a synthetic event asynchronously. Groups of simultaneous events (e.g.
|
|
|
|
* a tree of blocks being deleted) are merged into one event.
|
2014-09-08 14:26:52 -07:00
|
|
|
* @param {!EventTarget} node The event's target node.
|
|
|
|
* @param {string} eventName Name of event (e.g. 'click').
|
|
|
|
*/
|
|
|
|
Blockly.fireUiEvent = function(node, eventName) {
|
2015-01-27 15:57:45 -08:00
|
|
|
var list = Blockly.fireUiEvent.DB_[eventName];
|
|
|
|
if (list) {
|
|
|
|
if (list.indexOf(node) != -1) {
|
|
|
|
// This event is already scheduled to fire.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
list.push(node);
|
|
|
|
} else {
|
|
|
|
Blockly.fireUiEvent.DB_[eventName] = [node];
|
|
|
|
}
|
2014-09-08 14:26:52 -07:00
|
|
|
var fire = function() {
|
|
|
|
Blockly.fireUiEventNow(node, eventName);
|
|
|
|
};
|
|
|
|
setTimeout(fire, 0);
|
|
|
|
};
|
|
|
|
|
2015-01-27 15:57:45 -08:00
|
|
|
/**
|
|
|
|
* Database of upcoming firing event types.
|
|
|
|
* Used to fire only one event after multiple changes.
|
|
|
|
* @type {!Object}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.fireUiEvent.DB_ = {};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Don't do anything for this event, just halt propagation.
|
|
|
|
* @param {!Event} e An event.
|
|
|
|
*/
|
|
|
|
Blockly.noEvent = function(e) {
|
|
|
|
// This event has been handled. No need to bubble up to the document.
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
};
|
|
|
|
|
2015-04-28 13:51:25 -07:00
|
|
|
/**
|
|
|
|
* Is this event targeting a text input widget?
|
|
|
|
* @param {!Event} e An event.
|
|
|
|
* @return {boolean} True if text input.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.isTargetInput_ = function(e) {
|
|
|
|
return e.target.type == 'textarea' || e.target.type == 'text' ||
|
|
|
|
e.target.type == 'number' || e.target.type == 'email' ||
|
|
|
|
e.target.type == 'password' || e.target.type == 'search' ||
|
2015-06-22 15:42:25 -07:00
|
|
|
e.target.type == 'tel' || e.target.type == 'url' ||
|
|
|
|
e.target.isContentEditable;
|
2015-04-28 13:51:25 -07:00
|
|
|
};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Return the coordinates of the top-left corner of this element relative to
|
2015-04-28 13:51:25 -07:00
|
|
|
* its parent. Only for SVG elements and children (e.g. rect, g, path).
|
|
|
|
* @param {!Element} element SVG element to find the coordinates of.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @return {!Object} Object with .x and .y properties.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.getRelativeXY_ = function(element) {
|
|
|
|
var xy = {x: 0, y: 0};
|
|
|
|
// First, check for x and y attributes.
|
|
|
|
var x = element.getAttribute('x');
|
|
|
|
if (x) {
|
|
|
|
xy.x = parseInt(x, 10);
|
|
|
|
}
|
|
|
|
var y = element.getAttribute('y');
|
|
|
|
if (y) {
|
|
|
|
xy.y = parseInt(y, 10);
|
|
|
|
}
|
|
|
|
// Second, check for transform="translate(...)" attribute.
|
|
|
|
var transform = element.getAttribute('transform');
|
|
|
|
// Note that Firefox and IE (9,10) return 'translate(12)' instead of
|
|
|
|
// 'translate(12, 0)'.
|
|
|
|
// Note that IE (9,10) returns 'translate(16 8)' instead of
|
|
|
|
// 'translate(16, 8)'.
|
|
|
|
var r = transform &&
|
|
|
|
transform.match(/translate\(\s*([-\d.]+)([ ,]\s*([-\d.]+)\s*\))?/);
|
|
|
|
if (r) {
|
2015-05-27 18:54:37 -07:00
|
|
|
xy.x += parseFloat(r[1]);
|
2013-10-30 14:46:03 -07:00
|
|
|
if (r[3]) {
|
2015-05-27 18:54:37 -07:00
|
|
|
xy.y += parseFloat(r[3]);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return xy;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the absolute coordinates of the top-left corner of this element.
|
2015-04-28 13:51:25 -07:00
|
|
|
* The origin (0,0) is the top-left corner of the nearest SVG.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @param {!Element} element Element to find the coordinates of.
|
|
|
|
* @return {!Object} Object with .x and .y properties.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.getSvgXY_ = function(element) {
|
|
|
|
var x = 0;
|
|
|
|
var y = 0;
|
|
|
|
do {
|
|
|
|
// Loop through this block and every parent.
|
|
|
|
var xy = Blockly.getRelativeXY_(element);
|
|
|
|
x += xy.x;
|
|
|
|
y += xy.y;
|
|
|
|
element = element.parentNode;
|
2015-04-28 13:51:25 -07:00
|
|
|
} while (element && element.nodeName.toLowerCase() != 'svg');
|
2013-10-30 14:46:03 -07:00
|
|
|
return {x: x, y: y};
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method for creating SVG elements.
|
|
|
|
* @param {string} name Element's tag name.
|
|
|
|
* @param {!Object} attrs Dictionary of attribute names and values.
|
|
|
|
* @param {Element=} opt_parent Optional parent on which to append the element.
|
|
|
|
* @return {!SVGElement} Newly created SVG element.
|
|
|
|
*/
|
|
|
|
Blockly.createSvgElement = function(name, attrs, opt_parent) {
|
|
|
|
var e = /** @type {!SVGElement} */ (
|
|
|
|
document.createElementNS(Blockly.SVG_NS, name));
|
|
|
|
for (var key in attrs) {
|
|
|
|
e.setAttribute(key, attrs[key]);
|
|
|
|
}
|
|
|
|
// IE defines a unique attribute "runtimeStyle", it is NOT applied to
|
|
|
|
// elements created with createElementNS. However, Closure checks for IE
|
|
|
|
// and assumes the presence of the attribute and crashes.
|
|
|
|
if (document.body.runtimeStyle) { // Indicates presence of IE-only attr.
|
|
|
|
e.runtimeStyle = e.currentStyle = e.style;
|
|
|
|
}
|
|
|
|
if (opt_parent) {
|
|
|
|
opt_parent.appendChild(e);
|
|
|
|
}
|
|
|
|
return e;
|
|
|
|
};
|
|
|
|
|
2015-06-29 17:06:36 -07:00
|
|
|
/**
|
|
|
|
* Deselect any selections on the webpage.
|
|
|
|
* Chrome will select text outside the SVG when double-clicking.
|
|
|
|
* Deselect this text, so that it doesn't mess up any subsequent drag.
|
|
|
|
*/
|
|
|
|
Blockly.removeAllRanges = function() {
|
|
|
|
if (getSelection()) {
|
|
|
|
setTimeout(function() {
|
|
|
|
try {
|
|
|
|
var selection = getSelection();
|
|
|
|
if (!selection.isCollapsed) {
|
|
|
|
selection.removeAllRanges();
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// MSIE throws 'error 800a025e' here.
|
|
|
|
}
|
|
|
|
}, 0);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Is this event a right-click?
|
|
|
|
* @param {!Event} e Mouse event.
|
|
|
|
* @return {boolean} True if right-click.
|
|
|
|
*/
|
|
|
|
Blockly.isRightButton = function(e) {
|
2015-02-23 16:04:18 -08:00
|
|
|
if (e.ctrlKey && goog.userAgent.MAC) {
|
|
|
|
// Control-clicking on Mac OS X is treated as a right-click.
|
|
|
|
// WebKit on Mac OS X fails to change button to 2 (but Gecko does).
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return e.button == 2;
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the converted coordinates of the given mouse event.
|
|
|
|
* The origin (0,0) is the top-left corner of the Blockly svg.
|
|
|
|
* @param {!Event} e Mouse event.
|
2015-04-28 13:51:25 -07:00
|
|
|
* @param {!Element} svg SVG element.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @return {!Object} Object with .x and .y properties.
|
|
|
|
*/
|
2015-04-28 13:51:25 -07:00
|
|
|
Blockly.mouseToSvg = function(e, svg) {
|
2015-05-19 12:02:34 -07:00
|
|
|
var svgPoint = svg.createSVGPoint();
|
|
|
|
svgPoint.x = e.clientX;
|
|
|
|
svgPoint.y = e.clientY;
|
|
|
|
var matrix = svg.getScreenCTM();
|
|
|
|
matrix = matrix.inverse();
|
|
|
|
return svgPoint.matrixTransform(matrix);
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given an array of strings, return the length of the shortest one.
|
2014-02-01 03:00:04 -08:00
|
|
|
* @param {!Array.<string>} array Array of strings.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @return {number} Length of shortest string.
|
|
|
|
*/
|
|
|
|
Blockly.shortestStringLength = function(array) {
|
|
|
|
if (!array.length) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
var len = array[0].length;
|
|
|
|
for (var i = 1; i < array.length; i++) {
|
|
|
|
len = Math.min(len, array[i].length);
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given an array of strings, return the length of the common prefix.
|
|
|
|
* Words may not be split. Any space after a word is included in the length.
|
2014-02-01 03:00:04 -08:00
|
|
|
* @param {!Array.<string>} array Array of strings.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @param {?number} opt_shortest Length of shortest string.
|
|
|
|
* @return {number} Length of common prefix.
|
|
|
|
*/
|
|
|
|
Blockly.commonWordPrefix = function(array, opt_shortest) {
|
|
|
|
if (!array.length) {
|
|
|
|
return 0;
|
|
|
|
} else if (array.length == 1) {
|
|
|
|
return array[0].length;
|
|
|
|
}
|
|
|
|
var wordPrefix = 0;
|
|
|
|
var max = opt_shortest || Blockly.shortestStringLength(array);
|
|
|
|
for (var len = 0; len < max; len++) {
|
|
|
|
var letter = array[0][len];
|
|
|
|
for (var i = 1; i < array.length; i++) {
|
|
|
|
if (letter != array[i][len]) {
|
|
|
|
return wordPrefix;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (letter == ' ') {
|
|
|
|
wordPrefix = len + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var i = 1; i < array.length; i++) {
|
|
|
|
var letter = array[i][len];
|
|
|
|
if (letter && letter != ' ') {
|
|
|
|
return wordPrefix;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return max;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given an array of strings, return the length of the common suffix.
|
|
|
|
* Words may not be split. Any space after a word is included in the length.
|
2014-02-01 03:00:04 -08:00
|
|
|
* @param {!Array.<string>} array Array of strings.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @param {?number} opt_shortest Length of shortest string.
|
|
|
|
* @return {number} Length of common suffix.
|
|
|
|
*/
|
|
|
|
Blockly.commonWordSuffix = function(array, opt_shortest) {
|
|
|
|
if (!array.length) {
|
|
|
|
return 0;
|
|
|
|
} else if (array.length == 1) {
|
|
|
|
return array[0].length;
|
|
|
|
}
|
|
|
|
var wordPrefix = 0;
|
|
|
|
var max = opt_shortest || Blockly.shortestStringLength(array);
|
|
|
|
for (var len = 0; len < max; len++) {
|
|
|
|
var letter = array[0].substr(-len - 1, 1);
|
|
|
|
for (var i = 1; i < array.length; i++) {
|
|
|
|
if (letter != array[i].substr(-len - 1, 1)) {
|
|
|
|
return wordPrefix;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (letter == ' ') {
|
|
|
|
wordPrefix = len + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var i = 1; i < array.length; i++) {
|
|
|
|
var letter = array[i].charAt(array[i].length - len - 1);
|
|
|
|
if (letter && letter != ' ') {
|
|
|
|
return wordPrefix;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return max;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the given string a number (includes negative and decimals).
|
|
|
|
* @param {string} str Input string.
|
|
|
|
* @return {boolean} True if number, false otherwise.
|
|
|
|
*/
|
|
|
|
Blockly.isNumber = function(str) {
|
|
|
|
return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/);
|
|
|
|
};
|