From c33cdeb9d54fa798bc0c13f06b5be6abfe4b1773 Mon Sep 17 00:00:00 2001 From: yelo Date: Mon, 2 Dec 2013 15:03:37 +0800 Subject: [PATCH 01/24] add json options --- sumeru/src/external.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/sumeru/src/external.js b/sumeru/src/external.js index ebd1ad9..e142148 100644 --- a/sumeru/src/external.js +++ b/sumeru/src/external.js @@ -672,15 +672,27 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv */ function sendPostRequest(options, postData, cb){ //server + var defaultOptions; if(fw.IS_SUMERU_SERVER){ - postData = encodeURIComponent(JSON.stringify(postData)); - var defaultOptions = { - method : 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length + if (options && options.json === true) { + postData = JSON.stringify(postData); + defaultOptions = { + method : 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': postData.length } - }; + }; + } else { + postData = encodeURIComponent(JSON.stringify(postData)); + defaultOptions = { + method : 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }; + } var opts = Library.objUtils.extend(true, defaultOptions, options); From 4a8a647286b0e2c575c033cc41f850e11df84beb Mon Sep 17 00:00:00 2001 From: yelo Date: Mon, 9 Dec 2013 15:14:11 +0800 Subject: [PATCH 02/24] =?UTF-8?q?fix=20bug=EF=BC=8C=E5=8E=9F=E6=84=9F?= =?UTF-8?q?=E5=8F=B9=E5=8F=B7=E4=BC=9A=E5=AF=BC=E8=87=B4=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E4=BB=8E=E5=9C=B0=E5=9D=80=E6=A0=8F=E8=BF=9B=E5=85=A5=E6=97=B6?= =?UTF-8?q?,session.get()=E5=8F=96=E4=B8=8D=E5=88=B0=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sumeru/src/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sumeru/src/controller.js b/sumeru/src/controller.js index 0001ee1..ca2e409 100644 --- a/sumeru/src/controller.js +++ b/sumeru/src/controller.js @@ -215,7 +215,7 @@ var runnable = function(fw){ redirect:function(queryPath,paramMap,isforce){ var urlHash = queryPath; if(paramMap){ - urlHash += "!" + fw.utils.mapToUriParam(paramMap); + urlHash += "?" + fw.utils.mapToUriParam(paramMap); } fw.router.redirect(urlHash,isforce); }, From 0ffb956b1703f51088f2ef7ce2f4e0ae4cbf0bc0 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Mon, 16 Dec 2013 13:33:50 +0800 Subject: [PATCH 03/24] bugfix: package.js can work with comments in it --- package.json | 2 +- sumeru/build/build.js | 72 ++++++++++++++++++--------------- sumeru/build/buildJavascript.js | 3 ++ sumeru/server/readClientFile.js | 9 ++++- 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 27ec632..564f71c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sumeru", - "version": "0.8.19", + "version": "0.8.20", "description": "A Realtime Javascript RIA Framework For Mobile WebApp", "main": "app.js", "scripts": { diff --git a/sumeru/build/build.js b/sumeru/build/build.js index bd0be72..9d26282 100644 --- a/sumeru/build/build.js +++ b/sumeru/build/build.js @@ -69,38 +69,46 @@ var buildAppResource = function(appDir, theBinDir){ var buildAppContent = ''; var buildAppCssContent = ''; - function readPackage(path){ - var url = path + '/package.js'; - var entireContent = fs.readFileSync(url, 'utf-8'); - var contentReg = /packages\s*\(\s*(.*)\s*\)/mg; - var dirnameList = []; - - //去掉换行符、换页符、回车符等 - entireContent = entireContent.replace(/\n|\r|\t|\v|\f/g, ''); - - //取出参数, 存于dirnameList - var result = contentReg.exec(entireContent); - entireContent = result[1]; - entireContent = entireContent.replace(/'|"/mg, ''); - dirnameList = entireContent.split(','); - - dirnameList.forEach(function(dirname){ - dirname = dirname.trim(); - if(!dirname)return; - - var reg = /.js$/g, - cssReg = /.css$/g; - - var fileUrl = path + '/' + dirname; - if(reg.test(dirname)){ - buildAppContent += ';'+fs.readFileSync(fileUrl, 'utf-8'); - }else if(cssReg.test(dirname)){ - buildAppCssContent += fs.readFileSync(fileUrl, 'utf-8'); - }else{ - readPackage(fileUrl); - } - }); - }; + var readPackage = function(path) { + var url = path + '/package.js'; + var entireContent = fs.readFileSync(url, 'utf-8'); + var contentReg = /packages\s*\(\s*(.*)\s*\)/mg; + var commentReg = /\/\/.*(\n|\r)|(\/\*(.*?)\*\/)/mg; + var dirnameList = []; + + //去掉在package.js里的注释 + entireContent = entireContent.replace(commentReg, ''); + + //去掉换行符、换页符、回车符等 + entireContent = entireContent.replace(/\n|\r|\t|\v|\f/g, ''); + //取出参数, 存于dirnameList + var result = contentReg.exec(entireContent); + if (result === null) { + return; + } + entireContent = result[1]; + entireContent = entireContent.replace(/'|"/mg, ''); + dirnameList = entireContent.split(','); + + dirnameList.forEach(function(dirname) { + dirname = dirname.trim(); + if (!dirname) { + return; + } + + var reg = /.js$/g, + cssReg = /.css$/g; + + var fileUrl = path + '/' + dirname; + if (reg.test(dirname)) { + buildAppContent += ';'+fs.readFileSync(fileUrl, 'utf-8'); + } else if (cssReg.test(dirname)) { + buildAppCssContent += fs.readFileSync(fileUrl, 'utf-8'); + } else { + readPackage(fileUrl); + } + }); + } readPackage(appDir); diff --git a/sumeru/build/buildJavascript.js b/sumeru/build/buildJavascript.js index 273f71e..805ce71 100644 --- a/sumeru/build/buildJavascript.js +++ b/sumeru/build/buildJavascript.js @@ -39,6 +39,9 @@ module.exports = function(sumeruDir, dstDir) { entireContent = entireContent.replace(/\n|\r|\t|\v|\f/g, ''); //取出参数, 存于dirnameList var result = contentReg.exec(entireContent); + if (result === null) { + return; + } entireContent = result[1]; entireContent = entireContent.replace(/'|"/mg, ''); dirnameList = entireContent.split(','); diff --git a/sumeru/server/readClientFile.js b/sumeru/server/readClientFile.js index 2134e40..3b3fd2d 100644 --- a/sumeru/server/readClientFile.js +++ b/sumeru/server/readClientFile.js @@ -24,13 +24,20 @@ var evalByPackageJS = function(path,context,filename){ } var entireContent = fs.readFileSync(url, 'utf-8'); var contentReg = /packages\s*\(\s*(.*)\s*\)/mg; + var commentReg = /\/\/.*(\n|\r)|(\/\*(.*?)\*\/)/mg; var dirnameList = []; - + + //去掉在package.js里的注释 + entireContent = entireContent.replace(commentReg, ''); + //去掉换行符、换页符、回车符等 entireContent = entireContent.replace(/\n|\r|\t|\v|\f/g, ''); //取出参数, 存于dirnameList var result = contentReg.exec(entireContent); + if (result === null) { + return; + } entireContent = result[1]; entireContent = entireContent.replace(/'|"/mg, ''); dirnameList = entireContent.split(','); From c2a4d752bd19d4f6303a7a5bff4820a54a343151 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Thu, 19 Dec 2013 10:52:09 +0800 Subject: [PATCH 04/24] bugfix: scalar & vector default value of model --- sumeru/src/model.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/sumeru/src/model.js b/sumeru/src/model.js index b33117b..42768dd 100644 --- a/sumeru/src/model.js +++ b/sumeru/src/model.js @@ -556,17 +556,30 @@ var runnable = function(fw){ if(typeof modelDef.config != 'undefined'){ var fields = modelDef.config.fields || [], - oneField; + oneField, defaultValue; - //FIXME 理论上 我只准备在model上保留_fieldsMap了 - //newModelTemp.fields = fields; for(var i = 0, l = fields.length; i < l; i++){ oneField = fields[i]; - newModelTemp._fieldsMap[oneField['name']] = oneField; + if(oneField['type'] == 'array'){ + if(oneField['defaultValue']){ + defaultValue = oneField['defaultValue']; + oneField['defaultValue'] = Library.objUtils.isArray(defaultValue)?defaultValue:[]; + }else{ + oneField['defaultValue'] = []; + } + } else if(oneField['type'] == 'object'){ + if(oneField['defaultValue']){ + defaultValue = oneField['defaultValue']; + oneField['defaultValue'] = Library.objUtils.isObject(defaultValue)?defaultValue:{}; + }else{ + oneField['defaultValue'] = {}; + } + } + newModelTemp._fieldsMap[oneField['name']] = oneField; //FIXME 这里还要把validation的函数生成出来。还不确定是不是要这样干。 } @@ -632,10 +645,9 @@ var runnable = function(fw){ //解析now() newModel[oneField['name']] = fw.utils.getTimeStamp(); } else if(oneField['type'] == 'array'){ - //FIXME array的默认值不应该是eval出来的,标记,以后要改 - newModel[oneField['name']] = eval(oneField['defaultValue']) || []; + newModel[oneField['name']] = fw.utils.deepClone(oneField['defaultValue']) || []; } else if(oneField['type'] == 'object'){ - newModel[oneField['name']] = fw.utils.parseJSON(oneField['defaultValue']) || []; + newModel[oneField['name']] = fw.utils.deepClone(oneField['defaultValue']) || {}; } else { newModel[oneField['name']] = oneField['defaultValue'] || undefined; //其实后一个undefined不用写,只是为了更易读 } From 213156a9625a15f7b935cc549e3df74d1f351cee Mon Sep 17 00:00:00 2001 From: brandnewera Date: Wed, 8 Jan 2014 19:51:47 +0800 Subject: [PATCH 05/24] cluster support and a major update of touch --- sumeru/library/touch.js | 2116 ++++++++++++++++----------------- sumeru/server/cluster.js | 27 +- sumeru/src/frameworkConfig.js | 21 +- 3 files changed, 1055 insertions(+), 1109 deletions(-) diff --git a/sumeru/library/touch.js b/sumeru/library/touch.js index b32c0b6..6efd830 100644 --- a/sumeru/library/touch.js +++ b/sumeru/library/touch.js @@ -1,1098 +1,1022 @@ -var touch = Library.touch = sumeru.Library.create(function(exports){ - //工具类接口 - var isPlainObject = function(obj) { - if (!obj || type(obj) !== "object" || obj.nodeType || obj === obj.window ) { - return false; - } - var hasOwnProperty = Object.prototype.hasOwnProperty; - // Not own constructor property must be Object - if (obj.constructor && !hasOwnProperty.call(obj, "constructor") && - !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) { - return false; - } - - var key; - for (key in obj) {} - - return key === undefined || hasOwnProperty.call(obj, key); - }; - - var typeMap = {}; - "Boolean Number String Function Array Date RegExp Object".split(' ').forEach(function(item){ - typeMap["[object " + item + "]"] = item.toLowerCase(); - }); - - var type = function(obj){ - return obj == null ? - 'null' : - typeMap[ Object.prototype.toString.call(obj)] || "object"; - }, - - isObject = function(obj){ - return type(obj) === 'object'; - }, - - isArray = function(obj){ - return type(obj) === 'array'; - }, - isFunction = function(obj){ - return type(obj) === 'function'; - }, - isString = function(obj){ - return type(obj) === 'string'; - }, - isBoolean = function(obj){ - return type(obj) === 'bollean'; - }, - isNumber = function(obj){ - return type(obj) === 'number'; - }, - isDate = function(obj){ - return type(obj) === 'date'; - }, - isRegExp = function(obj){ - return type(obj) === 'regexp'; - }, - - extend = function(){ - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - //Handle a deep copy situation - if(typeof target === "boolean") { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - //Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction(target) ) { - target = {}; - } - - // if only one argument is passed, do nothing - if (length === i) { - return target; - } - - for ( ; i < length; i++) { - // Only deal with non-null/undefined values - if ((options = arguments[i]) != null) { - // Extend the base object - for (name in options) { - src = target[name]; - copy = options[name]; - - // Prevent never-ending loop - if (target === copy) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)) ) ) { - if (copyIsArray) { - copyIsArray = false; - clone = src && isArray(src) ? src : []; - - } else { - clone = src && isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[name] = extend(deep, clone, copy); - - // Don't bring in undefined values - } else if (copy !== undefined) { - target[ name ] = copy; - } - } - } - } - - //Return the modified object - return target; - }; - - var _utils = (function(){ - var randomStr = '1234567890abcdefghijklmno'+ - 'pqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ'; - var randomStrLen = randomStr.length - 1; - return { - query: function(selector){ - return document.querySelectorAll(selector); - }, - randomStr: function(max){ - var rv = ''; - max = (typeof max === 'undefined') ? 8 : max; - - for(var i=0; i < max; i++){ - rv += randomStr[Math.floor(Math.random() * (randomStrLen + 1))]; - } - return rv; - }, - //获取某个元素的Data ID, 如果该元素不存在该id, 则随机生成一个id, 并分配给 - //该元素的uid attribute。 - //return {uid} - getElUID: function(el){ - var uid = el.getAttribute('data-uid'); - if(!uid){ - uid = this.randomStr(18); - el.setAttribute('data-uid', uid); - } - return uid; - }, - addEvents: function(el, types, callback){ - types = types ? types.split(" ") : []; - for(var i= 0,len=types.length; i 0){ - liveEls = Array.prototype.slice.apply(liveEls, [0]); - liveEls.forEach(function(le){ - if(le.contains(target)){ - execHandler(handlers[i], le, ops, paras); - } - }); - } - }else{ - execHandler(handlers[i], el, ops, paras); - } - } - }, - off: function(){ - if(typeof arguments[0] === 'undefined'){ - return; - } - - var uid = _utils.getElUID(arguments[0]); - - //解除live的绑定 - if(typeof arguments[3] === 'string'){ - var handlers = []; - try{ - handlers = eventHanlderMap[uid][arguments[1]]['handler']; - }catch(e){ - handlers = []; - } - if(arguments[2]){ - var index = handlers.indexOf(arguments[2]); - if(index > -1){ - var h = handlers.splice(index, 1); - delete h.options; - } - }else{ - for( var i=0; i < handlers.length; i++){ - var ops = handlers[i].options; - if(ops && ops.__binding_live - && ops.__binding_live === arguments[3]){ - var h = handlers.splice(i, 1); - delete h.options; - i--; - } - } - } - return false; - } - - //解除on绑定 - if(arguments.length == 1){ - eventHanlderMap[uid] = null; - }else if(arguments.length === 2){ - eventHanlderMap[uid] && (eventHanlderMap[uid][arguments[1]] = null); - }else{ - if( eventHanlderMap[uid] && - eventHanlderMap[uid][arguments[1]] && - eventHanlderMap[uid][arguments[1]]['handler'] ){ - var handlers = eventHanlderMap[uid][arguments[1]]['handler']; - var index = handlers.indexOf(arguments[2]); - if(index > -1){ - var h = handlers.splice(index, 1); - delete h.options; - } - } - } - }, - add: function(el, type, hanlder, options){ - var uid = _utils.getElUID(el); - eventHanlderMap[uid] = eventHanlderMap[uid] || {}; - eventHanlderMap[uid][type] = eventHanlderMap[uid][type] || {}; - - eventHanlderMap[uid][type]['handler'] = eventHanlderMap[uid][type]['handler'] || []; - !options || (hanlder.options = options); - typeof hanlder === 'function' ? eventHanlderMap[uid][type]['handler'].push(hanlder) : null; - }, - hasHandler: function(el, type){ - try{ - var uid = _utils.getElUID(el); - var handlers = eventHanlderMap[uid][type]['handler']; - return handlers && handlers.length > 0 ? true : false; - }catch(e){ - return false; - } - }, - isEmpty: function(el){ - var uid = _utils.getElUID(el); - var thisEvent = eventHanlderMap[uid]; - for(var type in thisEvent){ - if(thisEvent[type] && - thisEvent[type]['handler'] && - thisEvent[type]['handler']['length'] !== 0){ - return false; - } - } - return true; - } - } - })(); - - var config = { - tap: true, - doubleTap: true, - tapMaxDistance: 10, - hold: true, - holdTime: 650,//ms - maxDoubleTapInterval: 300, - - //swipe - swipe: true, - swipeTime: 300, - swipeMinDistance: 18, - swipeFactor: 5, - - drag: true, - //pinch config, minScaleRate与minRotationAngle先指定为0 - pinch: true, - minScaleRate: 0, - minRotationAngle: 0 - }; - /* - *@constructor SmrEvent: one element, one SmrEvent obj. 这个对象 - *封装了旋转、pinchin、pinchout、swipe、tap等事件。 - */ - var SmrEvent = (function(){ - var _hasTouch = ('ontouchstart' in window); - - /** - * 获取事件的位置信息 - * @param ev, 原生事件对象 - * @return array [{ x: int, y: int }] - */ - function getPosOfEvent(ev){ - //多指触摸, 返回多个手势位置信息 - if(_hasTouch) { - var pos = []; - var src = null; - - for(var t=0, len=ev.touches.length; t= 2 && pmove.length >= 2) { - var disStart = getDistance(pstart[1], pstart[0]); - var disEnd = getDistance(pmove[1], pmove[0]); - - return disEnd / disStart; - } - return 1; - }; - - //return 角度,范围为{-180-0,0-180}, 用来识别swipe方向。 - function getAngle(p1, p2){ - return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI; - }; - //return 角度, 范围在{0-180}, 用来识别旋转角度 - function _getAngle180(p1, p2){ - var agl = Math.atan((p2.y - p1.y) * -1 / (p2.x - p1.x)) * (180 / Math.PI); - return (agl < 0 ? (agl + 180) : agl); - }; - - //根据角度计算方位 - //@para agl {int} 是调用getAngle获取的。 - function getDirectionFromAngle(agl) { - var directions = { - up: agl < -45 && agl > -135, - down: agl >= 45 && agl < 135, - left: agl >= 135 || agl <= -135, - right: agl >= -45 && agl <= 45 - }; - for(var key in directions){ - if(directions[key])return key; - } - return null; - }; - - - //取消事件的默认行为和冒泡 - function preventDefault(ev){ - ev.preventDefault(); - ev.stopPropagation(); - }; - - function getXYByElement(el){ - var left =0, top = 0; - - while (el.offsetParent) { - left += el.offsetLeft; - top += el.offsetTop; - el = el.offsetParent; - } - return { left: left, top: top }; - }; - - return function(el){ - var me = this; - var pos = {start : null, move: null, end: null}; - var startTime = 0; - var fingers = 0; - var startEvent = null; - var moveEvent = null; - var endEvent = null; - var startSwiping = false; - var startPinch = false; - var startDrag = false; - - var __offset = {}; - var __touchStart = false; - var __holdTimer = null; - var __tapped = false; - var __lastTapEndTime = null; - - function triggerEvent(name, paras){ - if(typeof me["on"+ name] === 'function'){ - me["on"+ name](paras); - } - }; - - function reset(){ - startEvent = moveEvent = endEvent = null; - __tapped = __touchStart = startSwiping = startPinch = false; - startDrag = false; - pos = {}; - __rotation_single_finger = false; - }; - - function isTouchStart(ev){ - return (ev.type === 'touchstart' || ev.type === 'mousedown'); - }; - function isTouchMove(ev){ - return (ev.type === 'touchmove' || ev.type === 'mousemove'); - }; - function isTouchEnd(ev){ - return (ev.type === 'touchend' || ev.type === 'mouseup' || ev.type === 'touchcancel'); - }; - - function triggerCustomEvent(el, customEventName, eventObj, copy){ - if(eventManager.hasHandler(el, customEventName)){ - copy = typeof copy == 'undefind' ? true : copy; - - if(copy){ - eventObj = _utils.deepCopy(eventObj); - } - eventObj.type = customEventName; - eventObj.startRotate = function(){ - me.startRotate(); - } - triggerEvent(customEventName, eventObj); - } - }; - - var __scale_last_rate = 1; - var __rotation_single_finger = false; - var __rotation_single_start = [];//元素坐标中心位置 - var __initial_angle = 0; - var __rotation = 0; - - var __prev_tapped_end_time = 0; - var __prev_tapped_pos = null; - - var gestures = { - _getAngleDiff: function(currentPos){ - var diff = parseInt(__initial_angle - _getAngle180(currentPos[0], currentPos[1]), 10); - var count = 0; - - while(Math.abs(diff - __rotation) > 90 && count++ < 50) { - if(__rotation < 0){ - diff -= 180; - }else{ - diff += 180; - } - } - __rotation = parseInt(diff, 10); - return __rotation; - }, - pinch: function(ev){ - if(config.pinch){ - //touchend进入此时的getFinger(ev) < 2 - if(!__touchStart)return; - if(getFingers(ev) < 2){ - if(!isTouchEnd(ev))return; - } - var em = eventManager; - var scale = calScale(pos.start, pos.move); - var rotation = this._getAngleDiff(pos.move); - var eventObj = { - type: '', - originEvent: ev, - scale: scale, - rotation: rotation, - direction: (rotation > 0 ? 'right' : 'left'), - fingersCount: getFingers(ev), - startRotate: function(){ - me.startRotate(); - } - }; - if(!startPinch){ - startPinch = true; - eventObj.fingerStatus = "start"; - triggerCustomEvent(el, smrEventList.PINCH_START, eventObj); - }else if(isTouchMove(ev)){ - eventObj.fingerStatus = "move"; - }else if(isTouchEnd(ev)){ - eventObj.fingerStatus = "end"; - triggerCustomEvent(el, smrEventList.PINCH_END, eventObj); - } - - triggerCustomEvent(el, smrEventList.PINCH, eventObj); - - if(Math.abs(1-scale) > config.minScaleRate){ - var scaleEv = _utils.deepCopy(eventObj); - - //手势放大, 触发pinchout事件 - var scale_diff = 0.00000000001;//防止touchend的scale与__scale_last_rate相等,不触发事件的情况。 - if(scale > __scale_last_rate){ - __scale_last_rate = scale - scale_diff; - triggerCustomEvent(el, smrEventList.PINCH_OUT, scaleEv, false); - }//手势缩小,触发pinchin事件 - else if(scale < __scale_last_rate){ - __scale_last_rate = scale + scale_diff; - triggerCustomEvent(el, smrEventList.PINCH_IN, scaleEv, false); - } - - if(isTouchEnd(ev)){ - __scale_last_rate = 1; - } - } - - if(Math.abs(rotation) > config.minRotationAngle){ - var rotationEv = _utils.deepCopy(eventObj), eventType; - - eventType = rotation > 0 ? smrEventList.ROTATION_RIGHT : smrEventList.ROTATION_LEFT; - triggerCustomEvent(el, eventType, rotationEv, false); - triggerCustomEvent(el, smrEventList.ROTATION, eventObj); - } - - //preventDefault(ev); - } - }, - rotateSingleFinger: function(ev){ - if(__rotation_single_finger && getFingers(ev) < 2){ - if(!pos.move)return; - if(__rotation_single_start.length < 2){ - var docOff = getXYByElement(el); - - __rotation_single_start = [{ - x: docOff.left + el.offsetWidth/2, - y: docOff.top + el.offsetHeight/2 - }, pos.move[0]]; - __initial_angle = parseInt(_getAngle180(__rotation_single_start[0], __rotation_single_start[1]), 10); - } - var move = [__rotation_single_start[0], pos.move[0]]; - var rotation = this._getAngleDiff(move); - var eventObj = { - type: '', - originEvent: ev, - rotation: rotation, - direction: (rotation > 0 ? 'right' : 'left'), - fingersCount: getFingers(ev) - }; - - if(isTouchMove(ev)){ - eventObj.fingerStatus = "move"; - }else if(isTouchEnd(ev) || ev.type === 'mouseout'){ - eventObj.fingerStatus = "end"; - triggerCustomEvent(el, smrEventList.PINCH_END, eventObj); - } - - eventType = rotation > 0 ? smrEventList.ROTATION_RIGHT : smrEventList.ROTATION_LEFT; - triggerCustomEvent(el, eventType, eventObj); - triggerCustomEvent(el, smrEventList.ROTATION, eventObj); - } - }, - swipe: function(ev){ - //目前swipe只存在一个手势上 - if(!__touchStart || !pos.move || getFingers(ev) > 1){ - return; - } - - var em = eventManager; - var now = Date.now(); - var touchTime = now - startTime; - var distance = getDistance(pos.start[0], pos.move[0]); - var position = { - x: pos.move[0].x - __offset.left, - y: pos.move[0].y - __offset.top - }; - var angle = getAngle(pos.start[0], pos.move[0]); - var direction = getDirectionFromAngle(angle); - var touchSecond = touchTime/1000; - var factor = ((10 - config.swipeFactor) * 10 * touchSecond * touchSecond); - var eventObj = { - type: smrEventList.SWIPE,//DEFAULT: smrEventList.SWIPE event. - originEvent: ev, - position: position, - direction: direction, - distance: distance, - distanceX: pos.move[0].x - pos.start[0].x, - distanceY: pos.move[0].y - pos.start[0].y, - angle: angle, - duration: touchTime, - fingersCount: getFingers(ev), - factor: factor - }; - if(config.swipe){ - var swipeTo = function(){ - var elt = smrEventList; - switch(direction){ - case 'up': triggerCustomEvent(el, elt.SWIPE_UP, eventObj);break; - case 'down': triggerCustomEvent(el, elt.SWIPE_DOWN, eventObj);break; - case 'left': triggerCustomEvent(el, elt.SWIPE_LEFT, eventObj);break; - case 'right': triggerCustomEvent(el, elt.SWIPE_RIGHT, eventObj);break; - } - }; - - if(!startSwiping){ - eventObj.fingerStatus = eventObj.swipe = 'start'; - startSwiping = true; - triggerCustomEvent(el, smrEventList.SWIPE_START, eventObj); - }else if(isTouchMove(ev)){ - eventObj.fingerStatus = eventObj.swipe = 'move'; - triggerCustomEvent(el, smrEventList.SWIPING, eventObj); - - if(touchTime > config.swipeTime && - touchTime < config.swipeTime + 50 && - distance > config.swipeMinDistance){ - swipeTo(); - triggerCustomEvent(el, smrEventList.SWIPE, eventObj, false); - } - }else if(isTouchEnd(ev) || ev.type === 'mouseout'){ - eventObj.fingerStatus = eventObj.swipe = 'end'; - triggerCustomEvent(el, smrEventList.SWIPE_END, eventObj); - - if(config.swipeTime > touchTime && - distance > config.swipeMinDistance){ - swipeTo(); - triggerCustomEvent(el, smrEventList.SWIPE, eventObj, false); - } - } - } - - if(config.drag){ - if(!startDrag){ - eventObj.fingerStatus = eventObj.swipe = 'start'; - startDrag = true; - }else if(isTouchMove(ev)){ - eventObj.fingerStatus = eventObj.swipe = 'move'; - }else if(isTouchEnd(ev)){ - eventObj.fingerStatus = eventObj.swipe = 'end'; - } - triggerCustomEvent(el, smrEventList.DRAG, eventObj); - } - }, - tap: function(ev){ - if(config.tap){ - var em = eventManager; - var now = Date.now(); - var touchTime = now - startTime; - var distance = getDistance(pos.start[0], pos.move ? pos.move[0]:pos.start[0]); - - clearTimeout(__holdTimer);//去除hold事件 - - var isDoubleTap = (function(){ - if(__prev_tapped_pos && config.doubleTap && - (startTime - __prev_tapped_end_time) < config.maxDoubleTapInterval){ - var doubleDis = getDistance(__prev_tapped_pos, pos.start[0]); - if(doubleDis < 16)return true; - } - return false; - })(); - - if(isDoubleTap){ - triggerEvent(smrEventList.DOUBLE_TAP, { - type: smrEventList.DOUBLE_TAP, - originEvent : ev, - position : pos.start[0] - }); - return; - } - - if(config.tapMaxDistance < distance)return; - - if(config.holdTime > touchTime && getFingers(ev) <= 1){ - //clearTimeout在ios上有时不work(alert引起的), 先用__tapped顶一下 - __tapped = true; - __prev_tapped_end_time = now; - __prev_tapped_pos = pos.start[0]; - - if(em.hasHandler(el, smrEventList.TAP)){ - triggerEvent(smrEventList.TAP, { - type: smrEventList.TAP, - originEvent : ev, - fingersCount: getFingers(ev), - position : pos.start[0] - }); - } - if(em.hasHandler(el, smrEventList.CLICK)){ - triggerEvent(smrEventList.CLICK, { - type: smrEventList.CLICK, - originEvent : ev, - fingersCount: getFingers(ev), - position : pos.start[0] - }); - } - } - } - }, - hold: function(ev){ - if(config.hold) { - clearTimeout(__holdTimer); - - __holdTimer = setTimeout(function() { - if(!pos.start)return; - var distance = getDistance(pos.start[0], pos.move ? pos.move[0]:pos.start[0]); - if(config.tapMaxDistance < distance)return; - - if(!__tapped){ - triggerEvent("hold", { - type: 'hold', - originEvent: ev, - fingersCount: getFingers(ev), - position: pos.start[0] - }); - } - }, config.holdTime); - } - } - }; - - var handlerOriginEvent = function(ev){ - switch(ev.type){ - case 'touchstart': - case 'mousedown': - __rotation_single_finger = false; - __rotation_single_start = []; - triggerCustomEvent(el, ev.type, { - originEvent: ev - }); - - __touchStart = true; - if(!pos.start || pos.start.length < 2){ - pos.start = getPosOfEvent(ev); - } - if(getFingers(ev) >= 2){ - __initial_angle = parseInt(_getAngle180(pos.start[0], pos.start[1]), 10); - } - - startTime = Date.now(); - startEvent = ev; - __offset = {}; - - //来自jquery offset的写法: https://github.com/jquery/jquery/blob/master/src/offset.js - var box = el.getBoundingClientRect(); - var docEl = document.documentElement; - __offset = { - top: box.top + ( window.pageYOffset || docEl.scrollTop ) - ( docEl.clientTop || 0 ), - left: box.left + ( window.pageXOffset || docEl.scrollLeft ) - ( docEl.clientLeft || 0 ) - }; - - gestures.hold(ev); - break; - case 'touchmove': - case 'mousemove': - triggerCustomEvent(el, ev.type, { - originEvent: ev - }); - if(!__touchStart || !pos.start)return; - pos.move = getPosOfEvent(ev); - - if(getFingers(ev) >= 2){ - gestures.pinch(ev); - }else if(__rotation_single_finger){ - gestures.rotateSingleFinger(ev); - }else{ - gestures.swipe(ev); - } - break; - case 'touchend': - case 'touchcancel': - case 'mouseup': - case 'mouseout': - triggerCustomEvent(el, ev.type, { - originEvent: ev - }); - if(!__touchStart)return; - endEvent = ev; - - if(startPinch){ - gestures.pinch(ev); - }else if(__rotation_single_finger){ - gestures.rotateSingleFinger(ev); - }else{ - if(startSwiping){ - gestures.swipe(ev); - } - } - gestures.tap(ev); - - reset(); - __initial_angle = 0; - __rotation = 0; - if(ev.touches && ev.touches.length === 1){ - __touchStart = true; - __rotation_single_finger = true; - } - break; - } - }; - - - var eventNames = _hasTouch ? 'touchstart touchmove touchend touchcancel': - 'mouseup mousedown mousemove mouseout'; - _utils.addEvents(el, eventNames, handlerOriginEvent); - - this.tearDown = function(){ - _utils.removeEvents(el, eventNames, handlerOriginEvent); - } - - this.startRotate = function(){ - __rotation_single_finger = true; - } - } - })(); - - /* - *@param el {Element} - *@param types {String} 事件类型, 可以空格分割多个事件。 - *@param handler {Function} 事件处理函数 - *@param options {Object} - *{ - * swipeFactor: int (1-10) 加速度因子, 值越大速率越大。 - * interval: 0 //单位ms, 用来对handler的回调进行切片。 - *} - */ - var _on = function(){ - if(typeof arguments.length < 3)throw 'Please specify complete argments'; - var element = typeof arguments[0] === 'string' ? - _utils.query(arguments[0]) : [arguments[0]]; - var types = arguments[1].split(' '); - var handler = arguments[arguments.length - 1]; - var options = arguments.length > 3 ? arguments[arguments.length - 2] : undefined; - - element = Array.prototype.slice.apply(element, [0]); - element.forEach(function(el){ - for(var i=0; i < types.length; i++){ - var eventName = mapEvent(types[i]); - if(event.special[eventName]){ - eventManager.add(el, eventName, handler, options); - event.special[eventName].setUp.apply(this, [el, eventName]); - }else{ - _utils.addEvents(el, eventName, handler); - } - } - }); - }; - - var _live = function(){ - if(arguments.length < 3){ - throw new Error('wrong argument'); - } - //如果指定的第一个元素不为选择器, 则交给on处理。 - if(typeof arguments[0] != 'string'){ - _on.apply(exports, arguments); - return; - } - var options = arguments.length > 3 ? arguments[arguments.length - 2] : {}; - var types = arguments[1]; - var handler = arguments[arguments.length - 1]; - options.__binding_live = arguments[0]; - _on.apply(exports, [document.body, types, options, handler]); - }; - - - var _off = function(selector, types, handler, liveSelector){ - if(typeof selector === 'undefined'){ - throw 'Please specify the selector.'; - } - - selector = typeof selector === 'string' ? _utils.query(selector) : [selector]; - selector = Array.prototype.slice.apply(selector, [0]); - - if(!types){ - eventManager.off(selector); - _utils.removeEvents(selector); - }else{ - types = types.split(' '); - selector.forEach(function(el){ - types.forEach(function(type, index){ - var args = [el, mapEvent(type)]; - handler ? args.push(handler) : null; - liveSelector ? args.push(liveSelector) : null; - - if(event.special[type]){ - eventManager.off.apply(eventManager, args); - if(eventManager.isEmpty(el, type)){ - event.special[type].tearDown(el); - } - }else{ - _utils.removeEvents.apply(this, args); - } - }); - }); - } - }; - - var _die = function(selector, types, handler){ - _off(document.body, types, handler, selector); - } - - var touchEvent = { - 'touchstart': 'mousedown', - 'touchmove': 'mousemove', - 'touchend': 'mouseup' - }; - - var mapEvent = function(eventName){ - if(!('ontouchstart' in window ) && touchEvent[eventName]){ - return touchEvent[eventName]; - } - return eventName; - }; - /** - *全局可配置的参数: - *{ - * tap: true, //tap类事件开关, 默认为true - * doubleTap: true, //doubleTap事件开关, 默认为true - * hold: true,//hold事件开关, 默认为true - * holdTime: 650,//hold时间长度 - * swipe: true,//swipe事件开关 - * swipeTime: 300,//触发swipe事件的最大时长 - * swipeMinDistance: 18,//swipe移动最小距离 - * swipeFactor: 5,//加速因子, 值越大变化速率越快 - * pinch: true,//pinch类事件开关 - *} - */ - var _config = function(customConfig){ - if(typeof customConfig !== 'object')return; - config = extend(config, customConfig); - }; - - function touchSetup(el, type){ - var touchEv = _touchData.get(el, '_touchEv'); - if(!touchEv){ - touchEv = new SmrEvent(el); - _touchData.set(el, '_touchEv', touchEv); - } - - touchEv['on'+type] = function(paras){ - eventManager.trigger(el, type, paras); - } - }; - - function tearDown(el){ - var touchEv = _touchData.get(el, '_touchEv'); - if(!touchEv)return; - _touchData.set(el, '_touchEv', null); - touchEv.tearDown(); - } - var smrSpecical = { setUp: touchSetup, tearDown: tearDown}; - var smrEventList = { - TOUCH_START: 'touchstart', - TOUCH_MOVE: 'touchmove', - TOUCH_END: 'touchend', - TOUCH_CANCEL: 'touchcancel', - - MOUSE_DOWN: 'mousedown', - MOUSE_MOVE: 'mousemove', - MOUSE_UP: 'mouseup', - - CLICK: 'click', - - //PINCH TYPE EVENT NAMES - PINCH_START: 'pinchstart', - PINCH_END: 'pinchend', - PINCH: 'pinch', - PINCH_IN: 'pinchin', - PINCH_OUT: 'pinchout', - - ROTATION_LEFT: 'rotateleft', - ROTATION_RIGHT: 'rotateright', - ROTATION: 'rotate', - - SWIPE_START: 'swipestart', - SWIPING: 'swiping', - SWIPE_END: 'swipeend', - SWIPE_LEFT: 'swipeleft', - SWIPE_RIGHT: 'swiperight', - SWIPE_UP: 'swipeup', - SWIPE_DOWN: 'swipedown', - SWIPE: 'swipe', - - DRAG: 'drag', - - //HOLD AND TAP - HOLD: 'hold', - TAP: 'tap', - DOUBLE_TAP: 'doubletap' - }; - - var event = {}; - event.special = {}; - Object.keys(smrEventList).forEach(function(key){ - event.special[smrEventList[key]] = smrSpecical; - }); - - exports.live = _live; - exports.die = _die; - exports.on = _on; - exports.off = _off; - exports.config = _config; +//version 0.2.10 +var touch = touch || {}; + +(function(doc, exports) { + 'use strict'; + var os = (function() { + var navigator = window.navigator, + userAgent = navigator.userAgent, + android = userAgent.match(/(Android)[\s\/]+([\d\.]+)/), + ios = userAgent.match(/(iPad|iPhone|iPod)\s+OS\s([\d_\.]+)/), + wp = userAgent.match(/(Windows\s+Phone)\s([\d\.]+)/), + isWebkit = /WebKit\/[\d.]+/i.test(userAgent), + isSafari = ios ? (navigator.standalone ? isWebkit: (/Safari/i.test(userAgent) && !/CriOS/i.test(userAgent) && !/MQQBrowser/i.test(userAgent))) : false, + os = {}; + + if (android) { + os.android = true; + os.version = android[2]; + } + if (ios) { + os.ios = true; + os.version = ios[2].replace(/_/g, '.'); + os.ios7 = /^7/.test(os.version); + if (ios[1] === 'iPad') { + os.ipad = true; + } else if (ios[1] === 'iPhone') { + os.iphone = true; + os.iphone5 = screen.height == 568; + } else if (ios[1] === 'iPod') { + os.ipod = true; + } + } + if (wp) { + os.wp = true; + os.version = wp[2]; + os.wp8 = /^8/.test(os.version); + } + if (isWebkit) { + os.webkit = true; + } + if (isSafari) { + os.safari = true; + } + + return os; + })(); + + var PCevts = { + 'touchstart': 'mousedown', + 'touchmove': 'mousemove', + 'touchend': 'mouseup', + 'touchcancel': 'mouseout' + }; + + var utils = { + getType: function(obj) { + return Object.prototype.toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase(); + }, + getSelector: function(el) { + if (el.id) { + return "#" + el.id; + } + if (el.className) { + var cns = el.className.split(/\s+/); + return "." + cns.join("."); + } else { + return el.tagName.toLowerCase(); + } + }, + matchSelector: function(target, selector) { + return target.webkitMatchesSelector(selector); + }, + getEventListeners: function(el) { + return el.listeners; + }, + getPCevts: function(evt) { + return PCevts[evt] || evt; + }, + forceReflow: function() { + var domTreeOpDiv = document.getElementById("domTreeOp"); + if (!domTreeOpDiv) { + domTreeOpDiv = document.createElement("div"); + domTreeOpDiv.id = "domTreeOp"; + document.body.appendChild(domTreeOpDiv); + } + var parentNode = domTreeOpDiv.parentNode; + var nextSibling = domTreeOpDiv.nextSibling; + parentNode.removeChild(domTreeOpDiv); + parentNode.insertBefore(domTreeOpDiv, nextSibling); + }, + simpleClone: function(obj){ + return JSON.parse(JSON.stringify(obj)); + } + }; + + /** 底层事件绑定/代理支持 */ + var proxyid = 0; + var proxies = []; + var _trigger = function(el, evt, detail) { + + detail = detail || {}; + var e, + opt = { + bubbles: true, + cancelable: true, + detail: detail + }; + + if (typeof CustomEvent !== 'undefined') { + e = new CustomEvent(evt, opt); + if (el) { + el.dispatchEvent(e); + } + } else { + e = document.createEvent("CustomEvent"); + e.initCustomEvent(evt, true, true, detail); + if (el) { + el.dispatchEvent(e); + } + } + }; + + /** + * {DOM} element + * {String} eventName + * {Function} handler + */ + var _bind = function(el, evt, handler) { + el.listeners = el.listeners || {}; + + if (!el.listeners[evt]) { + el.listeners[evt] = [handler]; + } else { + el.listeners[evt].push(handler); + } + var proxy = function(e) { + if (os.ios7) { + utils.forceReflow(); + } + e.originEvent = e; + e.startRotate = function() { + __rotation_single_finger = true; + }; + for (var p in e.detail) { + if(p !== 'type'){ + e[p] = e.detail[p]; + } + } + handler.call(e.target, e); + }; + + handler.proxy = handler.proxy || {}; + if (!handler.proxy[evt]) { + handler.proxy[evt] = [proxyid++]; + } else { + handler.proxy[evt].push(proxyid++); + } + proxies.push(proxy); + + if( el.addEventListener){ el.addEventListener(evt, proxy, false); } + }; + + /** + * {DOM} element + * {String} eventName + * {Function} the same handler of _bind + */ + var _unbind = function(el, evt, handler) { + if (!handler) { + var handlers = el.listeners[evt]; + if (handlers && handlers.length) { + handlers.forEach(function(handler) { + el.removeEventListener(evt, handler, false); + }); + } + } else { + var proxyids = handler.proxy[evt]; + if (proxyids && proxyids.length) { + proxyids.forEach(function(proxyid) { + if (el.removeEventListener) { + el.removeEventListener(evt, proxies[proxyid], false); + } + }); + } + } + }; + + /** + * {DOM} delegate element + * {String} eventName + * {String} selector of sub elements + * {Function} handler + */ + var _delegate = function(el, evt, sel, handler) { + var proxy = function(e) { + var target; + e.originEvent = e; + e.startRotate = function() { + __rotation_single_finger = true; + }; + for (var p in e.detail) { + if(p !== 'type'){ + e[p] = e.detail[p]; + } + } + var integrateSelector = utils.getSelector(el) + " " + sel; + var match = utils.matchSelector(e.target, integrateSelector); + var ischild = utils.matchSelector(e.target, integrateSelector + " " + e.target.nodeName); + if (!match && ischild) { + if (os.ios7) { + utils.forceReflow(); + } + target = e.target; + while (!utils.matchSelector(target, integrateSelector)) { + target = target.parentNode; + } + handler.call(target, e); + } else { + if (os.ios7) { + utils.forceReflow(); + } + if (match || ischild) { + handler.call(e.target, e); + } + } + }; + + handler.proxy = handler.proxy || {}; + if (!handler.proxy[evt]) { + handler.proxy[evt] = [proxyid++]; + } else { + handler.proxy[evt].push(proxyid++); + } + proxies.push(proxy); + + el.listeners = el.listeners || {}; + if (!el.listeners[evt]) { + el.listeners[evt] = [proxy]; + } else { + el.listeners[evt].push(proxy); + } + if(el.addEventListener){el.addEventListener(evt, proxy, false);} + }; + + /** + * {DOM} delegate element + * {String} eventName + * {String} selector of sub elements + * {Function} the same handler of _on + */ + var _undelegate = function(el, evt, sel, handler) { + if (!handler) { + var listeners = el.listeners[evt]; + listeners.forEach(function(proxy) { + el.removeEventListener(evt, proxy, false); + }); + } else { + var proxyids = handler.proxy[evt]; + if (proxyids.length) { + proxyids.forEach(function(proxyid) { + if (el.removeEventListener) { + el.removeEventListener(evt, proxies[proxyid], false); + } + }); + } + } + }; + + /** 手势识别 */ + var config = { + tap: true, + doubleTap: true, + tapMaxDistance: 10, + hold: true, + holdTime: 650, + //ms + maxDoubleTapInterval: 300, + + //swipe + swipe: true, + swipeTime: 300, + swipeMinDistance: 18, + swipeFactor: 5, + + drag: true, + pinch: true, + minScaleRate: 0, + minRotationAngle: 0 + }; + + var _hasTouch = ('ontouchstart' in window); + var smrEventList = { + TOUCH_START: 'touchstart', + TOUCH_MOVE: 'touchmove', + TOUCH_END: 'touchend', + TOUCH_CANCEL: 'touchcancel', + + MOUSE_DOWN: 'mousedown', + MOUSE_MOVE: 'mousemove', + MOUSE_UP: 'mouseup', + + CLICK: 'click', + + //PINCH TYPE EVENT NAMES + PINCH_START: 'pinchstart', + PINCH_END: 'pinchend', + PINCH: 'pinch', + PINCH_IN: 'pinchin', + PINCH_OUT: 'pinchout', + + ROTATION_LEFT: 'rotateleft', + ROTATION_RIGHT: 'rotateright', + ROTATION: 'rotate', + + SWIPE_START: 'swipestart', + SWIPING: 'swiping', + SWIPE_END: 'swipeend', + SWIPE_LEFT: 'swipeleft', + SWIPE_RIGHT: 'swiperight', + SWIPE_UP: 'swipeup', + SWIPE_DOWN: 'swipedown', + SWIPE: 'swipe', + + DRAG: 'drag', + DRAGSTART : 'dragstart', + DRAGEND : 'dragend', + + //HOLD AND TAP + HOLD: 'hold', + TAP: 'tap', + DOUBLE_TAP: 'doubletap' + }; + + /** + * 获取事件的位置信息 + * @param ev, 原生事件对象 + * @return array [{ x: int, y: int }] + */ + function getPosOfEvent(ev) { + //多指触摸, 返回多个手势位置信息 + if (_hasTouch) { + var posi = []; + var src = null; + + for (var t = 0, len = ev.touches.length; t < len; t++) { + src = ev.touches[t]; + posi.push({ + x: src.pageX, + y: src.pageY + }); + } + return posi; + } //处理PC浏览器的情况 + else { + return [{ + x: ev.pageX, + y: ev.pageY + }]; + } + } + /** + *获取两点之间的距离 + */ + function getDistance(pos1, pos2) { + var x = pos2.x - pos1.x, + y = pos2.y - pos1.y; + return Math.sqrt((x * x) + (y * y)); + } + + /** + *计算事件的手势个数 + *@param ev {Event} + */ + function getFingers(ev) { + return ev.touches ? ev.touches.length: 1; + } + //计算收缩的比例 + function calScale(pstart, pmove) { + if (pstart.length >= 2 && pmove.length >= 2) { + var disStart = getDistance(pstart[1], pstart[0]); + var disEnd = getDistance(pmove[1], pmove[0]); + + return disEnd / disStart; + } + return 1; + } + + //return 角度,范围为{-180-0,0-180}, 用来识别swipe方向。 + function getAngle(p1, p2) { + return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI; + } + //return 角度, 范围在{0-180}, 用来识别旋转角度 + function _getAngle180(p1, p2) { + var agl = Math.atan((p2.y - p1.y) * -1 / (p2.x - p1.x)) * (180 / Math.PI); + return (agl < 0 ? (agl + 180) : agl); + } + + //根据角度计算方位 + //@para agl {int} 是调用getAngle获取的。 + function getDirectionFromAngle(agl) { + var directions = { + up: agl < -45 && agl > -135, + down: agl >= 45 && agl < 135, + left: agl >= 135 || agl <= -135, + right: agl >= -45 && agl <= 45 + }; + for (var key in directions) { + if (directions[key]) return key; + } + return null; + } + + //取消事件的默认行为和冒泡 + function preventDefault(ev) { + ev.preventDefault(); + ev.stopPropagation(); + } + + function getXYByElement(el) { + var left = 0, + top = 0; + + while (el.offsetParent) { + left += el.offsetLeft; + top += el.offsetTop; + el = el.offsetParent; + } + return { + left: left, + top: top + }; + } + + function reset() { + startEvent = moveEvent = endEvent = null; + __tapped = __touchStart = startSwiping = startPinch = false; + startDrag = false; + pos = {}; + __rotation_single_finger = false; + } + + function isTouchStart(ev) { + return (ev.type === 'touchstart' || ev.type === 'mousedown'); + } + function isTouchMove(ev) { + return (ev.type === 'touchmove' || ev.type === 'mousemove'); + } + function isTouchEnd(ev) { + return (ev.type === 'touchend' || ev.type === 'mouseup' || ev.type === 'touchcancel'); + } + + var pos = { + start: null, + move: null, + end: null + }; + var startTime = 0; + var fingers = 0; + var startEvent = null; + var moveEvent = null; + var endEvent = null; + var startSwiping = false; + var startPinch = false; + var startDrag = false; + + var __offset = {}; + var __touchStart = false; + var __holdTimer = null; + var __tapped = false; + var __lastTapEndTime = null; + var __tapTimer = null; + + var __scale_last_rate = 1; + var __rotation_single_finger = false; + var __rotation_single_start = []; //元素坐标中心位置 + var __initial_angle = 0; + var __rotation = 0; + + var __prev_tapped_end_time = 0; + var __prev_tapped_pos = null; + + var gestures = { + _getAngleDiff: function(currentPos) { + var diff = parseInt(__initial_angle - _getAngle180(currentPos[0], currentPos[1]), 10); + var count = 0; + + while (Math.abs(diff - __rotation) > 90 && count++<50) { + if (__rotation < 0) { + diff -= 180; + } else { + diff += 180; + } + } + __rotation = parseInt(diff, 10); + return __rotation; + }, + pinch: function(ev) { + var el = ev.target; + if (config.pinch) { + //touchend进入此时的getFinger(ev) < 2 + if (!__touchStart) return; + if (getFingers(ev) < 2) { + if (!isTouchEnd(ev)) return; + } + var scale = calScale(pos.start, pos.move); + var rotation = this._getAngleDiff(pos.move); + var eventObj = { + type: '', + originEvent: ev, + scale: scale, + rotation: rotation, + direction: (rotation > 0 ? 'right': 'left'), + fingersCount: getFingers(ev) + }; + if (!startPinch) { + startPinch = true; + eventObj.fingerStatus = "start"; + _trigger(el, smrEventList.PINCH_START, eventObj); + } else if (isTouchMove(ev)) { + eventObj.fingerStatus = "move"; + _trigger(el, smrEventList.PINCH, eventObj); + } else if (isTouchEnd(ev)) { + eventObj.fingerStatus = "end"; + _trigger(el, smrEventList.PINCH_END, eventObj); + reset(); + } + + if (Math.abs(1 - scale) > config.minScaleRate) { + var scaleEv = utils.simpleClone(eventObj); + + //手势放大, 触发pinchout事件 + var scale_diff = 0.00000000001; //防止touchend的scale与__scale_last_rate相等,不触发事件的情况。 + if (scale > __scale_last_rate) { + __scale_last_rate = scale - scale_diff; + _trigger(el, smrEventList.PINCH_OUT, scaleEv, false); + } //手势缩小,触发pinchin事件 + else if (scale < __scale_last_rate) { + __scale_last_rate = scale + scale_diff; + _trigger(el, smrEventList.PINCH_IN, scaleEv, false); + } + + if (isTouchEnd(ev)) { + __scale_last_rate = 1; + } + } + + if (Math.abs(rotation) > config.minRotationAngle) { + var rotationEv = utils.simpleClone(eventObj), eventType; + + eventType = rotation > 0 ? smrEventList.ROTATION_RIGHT: smrEventList.ROTATION_LEFT; + _trigger(el, eventType, rotationEv, false); + _trigger(el, smrEventList.ROTATION, eventObj); + } + + } + }, + rotateSingleFinger: function(ev) { + var el = ev.target; + if (__rotation_single_finger && getFingers(ev) < 2) { + if (!pos.move) return; + if (__rotation_single_start.length < 2) { + var docOff = getXYByElement(el); + + __rotation_single_start = [{ + x: docOff.left + el.offsetWidth / 2, + y: docOff.top + el.offsetHeight / 2 + }, + pos.move[0]]; + __initial_angle = parseInt(_getAngle180(__rotation_single_start[0], __rotation_single_start[1]), 10); + } + var move = [__rotation_single_start[0], pos.move[0]]; + var rotation = this._getAngleDiff(move); + var eventObj = { + type: '', + originEvent: ev, + rotation: rotation, + direction: (rotation > 0 ? 'right': 'left'), + fingersCount: getFingers(ev) + }; + + if (isTouchMove(ev)) { + eventObj.fingerStatus = "move"; + } else if (isTouchEnd(ev) || ev.type === 'mouseout') { + eventObj.fingerStatus = "end"; + _trigger(el, smrEventList.PINCH_END, eventObj); + reset(); + } + + var eventType = rotation > 0 ? smrEventList.ROTATION_RIGHT: smrEventList.ROTATION_LEFT; + _trigger(el, eventType, eventObj); + _trigger(el, smrEventList.ROTATION, eventObj); + } + }, + swipe: function(ev) { + //目前swipe只存在一个手势上 + var el = ev.target; + if (!__touchStart || !pos.move || getFingers(ev) > 1) { + return; + } + + var now = Date.now(); + var touchTime = now - startTime; + var distance = getDistance(pos.start[0], pos.move[0]); + var position = { + x: pos.move[0].x - __offset.left, + y: pos.move[0].y - __offset.top + }; + var angle = getAngle(pos.start[0], pos.move[0]); + var direction = getDirectionFromAngle(angle); + var touchSecond = touchTime / 1000; + var factor = ((10 - config.swipeFactor) * 10 * touchSecond * touchSecond); + var eventObj = { + type: smrEventList.SWIPE, + //DEFAULT: smrEventList.SWIPE event. + originEvent: ev, + position: position, + direction: direction, + distance: distance, + distanceX: pos.move[0].x - pos.start[0].x, + distanceY: pos.move[0].y - pos.start[0].y, + x : pos.move[0].x - pos.start[0].x, + y : pos.move[0].y - pos.start[0].y, + angle: angle, + duration: touchTime, + fingersCount: getFingers(ev), + factor: factor + }; + if (config.swipe) { + var swipeTo = function() { + var elt = smrEventList; + switch (direction) { + case 'up': + _trigger(el, elt.SWIPE_UP, eventObj); + break; + case 'down': + _trigger(el, elt.SWIPE_DOWN, eventObj); + break; + case 'left': + _trigger(el, elt.SWIPE_LEFT, eventObj); + break; + case 'right': + _trigger(el, elt.SWIPE_RIGHT, eventObj); + break; + } + }; + + if (!startSwiping) { + eventObj.fingerStatus = eventObj.swipe = 'start'; + startSwiping = true; + _trigger(el, smrEventList.SWIPE_START, eventObj); + } else if (isTouchMove(ev)) { + eventObj.fingerStatus = eventObj.swipe = 'move'; + _trigger(el, smrEventList.SWIPING, eventObj); + + if (touchTime > config.swipeTime && touchTime < config.swipeTime + 50 && distance > config.swipeMinDistance) { + swipeTo(); + _trigger(el, smrEventList.SWIPE, eventObj, false); + } + } else if (isTouchEnd(ev) || ev.type === 'mouseout') { + eventObj.fingerStatus = eventObj.swipe = 'end'; + _trigger(el, smrEventList.SWIPE_END, eventObj); + + if (config.swipeTime > touchTime && distance > config.swipeMinDistance) { + swipeTo(); + _trigger(el, smrEventList.SWIPE, eventObj, false); + } + } + } + + if (config.drag) { + if (!startDrag) { + eventObj.fingerStatus = eventObj.swipe = 'start'; + startDrag = true; + _trigger(el, smrEventList.DRAGSTART, eventObj); + } else if (isTouchMove(ev)) { + eventObj.fingerStatus = eventObj.swipe = 'move'; + _trigger(el, smrEventList.DRAG, eventObj); + } else if (isTouchEnd(ev)) { + eventObj.fingerStatus = eventObj.swipe = 'end'; + _trigger(el, smrEventList.DRAGEND, eventObj); + } + + } + }, + tap: function(ev) { + var el = ev.target; + if (config.tap) { + var now = Date.now(); + var touchTime = now - startTime; + var distance = getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]); + + clearTimeout(__holdTimer); //去除hold事件 + var isDoubleTap = (function() { + if (__prev_tapped_pos && config.doubleTap && (startTime - __prev_tapped_end_time) < config.maxDoubleTapInterval) { + var doubleDis = getDistance(__prev_tapped_pos, pos.start[0]); + if (doubleDis < 16) return true; + } + return false; + })(); + + if (isDoubleTap) { + clearTimeout(__tapTimer); + _trigger(el, smrEventList.DOUBLE_TAP, { + type: smrEventList.DOUBLE_TAP, + originEvent: ev, + position: pos.start[0] + }); + return; + } + + if (config.tapMaxDistance < distance) return; + + if (config.holdTime > touchTime && getFingers(ev) <= 1) { + //clearTimeout在ios上有时不work(alert引起的), 先用__tapped顶一下 + __tapped = true; + __prev_tapped_end_time = now; + __prev_tapped_pos = pos.start[0]; + __tapTimer = setTimeout(function(){ + _trigger(el, smrEventList.TAP, { + type: smrEventList.TAP, + originEvent: ev, + fingersCount: getFingers(ev), + position: __prev_tapped_pos + }); + }, 220); + } + } + }, + hold: function(ev) { + var el = ev.target; + if (config.hold) { + clearTimeout(__holdTimer); + + __holdTimer = setTimeout(function() { + if (!pos.start) return; + var distance = getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]); + if (config.tapMaxDistance < distance) return; + + if (!__tapped) { + _trigger(el, "hold", { + type: 'hold', + originEvent: ev, + fingersCount: getFingers(ev), + position: pos.start[0] + }); + } + }, + config.holdTime); + } + } + }; + + var handlerOriginEvent = function(ev) { + + var el = ev.target; + switch (ev.type) { + case 'touchstart': + case 'mousedown': + //__rotation_single_finger = false; + __rotation_single_start = []; + __touchStart = true; + if (!pos.start || pos.start.length < 2) { + pos.start = getPosOfEvent(ev); + } + if (getFingers(ev) >= 2) { + __initial_angle = parseInt(_getAngle180(pos.start[0], pos.start[1]), 10); + } + + startTime = Date.now(); + startEvent = ev; + __offset = {}; + + var box = el.getBoundingClientRect(); + var docEl = document.documentElement; + __offset = { + top: box.top + (window.pageYOffset || docEl.scrollTop) - (docEl.clientTop || 0), + left: box.left + (window.pageXOffset || docEl.scrollLeft) - (docEl.clientLeft || 0) + }; + + gestures.hold(ev); + break; + case 'touchmove': + case 'mousemove': + if (!__touchStart || !pos.start) return; + pos.move = getPosOfEvent(ev); + if (getFingers(ev) >= 2) { + gestures.pinch(ev); + } else if (__rotation_single_finger) { + gestures.rotateSingleFinger(ev); + } else { + gestures.swipe(ev); + } + break; + case 'touchend': + case 'touchcancel': + case 'mouseup': + case 'mouseout': + if (!__touchStart) return; + endEvent = ev; + + if (startPinch) { + gestures.pinch(ev); + } else if (__rotation_single_finger) { + gestures.rotateSingleFinger(ev); + } else if (startSwiping) { + gestures.swipe(ev); + } else { + gestures.tap(ev); + } + + reset(); + __initial_angle = 0; + __rotation = 0; + if (ev.touches && ev.touches.length === 1) { + __touchStart = true; + __rotation_single_finger = true; + } + break; + } + }; + + /** + 开发者接口 + usage: + touch.on("#test", "tap swipeleft swiperight", handler); + touch.trigger("#test", "tap"); + touch.off("#test", "tap swipeleft swiperight", handler); + */ + var _on = function() { + + var evts, handler, evtMap, sel, args = arguments; + if (args.length < 2 || args > 4) { + return console.error("unexpected arguments!"); + } + var els = utils.getType(args[0]) === 'string' ? doc.querySelectorAll(args[0]) : args[0]; + els = els.length ? Array.prototype.slice.call(els) : [els]; + //事件绑定 + if (args.length === 3 && utils.getType(args[1]) === 'string') { + evts = args[1].split(" "); + handler = args[2]; + evts.forEach(function(evt) { + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + els.forEach(function(el) { + _bind(el, evt, handler); + }); + }); + return; + } + + function evtMapDelegate( evt ){ + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + els.forEach(function(el) { + _delegate(el, evt, sel, evtMap[evt]); + }); + } + //mapEvent delegate + if (args.length === 3 && utils.getType(args[1]) === 'object') { + evtMap = args[1]; + sel = args[2]; + for (var evt1 in evtMap) { + evtMapDelegate(evt1); + } + return; + } + + function evtMapBind(evt){ + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + els.forEach(function(el) { + _bind(el, evt, evtMap[evt]); + }); + } + + //mapEvent bind + if (args.length === 2 && utils.getType(args[1]) === 'object') { + evtMap = args[1]; + for (var evt2 in evtMap) { + evtMapBind(evt2); + } + return; + } + + //兼容factor config + if (args.length === 4 && utils.getType(args[2]) === "object") { + evts = args[1].split(" "); + handler = args[3]; + evts.forEach(function(evt) { + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + els.forEach(function(el) { + _bind(el, evt, handler); + }); + }); + return; + } + + //事件代理 + if (args.length === 4) { + var el = els[0]; + evts = args[1].split(" "); + sel = args[2]; + handler = args[3]; + evts.forEach(function(evt) { + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + _delegate(el, evt, sel, handler); + }); + return; + } + }; + + var _off = function() { + var evts, handler; + var args = arguments; + if (args.length < 1 || args.length > 4) { + return console.error("unexpected arguments!"); + } + var els = utils.getType(args[0]) === 'string' ? doc.querySelectorAll(args[0]) : args[0]; + els = els.length ? Array.prototype.slice.call(els) : [els]; + + if (args.length === 1 || args.length === 2) { + els.forEach(function(el) { + evts = args[1] ? args[1].split(" ") : Object.keys(el.listeners); + if (evts.length) { + evts.forEach(function(evt) { + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + _unbind(el, evt); + _undelegate(el, evt); + }); + } + }); + return; + } + + if (args.length === 3 && utils.getType(args[2]) === 'function') { + handler = args[2]; + els.forEach(function(el) { + evts = args[1].split(" "); + evts.forEach(function(evt) { + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + _unbind(el, evt, handler); + }); + }); + return; + } + + if (args.length === 3 && utils.getType(args[2]) === 'string') { + var sel = args[2]; + els.forEach(function(el) { + evts = args[1].split(" "); + evts.forEach(function(evt) { + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + _undelegate(el, evt, sel); + }); + }); + return; + } + + if (args.length === 4) { + handler = args[3]; + els.forEach(function(el) { + evts = args[1].split(" "); + evts.forEach(function(evt) { + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + _undelegate(el, evt, sel, handler); + }); + }); + return; + } + }; + + var _dispatch = function(el, evt, detail) { + var args = arguments; + if (!_hasTouch) { + evt = utils.getPCevts(evt); + } + var els = utils.getType(args[0]) === 'string' ? doc.querySelectorAll(args[0]) : args[0]; + els = els.length ? Array.prototype.call(els) : [els]; + + els.forEach(function(el) { + _trigger(el, evt, detail); + }); + }; + + //init gesture + function init() { + var eventNames = _hasTouch ? 'touchstart touchmove touchend touchcancel': 'mouseup mousedown mousemove mouseout'; + _on(doc, eventNames, handlerOriginEvent); + } + + init(); + + exports.on = _on; + exports.off = _off; + exports.bind = _on; + exports.unbind = _off; + exports.live = _on; + exports.die = _off; + exports.config = config; + exports.trigger = _dispatch; + +})(document, touch); + +Library.touch = sumeru.Library.create(function(exports){ + Library.objUtils.extend(exports, touch); return exports; }); \ No newline at end of file diff --git a/sumeru/server/cluster.js b/sumeru/server/cluster.js index fe6e447..2597946 100644 --- a/sumeru/server/cluster.js +++ b/sumeru/server/cluster.js @@ -1,12 +1,26 @@ -var channelNameRev = 'sumeru_cluster_notify_', - channelNameSend = 'sumeru_cluster_send_', +var cluster_params = { + host : sumeru.config.cluster.get("host"), + port : sumeru.config.cluster.get("port"), + user: sumeru.config.cluster.get("user"), + password:sumeru.config.cluster.get("password"), + dbname:sumeru.config.cluster.get("dbname") +}; +var channelNameRev = cluster_params.user+'-sumeru_cluster_notify_', + channelNameSend = cluster_params.user+'-sumeru_cluster_send_', instance; +var options = {"no_ready_check":true}; var redis = require('redis'); - + +var redis_init = function(){ + var client = redis.createClient(cluster_params.port,cluster_params.host,options); + client.auth(cluster_params.user+ '-' + cluster_params.password + '-' + cluster_params.dbname); + sumeru.log('redis_init',cluster_params.port,cluster_params.host,options,cluster_params.user+ '-' + cluster_params.password + '-' + cluster_params.dbname); + return client; +}; var init = function(fw){ - var redis_client_subscribe = redis.createClient(), + var redis_client_subscribe = redis_init(), redis_client_publish; redis_client_subscribe.on("error",function(){ @@ -28,7 +42,7 @@ var init = function(fw){ target : [channelNameSend], handle : function(data){ if (!redis_client_publish) { - redis_client_publish = redis.createClient(); + redis_client_publish = redis_init(); instance = redis_client_publish; }; redis_client_publish.publish(channelNameRev, JSON.stringify(data)); @@ -43,8 +57,7 @@ var init = function(fw){ var getInstance = function(){ if (instance) {return instance}; - - instance = redis.createClient(); + instance = redis_init(); return instance; } diff --git a/sumeru/src/frameworkConfig.js b/sumeru/src/frameworkConfig.js index a6d421d..1943b10 100644 --- a/sumeru/src/frameworkConfig.js +++ b/sumeru/src/frameworkConfig.js @@ -13,11 +13,20 @@ var globalConfig = function(fw){ // fw.config.defineModule('cluster'); - fw.config.cluster({ - enable : true, - cluster_mgr : '127.0.0.1', - cluster_mgr_port : 6379 - }); + if(fw.BAE_VERSION){ + fw.config.cluster({ + enable : false, + host : 'redis.duapp.com', + port : 80 + }); + }else{ + fw.config.cluster({ + enable : false, + host : '127.0.0.1', + port : 6379 + }); + } + fw.config({ httpServerPort: httpServerPort, @@ -29,7 +38,7 @@ var globalConfig = function(fw){ clientValidation: true, serverValidation: true }); - + if (typeof location != 'undefined') { fw.config({ From 2fae258db10716570e8de13b91a985167ed623ce Mon Sep 17 00:00:00 2001 From: brandnewera Date: Thu, 9 Jan 2014 12:29:32 +0800 Subject: [PATCH 06/24] eliminate some warnings --- sumeru/build/build.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sumeru/build/build.js b/sumeru/build/build.js index 9d26282..73c50fa 100644 --- a/sumeru/build/build.js +++ b/sumeru/build/build.js @@ -144,7 +144,8 @@ var buildAppResource = function(appDir, theBinDir){ * */ var compressor = UglifyJS.Compressor({ - unused : false + unused : false, + warnings: false }); ast.figure_out_scope(); var compressed_ast = ast.transform(compressor); From 248aa63db685d0e17f23bf0b01d95ef36d6d477b Mon Sep 17 00:00:00 2001 From: brandnewera Date: Thu, 9 Jan 2014 12:35:09 +0800 Subject: [PATCH 07/24] eliminate some warnings --- sumeru/build/buildJavascript.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sumeru/build/buildJavascript.js b/sumeru/build/buildJavascript.js index 805ce71..3c793c1 100644 --- a/sumeru/build/buildJavascript.js +++ b/sumeru/build/buildJavascript.js @@ -77,7 +77,8 @@ module.exports = function(sumeruDir, dstDir) { var orig_code = buildEntireContent; var ast = UglifyJS.parse(orig_code); // parse code and get the initial AST var compressor = UglifyJS.Compressor({ - unused : false + unused : false, + warnings:false }); ast.figure_out_scope(); var compressed_ast = ast.transform(compressor); From 20178958d5495dabef17fc5dff3c746b4ff91df5 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Thu, 9 Jan 2014 17:23:45 +0800 Subject: [PATCH 08/24] fix manifest path bug --- sumeru/build/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sumeru/build/build.js b/sumeru/build/build.js index 73c50fa..98cbcad 100644 --- a/sumeru/build/build.js +++ b/sumeru/build/build.js @@ -200,7 +200,7 @@ var buildManifest = function(appDir, theBinDir){ } } }; - readAllFileInView(baseViewDir, 'view'); + readAllFileInView(baseViewDir, '/view'); var cdir = buildConfig.cacheDirectory; if(cdir){ From f4f9db272a978f165382031a316aba83b4df6745 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Thu, 9 Jan 2014 17:36:46 +0800 Subject: [PATCH 09/24] add cluster.js --- app/server_config/bae.js | 17 +++++++++++------ app/server_config/cluster.js | 9 +++++++++ sumeru/npm-tools/sumeru | 16 ++++++++++++---- 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 app/server_config/cluster.js diff --git a/app/server_config/bae.js b/app/server_config/bae.js index 0d75c90..3294bd2 100644 --- a/app/server_config/bae.js +++ b/app/server_config/bae.js @@ -1,12 +1,17 @@ //config file for bae if(sumeru.BAE_VERSION){ sumeru.config.database({ - dbname : 'your_bae_db_name', - user: 'your_bae_ak',//bae 3.0 required - password: 'your_bae_sk',//bae 3.0 required + dbname : 'yourdbname', + user: 'yourpk',//bae 3.0 required + password: 'yoursk',//bae 3.0 required }); sumeru.config({ - site_url : 'http://yourapp.duapp.com/', //with tailing slash - }); - + site_url : '', //with tailing slash + }); + sumeru.config.cluster({ + enable : false, + dbname : 'yourdbname', + user: 'yourpk', + password: 'yoursk', + }); } \ No newline at end of file diff --git a/app/server_config/cluster.js b/app/server_config/cluster.js new file mode 100644 index 0000000..05db5ae --- /dev/null +++ b/app/server_config/cluster.js @@ -0,0 +1,9 @@ +sumeru.config.defineModule('cluster'); +sumeru.config.cluster({ + enable : false, + host : 'your_redis_ip', + port : 6379, + dbname : 'your_redis_dbname', + user: 'your_redis_user', + password: 'your_redis_password' +}); \ No newline at end of file diff --git a/sumeru/npm-tools/sumeru b/sumeru/npm-tools/sumeru index deea9e3..2945af7 100755 --- a/sumeru/npm-tools/sumeru +++ b/sumeru/npm-tools/sumeru @@ -200,22 +200,30 @@ function updateApplicationAt(ph) { //FIXME doesn't support multiple app scenario var server_config_path = path.join(ph, 'app/server_config'), mime_js = path.join(server_config_path, 'mime.js'), - tmp_dir_js = path.join(server_config_path, 'tmp_dir.js'), + tmp_dir_js = path.join(server_config_path, 'tmp_dir.js'), + cluster_js = path.join(server_config_path, 'cluster.js'), package_js = path.join(server_config_path, 'package.js'); if(!fs.existsSync(mime_js)){ copyFile(path.join(__dirname, '../../app/server_config/mime.js'), mime_js); } if(!fs.existsSync(tmp_dir_js)){ - copyFile(path.join(__dirname, '../../app/server_config/tmp_dir.js'), tmp_dir_js); + copyFile(path.join(__dirname, '../../app/server_config/tmp_dir.js'), tmp_dir_js); + } + + if(!fs.existsSync(cluster_js)){ + copyFile(path.join(__dirname, '../../app/server_config/cluster.js'), cluster_js); } //check if package.json contains them, if not, add them. var pkgContent = fs.readFileSync(package_js).toString(); if(pkgContent.match(/mime\.js/m) == null){ - pkgContent = pkgContent.replace(/sumeru.packages\(/g, function($0){return $0 + '\n' + "'mime.js,'"}); + pkgContent = pkgContent.replace(/sumeru.packages\(/g, function($0){return $0 + '\n' + "'mime.js,'"}); } if(pkgContent.match(/tmp_dir\.js/m) == null){ - pkgContent = pkgContent.replace(/sumeru.packages\(/g, function($0){return $0 + '\n' + "'tmp_dir.js,'"}); + pkgContent = pkgContent.replace(/sumeru.packages\(/g, function($0){return $0 + '\n' + "'tmp_dir.js,'"}); + } + if(pkgContent.match(/cluster\.js/m) == null){ + pkgContent = pkgContent.replace(/sumeru.packages\(/g, function($0){return $0 + '\n' + "'cluster.js,'"}); } fs.writeFileSync(package_js, pkgContent); From 55092b7877bbc9cf37941c73ef1e947f0b8b0d49 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Thu, 9 Jan 2014 18:46:41 +0800 Subject: [PATCH 10/24] 0.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 564f71c..1279fb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sumeru", - "version": "0.8.20", + "version": "0.9.0", "description": "A Realtime Javascript RIA Framework For Mobile WebApp", "main": "app.js", "scripts": { From a7ec084c4f43ca81e75076c5ac420c8b9e0e4ad8 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Tue, 14 Jan 2014 16:44:00 +0800 Subject: [PATCH 11/24] significant offline support improve --- sumeru/build/build.js | 11 +++- sumeru/build/buildJavascript.js | 3 +- sumeru/library/touch.js | 32 ++++++---- sumeru/server/clientTracer.js | 9 ++- sumeru/server/run.js | 31 +++++++--- sumeru/server/sysUser.js | 2 +- sumeru/src/auth.js | 12 +++- sumeru/src/authModel.js | 2 +- sumeru/src/cache.js | 100 ++++++++++++++++++++++++++++++++ sumeru/src/collection.js | 15 +---- sumeru/src/controller.js | 69 +++++++++++++++++----- sumeru/src/domdiff.js | 20 +++++++ sumeru/src/external.js | 78 ++++++++++++++++++------- sumeru/src/framework.js | 33 ++++++++--- sumeru/src/frameworkConfig.js | 9 +++ sumeru/src/messageDispatcher.js | 8 ++- sumeru/src/netMessage.js | 2 +- sumeru/src/package.js | 2 +- sumeru/src/pilot.js | 3 + sumeru/src/pubsub.js | 34 ++++++++--- sumeru/src/reachability.js | 4 ++ sumeru/src/router.js | 3 +- sumeru/src/validationRules.js | 4 +- sumeru/src/writeBuffer.js | 15 ++++- 24 files changed, 400 insertions(+), 101 deletions(-) create mode 100644 sumeru/src/cache.js diff --git a/sumeru/build/build.js b/sumeru/build/build.js index 98cbcad..7331464 100644 --- a/sumeru/build/build.js +++ b/sumeru/build/build.js @@ -1,5 +1,6 @@ var path = require('path'); var fs = require('fs'); +var _debug = true; var baseDir = path.join(__dirname, '../../'); var sumeruDir = path.join(__dirname, '/../'); @@ -38,12 +39,12 @@ if (typeof process.BAE !== 'undefined'){ dstDir = appDir;//path.join(__dirname, '/../../app' + (process.argv[2] ? '/' +process.argv[2] : '')); } - process.appDir = appDir; process.baseDir = baseDir; process.dstDir = dstDir; + sumeru.dev('build from :' + baseDir); sumeru.dev('to :' + dstDir); @@ -111,6 +112,9 @@ var buildAppResource = function(appDir, theBinDir){ } readPackage(appDir); + + + var UglifyJS = require('uglify-js'); @@ -165,8 +169,9 @@ var buildAppResource = function(appDir, theBinDir){ buildView(appDir, binDir); buildManifest(appDir, path.join(dstDir, 'bin')); }else{ - fs.writeFileSync(theBinDir + '/app.js', packedAppContent, 'utf-8'); - fs.writeFileSync(theBinDir + '/app.css', packedAppCssContent, 'utf-8'); + fs.writeFileSync(theBinDir + '/app.js', _debug?buildAppContent:packedAppContent, 'utf-8'); + fs.writeFileSync(theBinDir + '/app.css', _debug?buildAppCssContent:packedAppCssContent, 'utf-8'); + buildView(appDir, theBinDir); buildManifest(appDir, theBinDir); } diff --git a/sumeru/build/buildJavascript.js b/sumeru/build/buildJavascript.js index 3c793c1..bdda76f 100644 --- a/sumeru/build/buildJavascript.js +++ b/sumeru/build/buildJavascript.js @@ -5,6 +5,7 @@ */ var sumeru = require(__dirname + '/../src/newPkg.js')(); require(__dirname + '/../src/log.js')(sumeru); +var _debug = true; module.exports = function(sumeruDir, dstDir) { var fs = require('fs'); @@ -99,7 +100,7 @@ module.exports = function(sumeruDir, dstDir) { //写入sumeru.js 和 sumeru.css文件 fs.writeFileSync(targetCSSFileName, packedCSS, 'utf8'); - fs.writeFileSync(targetJsFileName, final_code, 'utf8'); + fs.writeFileSync(targetJsFileName, _debug?buildEntireContent:final_code, 'utf8'); } else{ sumeru.log('sumeru dir or sumeru package.js does not exist!'); diff --git a/sumeru/library/touch.js b/sumeru/library/touch.js index 6efd830..d14966a 100644 --- a/sumeru/library/touch.js +++ b/sumeru/library/touch.js @@ -1,4 +1,4 @@ -//version 0.2.10 +//version 0.2.11 var touch = touch || {}; (function(doc, exports) { @@ -63,7 +63,9 @@ var touch = touch || {}; if (el.className) { var cns = el.className.split(/\s+/); return "." + cns.join("."); - } else { + } else if(el === document){ + return "body"; + }else{ return el.tagName.toLowerCase(); } }, @@ -146,7 +148,11 @@ var touch = touch || {}; e[p] = e.detail[p]; } } - handler.call(e.target, e); + var returnValue = handler.call(e.target, e); + if(typeof returnValue !== "undefined" && !returnValue){ + e.stopPropagation(); + e.preventDefault(); + } }; handler.proxy = handler.proxy || {}; @@ -193,7 +199,7 @@ var touch = touch || {}; */ var _delegate = function(el, evt, sel, handler) { var proxy = function(e) { - var target; + var target, returnValue; e.originEvent = e; e.startRotate = function() { __rotation_single_finger = true; @@ -214,13 +220,21 @@ var touch = touch || {}; while (!utils.matchSelector(target, integrateSelector)) { target = target.parentNode; } - handler.call(target, e); + returnValue = handler.call(e.target, e); + if(typeof returnValue !== "undefined" && !returnValue){ + e.stopPropagation(); + e.preventDefault(); + } } else { if (os.ios7) { utils.forceReflow(); } if (match || ischild) { - handler.call(e.target, e); + returnValue = handler.call(e.target, e); + if(typeof returnValue !== "undefined" && !returnValue){ + e.stopPropagation(); + e.preventDefault(); + } } } }; @@ -410,12 +424,6 @@ var touch = touch || {}; return null; } - //取消事件的默认行为和冒泡 - function preventDefault(ev) { - ev.preventDefault(); - ev.stopPropagation(); - } - function getXYByElement(el) { var left = 0, top = 0; diff --git a/sumeru/server/clientTracer.js b/sumeru/server/clientTracer.js index caaa144..1c3b4c6 100644 --- a/sumeru/server/clientTracer.js +++ b/sumeru/server/clientTracer.js @@ -409,9 +409,16 @@ clientTracer.__reg('onSocketConnection',function(clientId,socketId,conn){ } socket_count ++; var client = findClient(clientId); + // 获取来源ip. + var ip = conn.remoteAddress; + + // 处理有代理的情况下的ip来源 + if(conn.headers['x-forwarded-for']){ + ip = conn.headers['x-forwarded-for'].split(', ')[0]; + } // 检查来源ip是否与client创建时一至,防止cookie,session的伪造 - if(client.__safeIP(conn.remoteAddress)){ + if(client.__safeIP(ip)){ client.__socketConnection(socketId); }else{ console.warn('unsafe ip address : ' , conn.remoteAddress); diff --git a/sumeru/server/run.js b/sumeru/server/run.js index 21d40a2..7c142d4 100644 --- a/sumeru/server/run.js +++ b/sumeru/server/run.js @@ -299,10 +299,9 @@ if (fs.existsSync(publishBaseDir)) { if(publishModule && publishModule.config && publishModule.type === 'external'){ externalConfig = publishModule.config; } - }else if((typeof publishModuleObject).toLowerCase() === "object"){ - //兼容老的external.js, 坑。。。 + }else if((typeof publishModuleObject).toLowerCase() === "object"){//兼容老的external.js var publishModule = require(file); - if(publishModule && !publishModule.config){ + if(publishModule && !publishModule.config && publishModule.geturl){ externalConfig = publishModule; } } @@ -314,12 +313,12 @@ if (fs.existsSync(publishBaseDir)) { fw.dev(publishBaseDir + ' DO NOT EXIST'); } -//external.js var http = require("http"); +var https = require("https"); var sockjs = require("sockjs"); var serverObjectId = require("./ObjectId"); var url = require('url'); -require(__dirname + '/../src/external.js')(fw, findDiff, publishBaseDir, externalConfig, http, serverObjectId, url); +require(__dirname + '/../src/external.js')(fw, findDiff, publishBaseDir, externalConfig, http, https, serverObjectId, url); var runStub = function(db) { @@ -359,7 +358,8 @@ var runStub = function(db) { require(__dirname + "/../src/event.js")(fw); require(__dirname + "/../src/pubsub.js")(fw,PublishContainer); require(__dirname + "/../src/sense.js")(fw); - require(__dirname + "/../src/pilot.js")(fw); + require(__dirname + "/../src/pilot.js")(fw); + require(__dirname + "/../src/cache.js")(fw); require(__dirname + '/../src/model.js')(fw); require(__dirname + '/../src/modelPoll.js')(fw); @@ -390,7 +390,7 @@ var runStub = function(db) { }).listen(PORT, function() { fw.log('Server Listening on ' + PORT); }); - + // register the server to group //groupManager.register([{addr:config.get('selfGroupManagerAddr') || '0.0.0.0',port:config.get('selfGroupManagerPort') || (parseInt(PORT) + 3000)}]); @@ -426,6 +426,18 @@ var runStub = function(db) { }); }); + + /** + * 此处是为测试离线加的 + * 测试离线时,需要格外的暴露globalServer、PORT。 + * start --jin + + if(process.argv[2]&&process.argv[2]=='test'){ + process.globalServer = globalServer; + process.sock = sock; + process.PORT = PORT; + } + end --jin*/ var clearSocketMgrBySocketId = function(_sumeru_socket_id){ delete SocketMgr[_sumeru_socket_id]; @@ -645,6 +657,9 @@ var runStub = function(db) { 'modelname' : PublishContainer[pubname]['modelName'], 'plainstruct' : PublishContainer[pubname]['plainStruct'] }; + if(PublishContainer[pubname]['needAuth']){ + publishModelMap[pubname]['needAuth'] = true; + } } var msgObj = { timestamp: (new Date()).valueOf(), @@ -652,7 +667,7 @@ var runStub = function(db) { }; if (fw.config.get("rsa_enable")){ msgObj.swappk = fw.myrsa.getPk();//server传递给客户端自己的公钥,这句不加密 - } + }; setTimeout(function(){ netMessage.sendMessage(msgObj,'echo_from_server', socketId, function(err){ fw.log('send echo_from_server faile' + err); diff --git a/sumeru/server/sysUser.js b/sumeru/server/sysUser.js index be571e9..1049060 100644 --- a/sumeru/server/sysUser.js +++ b/sumeru/server/sysUser.js @@ -27,7 +27,7 @@ var arrPop= Array.prototype.pop; var prefix = __dirname + '/authMethod/'; // 帐户类型 -var authMethodList = [ "local" , 'baidu' , 'tpa']; +var authMethodList = [ "local" ]; var AccountHandler = {supportType:[]}; diff --git a/sumeru/src/auth.js b/sumeru/src/auth.js index 085e080..02aa6d4 100644 --- a/sumeru/src/auth.js +++ b/sumeru/src/auth.js @@ -7,7 +7,7 @@ var auth = new fw.utils.emitter(); var pkgAuth = fw.addSubPackage('auth'); - + var inited = false; // fw.utils.cpp(auth,fw.utils.emitter); var __emit = auth.emit; @@ -60,6 +60,11 @@ var sendAndCallback = function(msg,target,cb){ + if(inited == false){ + throw new Error("not inited"); + return; + } + var cbn = "WAITING_CALLBACK_" + fw.utils.randomStr(8); if(!cb || 'function' != typeof cb){ @@ -286,7 +291,7 @@ }else{ statusChange(null,NOT_LOGIN,null); } - + inited = true; cb && cb(); }); @@ -320,6 +325,9 @@ 'getStatus':getStatus, 'getLastError':getLastError, 'getUserInfo':getUserInfo, + 'isInited':function(){ + return inited; + }, // ------ 'isLogin':isLogin, 'getToken':getToken diff --git a/sumeru/src/authModel.js b/sumeru/src/authModel.js index 055478b..69f14fd 100644 --- a/sumeru/src/authModel.js +++ b/sumeru/src/authModel.js @@ -13,7 +13,7 @@ Model.smr_Authentication = function(exports){ {name: 'userId', type: 'string'}, // 与用户系统所关联的一个值,用于在用户系统中检索唯一用户 {name: 'info', type: 'object'}, // 附加信息,来源自用户系统返回的,希望在当前用户活动过程中被使用到的一些数据 {name: 'authMethod', type: 'string'}, // 当前的登陆类型, local|baidu|tpa - {name: 'expires', type: 'int'}, // 超时时间,当用到达指定时间时,当前登陆则超时. + {name: 'expires', type: 'number'}, // 超时时间,当用到达指定时间时,当前登陆则超时. {name: 'status', type: 'string', defaultValue : 'offline'}, // 标记当前在线或离线状态, online | offline {name: 'remoteAddr', type: 'string'} // 记录当前登陆IP,随status变化而记录最新值. ], diff --git a/sumeru/src/cache.js b/sumeru/src/cache.js new file mode 100644 index 0000000..4464ae5 --- /dev/null +++ b/sumeru/src/cache.js @@ -0,0 +1,100 @@ +var runnable = function(fw){ + _CACHEKEY = '_sumeru_pubsub_caches'; + /** + * 这是一个对localStorage的包装,用于处理以下两个问题 + * 1、超出存储限制,自动删除优先级别低的数据。数据优先级的计算方式:开发者配置/app的前20个订阅数据, + * 2、数据编码存储机制 + * + */ + var cache = fw.addSubPackage('cache'); + + //当存储时如果遇到name为 QuotaExceededError,catch到回收部分cache + var _clearCache = function(f){ + var gaplen = f?fw.config.get('pubcachegap'):0;//没有参数时,表示不超出,pubcache就不删除 + var keylist = localStorage.getItem(_CACHEKEY); + if(typeof keylist == 'String'){ + keylist = keylist.split(','); + }else{ + keylist = []; + } + + var len = keylist.length - 20 - fw.config.get('pubcachenum'); + len = Math.max(gaplen,len); + + var delList = keylist.splice(20,len); + + Array.prototype.forEach.call(delList,function(item){ + localStorage.removeItem(item); + }); + + localStorage.setItem(_CACHEKEY,keylist.join(',')); + }; + var set = function(key,value){ + if(fw.IS_SUMERU_SERVER){ + return; + } + if(fw.config.get('pubcache') !== true)return; + var keylist = localStorage.getItem(_CACHEKEY); + //keylist。优先保存前20个数据,如有修改就替换之,这20条数据永不删除。 + if(typeof keylist == 'string'){ + keylist = keylist.split(','); + }else{ + keylist = []; + } + var index = keylist.indexOf(key); + if(index>=0){ + //不在前20的key刷新位置 + if(index>=20){ + keylist.splice(index,1); + keylist.push(key); + } + }else{ + keylist.push(key); + } + + var save = function(){ + _clearCache(); + try{ + localStorage.setItem(_CACHEKEY,keylist.join(',')); + localStorage.setItem(key,value); + }catch(e){ + if(e.name.toLowerCase() == 'quotaexceedederror'){ + _clearCache(true); + save(); + } + } + } + save(); + }; + var setPubData = function(pubname,args,value){ + if(fw.IS_SUMERU_SERVER){ + return; + } + if(!sumeru.pubsub._publishModelMap[pubname.replace(/@@_sumeru_@@_page_([\d]+)/, '')]['needAuth']){ + var key = pubname+((args instanceof Array)?'_'+args.join('_'):''); + set(key,value); + } + }; + var getPubData = function(pubname,args){ + if(fw.IS_SUMERU_SERVER){ + return; + } + var key = pubname+((args instanceof Array)?'_'+args.join('_'):''); + return get(key); + }; + var get = function(key){ + if(fw.IS_SUMERU_SERVER){ + return; + } + return localStorage.getItem(key); + }; + cache.__reg('get', get); + cache.__reg('set', set); + cache.__reg('setPubData', setPubData); + cache.__reg('getPubData', getPubData); +}; +if(typeof module !='undefined' && module.exports){ + module.exports = runnable; +}else{//这里是前端 + runnable(sumeru); +} \ No newline at end of file diff --git a/sumeru/src/collection.js b/sumeru/src/collection.js index 25f608c..b7ae572 100644 --- a/sumeru/src/collection.js +++ b/sumeru/src/collection.js @@ -639,23 +639,10 @@ var runnable = function(fw){ return instance; }; - - - /** - * for developer , storable - */ fw.collection.__reg('create', function(def,dataMap){ - var coll = __collectionFactory(def,dataMap); - coll._setStorable(); - return coll; - }); - - /** - * for framework, unstorable - */ - fw.collection.__reg('_create', function(def,dataMap){ return __collectionFactory(def,dataMap); }); + } if(typeof module !='undefined' && module.exports){ module.exports = runnable; diff --git a/sumeru/src/controller.js b/sumeru/src/controller.js index 0001ee1..943ba34 100644 --- a/sumeru/src/controller.js +++ b/sumeru/src/controller.js @@ -10,6 +10,33 @@ var runnable = function(fw){ var transitionOut = false;//如果为true,则表明此controller是反转出场 var globalIsforce = null; + function updateBlockHtml(element, html, opts) { + + if(element.innerHTML == html) + { + return; + } + + var options = Library.objUtils.extend({}, { + domdiff : false + }, opts); + + Library.touch.trigger(element, 'block_before_render'); + + if (options.domdiff) { + + element.innerHTML = element.innerHTML; + fw.domdiff.convert(html, element); + + } else { + + element.innerHTML = html; + + } + + Library.touch.trigger(element, 'block_after_render'); + } + /** * 全局的模板ID => 数据的Map */ @@ -193,9 +220,6 @@ var runnable = function(fw){ if (this.isWaiting > 0)//hack throw 'NOT call env.start after ' + after / 1000 + ' seconds, did you forget it?'; }, after); - // this.isWaitingChecker = setTimeout(function(){ - // throw 'NOT call env.start after ' + after / 1000 + ' seconds, do you forget it?'; - // }, after); }, clearClockChecker : function(){ @@ -383,7 +407,8 @@ var runnable = function(fw){ //将标记的tpl-role=page_unit换为page-unit-rendered-page html = html.replace(/tpl-role[\s]*=[\s]*['"]page_unit['"]/, '__page-unit-rendered-page="' + page + '"'); - target.innerHTML = html; + //target.innerHTML = html; + updateBlockHtml(target, html); } else { var fakeNode = document.createElement('div'); fakeNode.innerHTML = me.__templateBlocks[item](data); @@ -440,11 +465,16 @@ var runnable = function(fw){ for(var i = 0, l = targets.length; i < l; i++){ var target = targets[i]; - if(increment&&typeof nodomdiff== 'undefined'){ - target.innerHTML = target.innerHTML; - fw.domdiff.convert(me.__templateBlocks[item](data),target); + if(increment&&fw.config.get('domdiff')){ + //target.innerHTML = target.innerHTML; + //fw.domdiff.convert(me.__templateBlocks[item](data),target); + + updateBlockHtml(target, me.__templateBlocks[item](data), { domdiff:true }); + }else{ - target.innerHTML = me.__templateBlocks[item](data); + //target.innerHTML = me.__templateBlocks[item](data); + + updateBlockHtml(target, me.__templateBlocks[item](data)); } } @@ -471,11 +501,16 @@ var runnable = function(fw){ var target = targets[x]; - if(increment&&typeof nodomdiff== 'undefined'){ - target.innerHTML = target.innerHTML; - fw.domdiff.convert(me.__templateBlocks[i](data),target); + if(increment&&fw.config.get('domdiff')){ + //target.innerHTML = target.innerHTML; + //fw.domdiff.convert(me.__templateBlocks[i](data),target); + + updateBlockHtml(target, me.__templateBlocks[item](data), { domdiff:true }); + }else{ - target.innerHTML = me.__templateBlocks[i](data); + //target.innerHTML = me.__templateBlocks[i](data); + + updateBlockHtml(target, me.__templateBlocks[item](data)); } } @@ -764,7 +799,9 @@ var runnable = function(fw){ var targets = queryElementsByTplId(i); for(var x = 0, y = targets.length; x < y; x++){ var target = targets[x]; - target.innerHTML = me.__templateBlocks[i]({}); + //target.innerHTML = me.__templateBlocks[i]({}); + + updateBlockHtml(target, me.__templateBlocks[i]({})); } }); @@ -921,7 +958,9 @@ var runnable = function(fw){ for(var x = 0, y = targets.length; x < y; x++){ var target = targets[x]; if (!target.innerHTML){//如果里面有内容,其实我并不需要清空它 - target.innerHTML = me.__templateBlocks[i]({}); + //target.innerHTML = me.__templateBlocks[i]({}); + + updateBlockHtml(target, me.__templateBlocks[i]({})); } } }); @@ -1125,7 +1164,7 @@ var runnable = function(fw){ }; } _bindingMap[widgetId].eventMap['__default_key__'] = function(){}; - } + }; var serverController = function(id, clientId , constructor){ var me = this; var env , session; diff --git a/sumeru/src/domdiff.js b/sumeru/src/domdiff.js index 5f25cb9..a5f73e2 100644 --- a/sumeru/src/domdiff.js +++ b/sumeru/src/domdiff.js @@ -95,6 +95,18 @@ pos = route.splice(0,1)[0]; } return e; + }, + + delcomment: function(e){ + if(e.nodeType==8){ + e.parentNode.removeChild(e); + }else{ + var len = e.childNodes.length-1; + while(len>=0){ + util.delcomment(e.childNodes[len]); + len--; + } + } } } @@ -324,10 +336,18 @@ var d1 = util.make(targetElement.nodeName,newhtml),d2 = targetElement; + //删除需要diff的dom中的注释。 + util.delcomment(d1); + util.delcomment(d2); + var routes = getDiff(d1,d2), route, iroute, d, lastRoute = routes.length, v; if(lastRoute===1 && routes[0]===0) { return false; } + if(lastRoute>fw.config.get('domdiffnum')){ + targetElement.innerHTML = newhtml; + return false; + } for(d = 0; d < lastRoute; d++) { diff --git a/sumeru/src/external.js b/sumeru/src/external.js index ebd1ad9..757586e 100644 --- a/sumeru/src/external.js +++ b/sumeru/src/external.js @@ -1,5 +1,5 @@ "use strict"; -var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serverObjectId, url){ +var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, https, serverObjectId, url){ //package var external = fw.addSubPackage('external'); @@ -72,7 +72,7 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv }); if(ret.length > 1){ - fw.log("ERROR: uniqueColume data is not unique, it may cause error. "); + fw.log("ERROR: uniqueColumn data is not unique, it may cause error. "); } if(ret.length){ @@ -96,8 +96,10 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv var chunks = []; var size = 0; - - var getRequest = http.get(url, function(res){ + var urlObj = urlParser && urlParser.parse(url); + var mode = urlObj.protocol === "https:" ? https : http; + + var getRequest = mode.get(url, function(res){ var data = null; @@ -135,16 +137,30 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv //error handler getRequest.on('error', function(err){ - fw.log("Error when do external fetch", url); + var errMsg = "Error when do external fetch"; + fw.log(errMsg, url); errorHandler && errorHandler(err); - cb([]); + var errInfo = { + errMsg : errMsg, + errUrl : url, + err : err, + __smrerr__ : true + }; + cb(errInfo); }); //timeout handler getRequest.setTimeout( REQUEST_TIMEOUT, function(info){ - fw.log("Timeout when do external fetch", url); + var timeoutMsg = "Timeout when do external fetch"; + fw.log(timeoutMsg, url); timeoutHandler && timeoutHandler(); - cb([]); + var timeoutInfo = { + errMsg : timeoutMsg, + errUrl : url, + err : info, + __smrerr__ : true + }; + cb(timeoutInfo); }); } @@ -162,8 +178,9 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv var chunks = []; var size = 0; + var mode = options.protocol === "https:" ? https : http; - var postRequest = http.request(options, function(res){ + var postRequest = mode.request(options, function(res){ var data = null; @@ -204,18 +221,31 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv //error handler postRequest.on('error', function(err){ - fw.log("Error when do external post"); - options && postData && fw.log(options, postData); + var errMsg = "Error when do external post"; + fw.log(errMsg, options, postData); errorHandler && errorHandler(err); - cb([]); + var errInfo = { + errMsg : errMsg, + options : options, + requestBody : postData, + err : err, + __smrerr__ : true + }; + cb(errInfo); }); //timeout handler postRequest.setTimeout( REQUEST_TIMEOUT, function(){ - fw.log("Timeout when do external post"); - options && postData && fw.log(options, postData); + var timeoutMsg = "Timeout when do external post"; + fw.log(timeoutMsg, options, postData); timeoutHandler && timeoutHandler(); - cb([]); + var timeoutInfo = { + errMsg : timeoutMsg, + options : options, + requestBody : postData, + __smrerr__ : true + }; + cb(timeoutInfo); }); } @@ -253,7 +283,7 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv if(!config.resolve){ //强制有resolve函数 fw.log('Need resolve method for external fetch!'); return; - } + } try{ var remoteData = config.resolve(data); remoteData = Array.isArray(remoteData) ? remoteData : [remoteData]; @@ -326,6 +356,10 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv var _doSync = function(data){ + if(data.__smrerr__){ + callback([]); //TODO when DB cache hierarchy is done by susu + return; + } if(typeof remoteDataMgr[url] === "undefined"){ var firstFetch = true; } //首次抓取不必trigger_push var remoteData = _resolve(data, pubName, url); //处理原始数据 if(firstFetch){ @@ -359,6 +393,7 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv } var opts = { + protocol : postOptions.protocol, hostname : postOptions.hostname, path : postOptions.pathname, port : postOptions.port || 80, @@ -465,6 +500,7 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv handle : function(pack,target,conn) { var cbn = pack.cbn; var postData = encodeURIComponent(JSON.stringify(pack.postData)); + var buffer = pack.buffer; var defaultOptions = { method : 'POST', headers: { @@ -474,9 +510,9 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv }; var opts = Library.objUtils.extend(true, defaultOptions, pack.options); - //var opts = fw.utils.merge( pack.options, defaultOptions); _doPost(opts, postData, function(data){ + data = buffer ? data : data.toString(); fw.netMessage.sendMessage(data.toString(),cbn,conn._sumeru_socket_id); }); @@ -646,7 +682,7 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv onMessage : { target : cbn, overwrite: true, - once:true, + once: true, handle : function(data){ cb(data); } @@ -670,7 +706,7 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv * @param {Object} postData: post data sent to external server. * @param {Function} cb: post callback, result for; */ - function sendPostRequest(options, postData, cb){ + function sendPostRequest(options, postData, cb, buffer){ //server if(fw.IS_SUMERU_SERVER){ postData = encodeURIComponent(JSON.stringify(postData)); @@ -685,6 +721,7 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv var opts = Library.objUtils.extend(true, defaultOptions, options); _doPost(opts, postData, function(data){ + data = buffer ? data : data.toString(); cb(data); }); @@ -708,7 +745,8 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, serv fw.netMessage.sendMessage({ cbn : cbn, options : options, - postData : postData + postData : postData, + buffer : buffer }, "SEND_EXTERNAL_POST"); } diff --git a/sumeru/src/framework.js b/sumeru/src/framework.js index 20b55a1..6a37142 100644 --- a/sumeru/src/framework.js +++ b/sumeru/src/framework.js @@ -12,7 +12,7 @@ var clientId = null, - authMethod = cookie.getCookie('authMethod'); + authMethod = cookie.getCookie('authMethod'); if(!(clientId = cookie.getCookie('clientId'))){ var t = Date.now().toString(32); @@ -73,7 +73,7 @@ }; socket.onopen = function(){ - reachability.setStatus_(reachability.STATUS_CONNECTED); + reachability.setStatus_(reachability.STATUS_CONNECTOPEN); //发送链接标示符 var identifier = {}; @@ -181,13 +181,32 @@ }; fw.init = function(callback){ + + /** + * 假设网络始终处于连接状态: + 一个app只需要连接一次,断线重连时的socket重连以及消息重发由reachability来处理。 + transition.init也只需要做一次。 + */ if(!inited){ - __socketInit(0, callback); - }else{ - callback && callback(); + if(fw.config.get('pubcache')){ + __socketInit(0); + }else{ + __socketInit(0, function(){ + callback && callback(); + }); + } + //for offline + var publishModelMap = fw.cache.get('publishModelMap'); + if(publishModelMap){ + Library.objUtils.extend(sumeru.pubsub._publishModelMap, JSON.parse(publishModelMap)); + } + fw.transition._init(); + } + + if(fw.config.get('pubcache')){ + //目前的callback都是分发路由 + callback && callback(); } - fw.transition._init(); - //fw.Controller.__load('_load').apply(this, arguments); }; fw.reconnect = function(){ diff --git a/sumeru/src/frameworkConfig.js b/sumeru/src/frameworkConfig.js index 1943b10..78bb5b6 100644 --- a/sumeru/src/frameworkConfig.js +++ b/sumeru/src/frameworkConfig.js @@ -39,6 +39,15 @@ var globalConfig = function(fw){ serverValidation: true }); + + fw.config({ + pubcache:true,//true:缓存subscript数据 + pubcachenum: 200,//本地缓存subscript数据条数的上限 + pubcachegap: 5,//缓存超出时每次删除的条数。 + domdiff: true,//是否开启domdiff + domdiffnum: 20//domdiff阀值,超过这个数量,就直接覆盖innerHTML + }); + if (typeof location != 'undefined') { fw.config({ diff --git a/sumeru/src/messageDispatcher.js b/sumeru/src/messageDispatcher.js index f13e869..4923ba6 100644 --- a/sumeru/src/messageDispatcher.js +++ b/sumeru/src/messageDispatcher.js @@ -268,7 +268,11 @@ var runnable = function(fw){ } //a flag tells if data have been stored remotely - if(!isPlainStruct)collection._setSynced(true); + if(!isPlainStruct){ + collection._setSynced(true); + if(pubname!='auth-init') + fw.cache.setPubData(pubname,item.args,JSON.stringify(collection.getData())); + } } /* @@ -416,6 +420,7 @@ var runnable = function(fw){ * 2.添加了从服务器接收公钥 */ var onMessage_echo_from_server = function(data){ + fw.reachability.setStatus_(fw.reachability.STATUS_CONNECTED); var localTimeStamp = (new Date()).valueOf(); serverTimeStamp = data.timestamp; @@ -433,6 +438,7 @@ var runnable = function(fw){ fw.utils.__reg('getTimeStamp', getTimeStamp); Library.objUtils.extend(sumeru.pubsub._publishModelMap, data.pubmap); + fw.cache.set('publishModelMap',JSON.stringify(data.pubmap)); fw.netMessage.sendLocalMessage({}, 'after_echo_from_server'); }; diff --git a/sumeru/src/netMessage.js b/sumeru/src/netMessage.js index 5fd4984..90fe50a 100644 --- a/sumeru/src/netMessage.js +++ b/sumeru/src/netMessage.js @@ -16,7 +16,7 @@ * } * * default number : handleName - * "0" : "onLocalMessage", // 随便什么鬼地方去接收,但不从网络进行派发,当前由于client端存在这种使用情况.所以在此预留 + * "0" : "onLocalMessage", // 随便什么地方接收,但不从网络进行派发,当前由于client端存在这种使用情况.所以在此预留 * "100" : "onError", // 两侧通用为处理错误消息 * "200" : "onMessage", // 两侧能用为处理数据消息 * "300" : "onGlobalError", // to client only, 两端都存在,但只有client端目前存在使用场景 diff --git a/sumeru/src/package.js b/sumeru/src/package.js index e5deec8..7536b34 100644 --- a/sumeru/src/package.js +++ b/sumeru/src/package.js @@ -1 +1 @@ -sumeru.packages( 'newPkg.js', 'log.js', 'utils.js', 'event.js', '../library/rsa/sumeru-rsa.js', 'netMessage.js', 'sense.js', 'session.js', 'ObjectId.js', 'app.js', 'config.js', 'transition.js', 'pubsub.js', 'controller.js', 'library.js', '../library/obj.js', '../library/handlebars-1.0.0.beta.6.js', '../library/handlebars-helper.js', '../library/net.js', '../library/sockjs.js', '../library/string.js', '../library/screen.js', '../library/cookie.js', '../library/touch.js', '../library/domUtils.js', '../library/history.js', '../library/asyncCallbackHandler.js', 'query.js', 'validation.js', 'validationRules.js', 'model.js', 'modelPoll.js', 'collection.js', 'domdiff.js', 'dal.js', 'external.js', 'messageDispatcher.js', 'frameworkConfig.js', 'router.js', 'routeMgr.js', 'historyCache.js', 'reachability.js', 'writeBuffer.js', 'pilot.js', 'uri.js', 'framework.js', 'renderBase.js', 'render.js', 'COMMON_STATUS_CODE.js', 'authModel.js', 'auth.js', '../library/fileUploader.js', './assets/css/transition.css', './assets/css/animate.css' ); \ No newline at end of file +sumeru.packages( 'newPkg.js', 'log.js', 'utils.js', 'event.js', '../library/rsa/sumeru-rsa.js', 'netMessage.js', 'sense.js', 'session.js', 'ObjectId.js', 'app.js', 'config.js', 'transition.js', 'pubsub.js', 'controller.js', 'library.js', '../library/obj.js', '../library/handlebars-1.0.0.beta.6.js', '../library/handlebars-helper.js', '../library/net.js', '../library/sockjs.js', '../library/string.js', '../library/screen.js', '../library/cookie.js', '../library/touch.js', '../library/domUtils.js', '../library/history.js', '../library/asyncCallbackHandler.js', 'query.js', 'validation.js', 'validationRules.js', 'model.js', 'modelPoll.js', 'collection.js', 'domdiff.js', 'cache.js', 'dal.js', 'external.js', 'messageDispatcher.js', 'frameworkConfig.js', 'router.js', 'routeMgr.js', 'historyCache.js', 'reachability.js', 'writeBuffer.js', 'pilot.js', 'uri.js', 'framework.js', 'renderBase.js', 'render.js', 'COMMON_STATUS_CODE.js', 'authModel.js', 'auth.js', '../library/fileUploader.js', './assets/css/transition.css', './assets/css/animate.css' ); \ No newline at end of file diff --git a/sumeru/src/pilot.js b/sumeru/src/pilot.js index 253f1ac..61c9af1 100644 --- a/sumeru/src/pilot.js +++ b/sumeru/src/pilot.js @@ -28,6 +28,9 @@ var runnable = function(sumeru){ } var getPilot = function(pilotid){ + if(fw.IS_SUMERU_SERVER){ + return; + } return pilot[pilotid]; } diff --git a/sumeru/src/pubsub.js b/sumeru/src/pubsub.js index 6b02e56..0b749dc 100644 --- a/sumeru/src/pubsub.js +++ b/sumeru/src/pubsub.js @@ -28,15 +28,28 @@ var runnable = function(fw,PublishContainer){ env = this ;//this是env env.wait();//自动调用wait方法 } - - var collection = fw.collection.create({modelName : modelName}); - - //在collection上记了一下他是从哪个publish上来的 - collection.pubName = pubName; - + var completeCallback = arrPop.call(arguments); var args = arrSlice.call(arguments,1); + var collection; + var cache = fw.cache.getPubData(pubName,args); + if(cache!=null){ + try{ + cache = JSON.parse(cache); + collection = fw.collection.create({modelName : modelName}, cache); + collection.pubName = pubName; + }catch(e){ + collection = fw.collection.create({modelName : modelName}); + collection.pubName = pubName; + } + }else{ + collection = fw.collection.create({modelName : modelName}); + collection.pubName = pubName; + } + + //在collection上记了一下他是从哪个publish上来的 + //send the subscribe netMessage var version = collection.getVersion(); var id = this.__UK; @@ -79,7 +92,7 @@ var runnable = function(fw,PublishContainer){ try{ completeCallback.apply(undefined,arguments); }catch(e){ - console.warn("error when pubsub callback on line 84 ",e); + console.warn("error when pubsub callback on line 84 \n" + e.stack || e); } if(env){ env.start();//自动调用start方法 @@ -91,6 +104,7 @@ var runnable = function(fw,PublishContainer){ collection : collection, callback : tmpfunc, callbackStr : callbackStr, + args : args, env : this }); @@ -109,7 +123,11 @@ var runnable = function(fw,PublishContainer){ },function(){ sumeru.dev("send subscribe " + pubName, version || 'no version'); }); - + //for offline render中会执行callback,里面会用到return的 collection,所以需要延迟执行。--jin + if(cache!=null){ + //collection.render(); + setTimeout((function(c){return function(){c.render()}})(collection),2); + } return collection; //when data received from server, will run the onComplete }, diff --git a/sumeru/src/reachability.js b/sumeru/src/reachability.js index aec4959..3047610 100644 --- a/sumeru/src/reachability.js +++ b/sumeru/src/reachability.js @@ -10,6 +10,7 @@ var runnable = function(sumeru){ var STATUS_OFFLINE = 0x00; var STATUS_CONNECTING = 0x10; + var STATUS_CONNECTOPEN = 0x11; var STATUS_CONNECTED = 0x100; var TYPE_WIFI = 0x01; @@ -33,6 +34,8 @@ var runnable = function(sumeru){ functionstack.offline && functionstack.offline(); }else if(status === STATUS_CONNECTING) { functionstack.connecting && functionstack.connecting(); + }else if(status === STATUS_CONNECTOPEN) { + functionstack.connectopen && functionstack.connectopen(); }else if(status === STATUS_CONNECTED) { functionstack.online && functionstack.online(); } @@ -52,6 +55,7 @@ var runnable = function(sumeru){ api.STATUS_OFFLINE = STATUS_OFFLINE; api.STATUS_CONNECTING = STATUS_CONNECTING; + api.STATUS_CONNECTOPEN = STATUS_CONNECTOPEN; api.STATUS_CONNECTED = STATUS_CONNECTED; //FIXME 完善在线功能,添加trigger online、offline方法。 //因为断线有两种可能,一种是与server中断(可能server故障),第二种是失去网络连接 diff --git a/sumeru/src/router.js b/sumeru/src/router.js index 994de4b..39bdd01 100644 --- a/sumeru/src/router.js +++ b/sumeru/src/router.js @@ -40,7 +40,8 @@ var SUMERU_ROUTER = SUMERU_ROUTER === undefined ? true : SUMERU_ROUTER; // var isInternalJoin = false; var isControllerChange = true, isParamsChange = true, isSessionChange = true; var lastController = null ,lastParams = null,lastSession = null,lastOneSession=null; - var isIgnore = false , isforce = false; + var isIgnore = false , /*?这里的isIgnore一直是false状态,用处是什么?*/ + isforce = false; var objToUrl = function(session){ var sessionObj = (typeof session == 'object') ?session:JSON.parse(session); diff --git a/sumeru/src/validationRules.js b/sumeru/src/validationRules.js index eece22a..3d50f14 100644 --- a/sumeru/src/validationRules.js +++ b/sumeru/src/validationRules.js @@ -81,12 +81,12 @@ var runnable = function(fw){ }); fw.validation.addrule("chinese" , { "runat":"both", - "regexp":"/^[\u4e00-\u9fa5]+$/", + "regexp":"^[\u4e00-\u9fa5]+$", "msg":"$1必须为中文。" }); fw.validation.addrule("url" , { "runat":"both", - "regexp":"/^[a-zA-z]:\\/\\/[^s]$/", + "regexp":"^[A-Za-z]+://[A-Za-z0-9-_]+\\.[A-Za-z0-9-_%&\?\/.=]+$", "msg":"$1必须为URL。" }); fw.validation.addrule("unique" , { diff --git a/sumeru/src/writeBuffer.js b/sumeru/src/writeBuffer.js index 3c64215..b6c1908 100644 --- a/sumeru/src/writeBuffer.js +++ b/sumeru/src/writeBuffer.js @@ -35,9 +35,20 @@ var runnable = function(sumeru){ }; var current; - + if(reachability.getStatus() == reachability.STATUS_CONNECTOPEN){ + for(var i=buffer.length-1;i>=0;i--){ + current = buffer[i]; + if(JSON.parse(current.msg).target == 'echo'){ + output_(current.msg, current.onError, current.onSuccess); + buffer.splice(i,1); + break; + } + } + + } + while(reachability.getStatus() == reachability.STATUS_CONNECTED - && (current = buffer.shift())){ + && (current = buffer.shift())){ output_(current.msg, current.onError, current.onSuccess); } } From d09438d5ebd02fc17c822489b15c0290da20e7a1 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Tue, 14 Jan 2014 16:45:32 +0800 Subject: [PATCH 12/24] 0.10.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1279fb3..8763358 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sumeru", - "version": "0.9.0", + "version": "0.10.0", "description": "A Realtime Javascript RIA Framework For Mobile WebApp", "main": "app.js", "scripts": { From a3badbf48a03ad69be71c939287fc0ad44a79ab2 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Mon, 17 Feb 2014 17:26:52 +0800 Subject: [PATCH 13/24] offline improve of auth.js and status code change in reachability; with bugfix of external.js with https:// --- sumeru/build/build.js | 2 +- sumeru/build/buildJavascript.js | 2 +- sumeru/src/auth.js | 45 ++++++++++++----- sumeru/src/eventStack.js | 86 +++++++++++++++++++++++++++++++++ sumeru/src/external.js | 6 +-- sumeru/src/netStatus.js | 60 +++++++++++++++++++++++ sumeru/src/package.js | 2 +- sumeru/src/pubsub.js | 2 +- sumeru/src/reachability.js | 61 +++++++---------------- sumeru/src/router.js | 11 ++++- 10 files changed, 213 insertions(+), 64 deletions(-) create mode 100644 sumeru/src/eventStack.js create mode 100644 sumeru/src/netStatus.js diff --git a/sumeru/build/build.js b/sumeru/build/build.js index 7331464..fb2b244 100644 --- a/sumeru/build/build.js +++ b/sumeru/build/build.js @@ -1,6 +1,6 @@ var path = require('path'); var fs = require('fs'); -var _debug = true; +var _debug = false; var baseDir = path.join(__dirname, '../../'); var sumeruDir = path.join(__dirname, '/../'); diff --git a/sumeru/build/buildJavascript.js b/sumeru/build/buildJavascript.js index bdda76f..d155393 100644 --- a/sumeru/build/buildJavascript.js +++ b/sumeru/build/buildJavascript.js @@ -5,7 +5,7 @@ */ var sumeru = require(__dirname + '/../src/newPkg.js')(); require(__dirname + '/../src/log.js')(sumeru); -var _debug = true; +var _debug = false; module.exports = function(sumeruDir, dstDir) { var fs = require('fs'); diff --git a/sumeru/src/auth.js b/sumeru/src/auth.js index 02aa6d4..4023ead 100644 --- a/sumeru/src/auth.js +++ b/sumeru/src/auth.js @@ -8,6 +8,16 @@ var pkgAuth = fw.addSubPackage('auth'); var inited = false; + var isOnline =fw.reachability ? fw.reachability.getStatus() == fw.reachability.STATUS_CONNECTED : false; + + fw.eventStack._on('online',function(){ + isOnline = true; + }); + + fw.eventStack._on('offline',function(){ + isOnline = false; + }); + // fw.utils.cpp(auth,fw.utils.emitter); var __emit = auth.emit; @@ -24,6 +34,19 @@ } }; + /** + * 检查网络和是否已初始化,如果未初始化则触发auth的error事件并返回false,否则返回true。 + */ + var checkWorking = function(cb, msg){ + return function(){ + if(isOnline && inited){ + return cb && cb.apply(null,arguments); + } + emit('error',new Error(msg || "unconnected network")); + return; + }; + }; + var netMessage = fw.netMessage; var cookie = Library.cookie; var pubsub = new fw.pubsub._pubsubObject(); @@ -181,7 +204,7 @@ }; var logout = function(){ - + authMethod = cookie.getCookie('authMethod'); // 在非登陆状态下,或cookie里没有authMethod,不能完成退出操作,直接退出 @@ -316,15 +339,15 @@ fw.utils.cpp(auth,{ - 'login':login, - 'logout':logout, - 'modifyUserInfo':modifyUserInfo, - 'modifyPassword':modifyPassword, - 'registerValidate':registerValidate, - 'register':register, - 'getStatus':getStatus, - 'getLastError':getLastError, - 'getUserInfo':getUserInfo, + 'login':checkWorking(login), + 'logout':checkWorking(logout), + 'modifyUserInfo':checkWorking(modifyUserInfo), + 'modifyPassword':checkWorking(modifyPassword), + 'registerValidate':checkWorking(registerValidate), + 'register':checkWorking(register), + 'getStatus':checkWorking(getStatus), + 'getLastError':checkWorking(getLastError), + 'getUserInfo':checkWorking(getUserInfo), 'isInited':function(){ return inited; }, @@ -348,4 +371,4 @@ // sumeru.auth.on('statusChange',function(err,status,userinfo){console.log(err,status,userinfo)}); return; -})(sumeru); \ No newline at end of file +})(sumeru); diff --git a/sumeru/src/eventStack.js b/sumeru/src/eventStack.js new file mode 100644 index 0000000..78aed38 --- /dev/null +++ b/sumeru/src/eventStack.js @@ -0,0 +1,86 @@ +/** + * eventStack + * @author jinjinyun@baidu.com + * usage: + var targetObj = window; + var onlineHandle = function(){console.log('window online')}; + var offlineHandle = function(){console.log('window offline')}; + + sumeru.eventStack._on(targetObj,'online',onlineHandle); + sumeru.eventStack._trigger(targetObj,'online'); + sumeru.eventStack._off(targetObj,'online',onlineHandle); + sumeru.eventStack._trigger(targetObj,'online'); + + 当targetObj 省略时 targetObj == sumeru + + sumeru.eventStack._on('online',onlineHandle); + sumeru.eventStack._on('offline',offlineHandle); + */ + + +var runnable = function(sumeru){ + if(sumeru.eventStack){ + return; + } + //func将附加到target上,stackname是其在target上的属性名。 + var STACKNAME ='_smr_eventFuncStack'; + + var api = sumeru.addSubPackage('eventStack'); + + //on是去重的。 + var _on = function(target,eventName,func){ + if(arguments.length==2){ + var func = arguments[1]; + var eventName = arguments[0]; + var target = sumeru; + } + target[STACKNAME] = target[STACKNAME]||{}; + target[STACKNAME][eventName] = target[STACKNAME][eventName]||[]; + var hasIt = false; + Array.prototype.forEach.call(target[STACKNAME][eventName],function(item){ + if(item===func){ + hasIt = true; + } + }) + if(!hasIt)target[STACKNAME][eventName].push(func); + }; + var _off = function(target,eventName,func){ + if(arguments.length==2){ + var func = arguments[1]; + var eventName = arguments[0]; + var target = sumeru; + } + var funcs,p,len,i; + if(target[STACKNAME]&&target[STACKNAME][eventName]){ + funcs = target[STACKNAME][eventName]; + len = funcs.length; + for(i=0;i Date: Mon, 17 Feb 2014 17:37:50 +0800 Subject: [PATCH 14/24] typo in external.js & whitelist of pubsub cache --- sumeru/src/cache.js | 4 +++- sumeru/src/external.js | 12 ++++++++---- sumeru/src/frameworkConfig.js | 1 + sumeru/src/messageDispatcher.js | 1 - sumeru/src/netStatus.js | 6 +++--- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/sumeru/src/cache.js b/sumeru/src/cache.js index 4464ae5..417c148 100644 --- a/sumeru/src/cache.js +++ b/sumeru/src/cache.js @@ -70,7 +70,9 @@ var runnable = function(fw){ if(fw.IS_SUMERU_SERVER){ return; } - if(!sumeru.pubsub._publishModelMap[pubname.replace(/@@_sumeru_@@_page_([\d]+)/, '')]['needAuth']){ + + if(pubname&&fw.config.get('pubcacheexcept').indexOf(pubname)<0)//过滤掉不需要缓存的publish + if(!fw.pubsub._publishModelMap[pubname.replace(/@@_sumeru_@@_page_([\d]+)/, '')]['needAuth']){ var key = pubname+((args instanceof Array)?'_'+args.join('_'):''); set(key,value); } diff --git a/sumeru/src/external.js b/sumeru/src/external.js index dcee205..9213e19 100644 --- a/sumeru/src/external.js +++ b/sumeru/src/external.js @@ -97,8 +97,10 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, http var chunks = []; var size = 0; var urlObj = urlParser && urlParser.parse(url); - var mode = urlObj.protocol === "https:" ? https : http; - + var mode = urlObj.protocol === "https" ? https : http; + if(urlObj && urlObj.protocol){ + delete urlObj.protocol; + } var getRequest = mode.get(url, function(res){ var data = null; @@ -178,8 +180,10 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, http var chunks = []; var size = 0; - var mode = options.protocol === "https:" ? https : http; - + var mode = options.protocol === "https" ? https : http; + if(options && options.protocol){ + delete options.protocol; + } var postRequest = mode.request(options, function(res){ var data = null; diff --git a/sumeru/src/frameworkConfig.js b/sumeru/src/frameworkConfig.js index 78bb5b6..8470543 100644 --- a/sumeru/src/frameworkConfig.js +++ b/sumeru/src/frameworkConfig.js @@ -44,6 +44,7 @@ var globalConfig = function(fw){ pubcache:true,//true:缓存subscript数据 pubcachenum: 200,//本地缓存subscript数据条数的上限 pubcachegap: 5,//缓存超出时每次删除的条数。 + pubcacheexcept:['auth-init'],//定义不缓存的部分 domdiff: true,//是否开启domdiff domdiffnum: 20//domdiff阀值,超过这个数量,就直接覆盖innerHTML }); diff --git a/sumeru/src/messageDispatcher.js b/sumeru/src/messageDispatcher.js index 4923ba6..3495a74 100644 --- a/sumeru/src/messageDispatcher.js +++ b/sumeru/src/messageDispatcher.js @@ -270,7 +270,6 @@ var runnable = function(fw){ //a flag tells if data have been stored remotely if(!isPlainStruct){ collection._setSynced(true); - if(pubname!='auth-init') fw.cache.setPubData(pubname,item.args,JSON.stringify(collection.getData())); } } diff --git a/sumeru/src/netStatus.js b/sumeru/src/netStatus.js index 0976b4a..f8267c8 100644 --- a/sumeru/src/netStatus.js +++ b/sumeru/src/netStatus.js @@ -46,9 +46,9 @@ var runnable = function(sumeru){ window.addEventListener("offline", offlineHandle, false); window.addEventListener("online", onlineHandle, false); - api.__reg('info', getNetworkInfo, 'publish'); - api.__reg('setStatus_', setStatus_, 'publish'); - api.__reg('getStatus_', getStatus_, 'publish'); + api.__reg('info', getNetworkInfo, 'private'); + api.__reg('setStatus_', setStatus_, 'private'); + api.__reg('getStatus_', getStatus_, 'private'); } if(typeof module !='undefined' && module.exports){ From 54f3fdbf9bcb4fab7eacd114307a40a2b6f7a3d7 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Mon, 17 Feb 2014 18:24:30 +0800 Subject: [PATCH 15/24] reject pull request c33cdeb & 4a8a647 --- sumeru/src/controller.js | 2 +- sumeru/src/external.js | 26 +++++++------------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/sumeru/src/controller.js b/sumeru/src/controller.js index 922bbd6..943ba34 100644 --- a/sumeru/src/controller.js +++ b/sumeru/src/controller.js @@ -239,7 +239,7 @@ var runnable = function(fw){ redirect:function(queryPath,paramMap,isforce){ var urlHash = queryPath; if(paramMap){ - urlHash += "?" + fw.utils.mapToUriParam(paramMap); + urlHash += "!" + fw.utils.mapToUriParam(paramMap); } fw.router.redirect(urlHash,isforce); }, diff --git a/sumeru/src/external.js b/sumeru/src/external.js index b018249..9213e19 100644 --- a/sumeru/src/external.js +++ b/sumeru/src/external.js @@ -712,27 +712,15 @@ var runnable = function(fw, findDiff, publishBaseDir, externalConfig, http, http */ function sendPostRequest(options, postData, cb, buffer){ //server - var defaultOptions; if(fw.IS_SUMERU_SERVER){ - if (options && options.json === true) { - postData = JSON.stringify(postData); - defaultOptions = { - method : 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': postData.length - } - }; - } else { - postData = encodeURIComponent(JSON.stringify(postData)); - defaultOptions = { - method : 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length + postData = encodeURIComponent(JSON.stringify(postData)); + var defaultOptions = { + method : 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length } - }; - } + }; var opts = Library.objUtils.extend(true, defaultOptions, options); From 845aafa7147ea62cd6a0abb01237617cceebee34 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Mon, 17 Feb 2014 18:30:54 +0800 Subject: [PATCH 16/24] merge 4a8a647 --- sumeru/src/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sumeru/src/controller.js b/sumeru/src/controller.js index 943ba34..922bbd6 100644 --- a/sumeru/src/controller.js +++ b/sumeru/src/controller.js @@ -239,7 +239,7 @@ var runnable = function(fw){ redirect:function(queryPath,paramMap,isforce){ var urlHash = queryPath; if(paramMap){ - urlHash += "!" + fw.utils.mapToUriParam(paramMap); + urlHash += "?" + fw.utils.mapToUriParam(paramMap); } fw.router.redirect(urlHash,isforce); }, From 65063dacf859d429a7edd8de2a90f19cc3407728 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Tue, 18 Feb 2014 13:28:56 +0800 Subject: [PATCH 17/24] update all dependencies to latest --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8763358..f407733 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ ], "dependencies": { "mkdirp": "0.3.5", - "mongodb": "1.3.18", - "shelljs": "0.1.4", - "sockjs": "0.3.7", - "uglify-js": "2.3.6", - "redis" : "0.8.4", - "clean-css" : "1.0.12" + "mongodb": "1.3", + "shelljs": "0.2", + "sockjs": "0.3", + "uglify-js": "2.4", + "redis": "0.10", + "clean-css": "2.1" }, "devDependencies": { "mocha": "1.8.1", From 8417c043cb5b8ffc9183e1346119310025298517 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Tue, 18 Feb 2014 13:37:09 +0800 Subject: [PATCH 18/24] 0.10.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f407733..c207802 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sumeru", - "version": "0.10.0", + "version": "0.10.1", "description": "A Realtime Javascript RIA Framework For Mobile WebApp", "main": "app.js", "scripts": { From 48433ff114ee9499bf80aa9130884f76622af473 Mon Sep 17 00:00:00 2001 From: brandnewera Date: Wed, 19 Feb 2014 12:07:32 +0800 Subject: [PATCH 19/24] upgrade cleanCSS invoke bug --- sumeru/build/build.js | 2 +- sumeru/build/buildJavascript.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sumeru/build/build.js b/sumeru/build/build.js index fb2b244..55b0a3f 100644 --- a/sumeru/build/build.js +++ b/sumeru/build/build.js @@ -160,7 +160,7 @@ var buildAppResource = function(appDir, theBinDir){ //clean css var cleanCSS = require('clean-css'); - var packedAppCssContent = cleanCSS.process(buildAppCssContent); + var packedAppCssContent = new cleanCSS().minify(buildAppCssContent); if(typeof process.BAE !== 'undefined'){ diff --git a/sumeru/build/buildJavascript.js b/sumeru/build/buildJavascript.js index d155393..f47b3bd 100644 --- a/sumeru/build/buildJavascript.js +++ b/sumeru/build/buildJavascript.js @@ -95,7 +95,7 @@ module.exports = function(sumeruDir, dstDir) { //clean css var cleanCSS = require('clean-css'); - var packedCSS = cleanCSS.process(buildCSSEntireContent); + var packedCSS = new cleanCSS().minify(buildCSSEntireContent); //写入sumeru.js 和 sumeru.css文件 From 05522851198ab780dfcf2b1c55e15e0b48be60cf Mon Sep 17 00:00:00 2001 From: brandnewera Date: Wed, 19 Feb 2014 12:08:00 +0800 Subject: [PATCH 20/24] 0.10.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c207802..510f00b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sumeru", - "version": "0.10.1", + "version": "0.10.2", "description": "A Realtime Javascript RIA Framework For Mobile WebApp", "main": "app.js", "scripts": { From cd370393c3df26e84c893c24ec63d319f875242c Mon Sep 17 00:00:00 2001 From: brandnewera Date: Mon, 24 Feb 2014 23:33:46 +0800 Subject: [PATCH 21/24] identical to 0.10.2, issue a new version due to npm fetch failure --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 510f00b..6fd636a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sumeru", - "version": "0.10.2", + "version": "0.10.4", "description": "A Realtime Javascript RIA Framework For Mobile WebApp", "main": "app.js", "scripts": { From 39e74383f09f6e599f83af2a13c63dbbfc55a2b3 Mon Sep 17 00:00:00 2001 From: wangsu01 Date: Thu, 22 May 2014 16:50:44 +0800 Subject: [PATCH 22/24] =?UTF-8?q?=E5=88=A0=E9=99=A4server=E7=AB=AF?= =?UTF-8?q?=E5=A4=9A=E4=BD=99=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: wangsu01 --- sumeru/server/authServer.js | 492 ----------------------------- sumeru/server/clientSimulation.js | 32 -- sumeru/server/groupManager.js | 342 -------------------- sumeru/server/log2 | 0 sumeru/server/output.js | 16 - sumeru/server/pcsPollServer.js | 276 ---------------- sumeru/server/run_no_mongo.js | 509 ------------------------------ sumeru/server/tpaAdap.js | 62 ---- 8 files changed, 1729 deletions(-) delete mode 100644 sumeru/server/authServer.js delete mode 100644 sumeru/server/clientSimulation.js delete mode 100644 sumeru/server/groupManager.js delete mode 100644 sumeru/server/log2 delete mode 100644 sumeru/server/output.js delete mode 100644 sumeru/server/pcsPollServer.js delete mode 100644 sumeru/server/run_no_mongo.js delete mode 100644 sumeru/server/tpaAdap.js diff --git a/sumeru/server/authServer.js b/sumeru/server/authServer.js deleted file mode 100644 index 408b873..0000000 --- a/sumeru/server/authServer.js +++ /dev/null @@ -1,492 +0,0 @@ -//权限中心后台 -var crypto = require('crypto'); -var passportBaidu = require('./driver/passport.js'); - -module.exports = function(fw, getDbCollectionHandler, ObjectId){ - /** - * - * 第三方认证的适配器,现在固定为tpaAdap.js,目前由于对每个认证需要做一个publish,所以目前只支持一个第三方登陆. - * tpaAdap,默认情况下,是一个无效的内容但格式有效的适配器对像. - * 第三方适配器的开发中,应遵循引结构,其中: - * check方法,用于检测是否是正确的登陆状态, - * login方法,用于验证登陆, - * logout方法,用于登出, - */ - var tpaAdap = {}, - EnableOtherLogin = null; - try{ - /* - * 载入固定的适配器文件 - */ - tpaAdap = require('./tpaAdap.js')(fw,getDbCollectionHandler,ObjectId); - EnableOtherLogin = true; - }catch(e){ - /** - * 如果取不到接口或没有成功载入适配器文件,则停用第三方登陆 - */ - fw.log("ERR : authServer : Can not load tpaAdap.js, " + e); - EnableOtherLogin = false; - } - - var authstatus = {}; - var passportTypeList = {}; - var DEFAULT_EXPIRES = 15 * 24 * 3600 * 1000;//15天 - - authstatus.LOGOUT = '0';//未登录 - authstatus.LOGIN = '1';//已登陆k - authstatus.LOGIN_UNKNOW = '2';//登录用户名或密码错误 - authstatus.LOGIN_TIMEOUT = '3';//登陆过期 - authstatus.LOGIN_PASSWORD_UPDATE = '4';//密码更改 - authstatus.LOGIN_VERIFY_CODE = '5';//需要输入验证码 - - authstatus.UPSERT_SUCC = '10';//注册或更新成功 - authstatus.UPSERT_FAIL = '11';//注册或更新未知的失败 - authstatus.UPSERT_REPEAT = '12';//TOKEN 重复 - - passportTypeList.local = 'LOCAL'; - passportTypeList.baidu = 'BAIDU'; - /** - * FIXME - * 将开发者定义的第三方passportType发送到client端需要大量实现, - * 并且现在的authServer的实现需要为每一个第三方认证做一个publish, - * 所以修改server端认证实现,将之前的passportType由开发者定义简化为固定常量'tpa'. - * 在需要同时支持多个第三方帐号登陆时,需要重新考虑此部份的实现. - */ - passportTypeList.tpa = 'tpa'; - - var _cleanLogin = function(){ - getDbCollectionHandler('smrLoginModel',function(err, collection){ - if(!err){ - collection.find({}, function(ferr, data){ - if(!ferr){ - data.toArray(function(terr, result){ - if(!terr){ - result.forEach(function(record){ - var existedTime = Date.now() - record.lastRequestTime; - var expires = record.expires + 7 * 24 *3600 * 1000; - - if(existedTime > expires){ - collection.remove({clientId:record.clientId, sessionId:record.sessionId}, function(err, numberOfRemovedDocs) { - if(err){ - fw.log('Clean Login Record error'); - } - }); - } - }); - } - }); - } - }); - } - }); - }; - - fw.cleanLoginRecords = _cleanLogin; - - var _checkLogin = function(clientId, sessionId, passportType, callback){ - getDbCollectionHandler('smrLoginModel',function(err,loginDbCollection){ - loginDbCollection.findOne({sessionId: sessionId , clientId:clientId}, function(err, items){ - if(items){ - var userId = items.userId; - - /** - * FIXME BUG - * 目前的实现,只在登陆的时候才会更新lastRequestTime.  - * 如果前端页面不刷新则会产生帐号每24小时或4小时(视登陆类型不同而不同)超时, - * 从而无法保持长时间活动的Bug. - */ - var timeGap = Date.now() - items.lastRequestTime; - - if(passportType === passportTypeList.local){ - getDbCollectionHandler('smrAuthModel',function(err1,handler){ - handler.find({smr_id: userId}).toArray(function(err, subItems){ - if(subItems && subItems.length > 0){ - //re-product new sessionId. - var newSessionId = subItems[0].token + subItems[0].secretKey + subItems[0].password + items.time; - - var sha1 = crypto.createHash('sha1'); - sha1.update(newSessionId); - newSessionId = sha1.digest('hex'); - - //sessionId 超过一天视为过期 - if(timeGap > items.expires){ - callback(authstatus.LOGIN_TIMEOUT, subItems[0]); - //sessionid不一致的情况只有password更改的情况下才会出现。 - //目前只有本地账户才支持这一种情况。 - }else if(newSessionId !== sessionId){ - callback(authstatus.LOGIN_PASSWORD_UPDATE, subItems[0]); - //isLogin标志为未登录状态, 退出会设置isLogin为0 - }else if(items.isLogin !== authstatus.LOGIN){ - callback(authstatus.LOGOUT); - }else{ - callback(authstatus.LOGIN, subItems[0]); - } - }else{ - callback(authstatus.LOGOUT); - } - }); - }); - }else if(passportType === passportTypeList.baidu){ - //sessionId 超过一天视为过期 - var userinfo = { - token: items.token, - info: items.info - }; - if(timeGap > items.expires){ - callback(authstatus.LOGIN_TIMEOUT, userinfo); - }else if(items.isLogin !== authstatus.LOGIN){ - callback(authstatus.LOGOUT, userinfo); - }else{ - callback(authstatus.LOGIN, userinfo); - } - }else if(passportType === passportTypeList.tpa){ - var userinfo = { - token: items.token, - info: items.info - }; - - /** - * 此处使用第三方的check,登陆状态的缓存及验证控制均交由第三方控制. - */ - tpaAdap.checkAndKeepAlive(items,timeGap,function(err,userInfo){ - if(err == null){ - callback(authstatus.LOGIN, userinfo); - }else if(err === "LOGIN_TIMEOUT"){ - callback(authstatus.LOGIN_TIMEOUT, userinfo); - }else{ - callback(authstatus.LOGOUT, userinfo); - } - }); - }else{ - callback(authstatus.LOGOUT); - } - }else{ - callback(authstatus.LOGOUT); - } - }); - }); - }; - - fw.checkLogin = function(clientId, sessionId, passportType, callback){ - passportType = passportType || passportTypeList.local; - _checkLogin(clientId, sessionId, passportType, function(status, info){ - callback(status, info); - }); - }; - - fw.publish('smrLoginModel', 'auth-init', function(sessionId, clientId, passportType, callback){ - /** - * FIXME - * 此处引发的数据下发,会导致前端aut.getModel()得到不完整的数据. - * 在数据不完整的状态下,无法取到正确的passportType和info等信息. - * 此问题可能导至刷新页面等情况后,server端与client端共同得到错误数据和响应. - */ - if(sessionId && clientId){ - _checkLogin(clientId, sessionId, passportType, function(status, userInfo){ - if(status === authstatus.LOGIN){ - callback([{ - status: status, - clientId: clientId, - sessionId: sessionId, - token: userInfo.token, - info: userInfo.info, - passportType: passportType - }]); - }else{ - callback([{ status: status, clientId: clientId }]); - } - }); - }else{ - callback([{ clientId: clientId}]); - } - }); - - //登出,并将登陆记录中的标志位isLogin标志为 '0' - fw.publish('smrAuthModel', 'auth-logout', function(sessionId, clientId, callback){ - if(sessionId){ - var collection = this; - getDbCollectionHandler('smrLoginModel',function(err,handler){ - /** - * FIXME - * 此处的实现,在每次登陆时都会创建一条新的记录,在退出后则变成一条垃圾数据. - * 将会导至数据库中smrLoginModel表数据暴涨. - */ - handler.findOne({sessionId:sessionId},function(err,item){ - /** - * FIXME - * 由于不知道之前的实现为什么使用update而不是直接删除, - * 在未清楚实现的情况下,只需增加了查询smrLoginModel用于通知第三方认证退出的实现, - * 未尝试修复smrLoginModel数据暴涨的bug.保留update实现,在理清实现后,应采用删除. - * -- wangsu - */ - handler.update({sessionId : sessionId}, - {$set : {isLogin : authstatus.LOGOUT}}, - function(err, result){ - var logoutMsg = [{status: err ? authstatus.LOGIN : authstatus.LOGOUT}]; - /** - * FIXME - * 在登陆过程中,未对smrLoginModel保存passportType类型,此处无法辨识当前的登陆时类型是什么, - * 所以此处在启用第三方帐户的情况下,都将尝试通知第三方帐户登出. - * 所以在tpaAdap.js中logout的实现,需要增加是否为自身登户的辨识能力, - * 相对于修改authServer中的实现,此方法更容易实现,在login时userInfo增加标识信息即可. - * -- wangsu - */ - if(EnableOtherLogin && item && item.info){ - /* - * 通知第三方帐户退出. - * item.info为第三方在login时传回的userInfo. - */ - tpaAdap.logout(item.info,function(){ - callback && callback(logoutMsg); - }); - }else{ - callback && callback(logoutMsg); - } - }); - }); - }); - } - }); - - //登录,生成sessionId, 并保存于登录记录中。 - fw.publish('smrAuthModel', 'auth-login', - function(token, password, clientId, expires, callback){ - var collection = this; - - var sha1 = crypto.createHash('sha1'); - sha1.update(password); - password = sha1.digest('hex'); - collection.find({token: token, password: password}, function(err, items){ - if(items && items.length > 0){ - var user = items[0]; - var time = Date.now(); - var sessionId = token + user.secretKey + password + time; - - _cleanLogin(); - getDbCollectionHandler('smrLoginModel',function(err,handler){ - - sha1 = crypto.createHash('sha1'); - sha1.update(sessionId); - sessionId = sha1.digest('hex'); - - handler.insert({ - clientId: clientId, - token: token, - sessionId: sessionId, - time: time, - userId: user.smr_id, - lastRequestTime: time, - isLogin: authstatus.LOGIN, - expires: expires ? expires : DEFAULT_EXPIRES - }, function(err, item){ - if(!err){ - callback([{ - smr_id: user.smr_id, - status: authstatus.LOGIN, - sessionId: sessionId, - token: token, - info: user.info, - clientId: clientId, - passportType: passportTypeList.local - }]); - }else{ - callback([{status: authstatus.LOGIN_UNKNOW}]); - } - }); - }); - }else{ - callback([{status: authstatus.LOGIN_UNKNOW}]); - } - }) - }); - - //登录,生成sessionId, 并保存于登录记录中。 - fw.publish('smrAuthModel', 'auth-login-baidu', - function(clientId, token, password, vCodeStr, verifyCode, expires, callback){ - var collection = this; - _cleanLogin(); - passportBaidu.passportLogin(token, password, - {vcodestr: vCodeStr, verifycode: verifyCode}, function(baiduErr, userInfo){ - if(!baiduErr){ - var time = Date.now(); - getDbCollectionHandler('smrLoginModel',function(err, handler){ - var sessionId = token + fw.__random(12) + password + time; - var info = { - auth: userInfo.auth, - ptoken: userInfo.ptoken, - stoken: userInfo.stoken, - weakpass: userInfo.weakpass, - bduss: userInfo.bduss, - baiduid: userInfo.baiduid - }; - var sha1 = crypto.createHash('sha1'); - sha1.update(sessionId); - sessionId = sha1.digest('hex'); - - handler.insert({ - clientId: clientId, - token: token, - sessionId: sessionId, - time: time, - userId: userInfo._uid, - lastRequestTime: time, - info: info, - isLogin: authstatus.LOGIN, - expires: expires ? expires : DEFAULT_EXPIRES - }, function(err, item){ - if(!err){ - callback([{ - smr_id: userInfo.uid, - status: authstatus.LOGIN, - sessionId: sessionId, - token: token, - info: info, - clientId: clientId, - passportType: passportTypeList.baidu - }]); - }else{ - callback([{status: authstatus.LOGIN_UNKNOW}]); - } - }); - }); - }else{ - if(baiduErr.errno === 257){ - callback([{ - status: authstatus.LOGIN_VERIFY_CODE, - vCodeStr: baiduErr.vcodestr, - passportType: passportTypeList.baidu - }]); - }else{ - callback([{status: baiduErr.errno}]); - } - } - }); - }); - - /** - * - */ - if(EnableOtherLogin && tpaAdap){ - - fw.publish('smrAuthModel', 'other-login', function(token, password, argstr, clientId, callback){ - var sha1 = crypto.createHash('sha1'); - var time = Date.now() + ""; - var sessionId = null; - - sha1.update(token || ""); // 登陆名 - sha1.update(fw.__random(12)); // 随机串 - sha1.update(password || ""); // 密码 - sha1.update(time); // 登陆时间 - - sessionId = sha1.digest('hex'); - - tpaAdap.login(clientId,token,password,argstr,function(err,userInfo){ - - if(err){ - if(err === 5){ - callback([{ - status: authstatus.LOGIN_VERIFY_CODE, - vCodeStr:userInfo.v_url, - passportType: passportTypeList.tpa, - info : userInfo - }]); - }else{ - callback([{status: err}]); - } - return; - } - - getDbCollectionHandler('smrLoginModel',function(err, handler){ - var loginModel = { - smr_id : ObjectId(), //create db id. - clientId : clientId, - token : token, - sessionId : sessionId, - time : time, - userId : '', - lastRequestTime : time, - info : userInfo, - isLogin : authstatus.LOGIN - }; - - handler.insert(loginModel, function(err, item){ - if(!err){ - callback([{ - smr_id: item[0] && item[0].smr_id, - status: authstatus.LOGIN, - sessionId: sessionId, - token: token, - info: userInfo, - clientId: clientId, - passportType: passportTypeList.tpa - }]); - - }else if(err == 275 ){ - callback([{ - smr_id: '', - status: authstatus.LOGIN_VERIFY_CODE, - sessionId: '', - token: token, - info: userInfo, - clientId: clientId, - passportType: passportTypeList.tpa - }]); - }else{ - callback([{status: authstatus.LOGIN_UNKNOW}]); - } - }); - }); - - }); - }); - } - - //注册新用户 - fw.publish('smrAuthModel', 'auth-register', function(token, password, info, clientId, callback){ - var collection = this, - sha1 = crypto.createHash('sha1'); - - sha1.update(password); - password = sha1.digest('hex'); - - collection.find({token: token}, function(err, items){ - if(items && items.length <= 0){ - var user = { - smr_id: ObjectId(), - token: token, - password: password, - secretKey: fw.__random(8), - info: info - } - collection.insert(user, function(err, collection){ - delete user.password; - user.status = (err == null) ? - authstatus.UPSERT_SUCC : authstatus.UPSERT_FAIL; - callback([user]); - }); - }else{ - callback([{token: token, status: authstatus.UPSERT_REPEAT}]); - } - }); - }); - - //更新用户信息 - fw.publish('smrAuthModel', 'auth-update', function(sessionId, clientId, newinfo, callback){ - var collection = this, - sha1 = crypto.createHash('sha1'); - - - _checkLogin(clientId, sessionId, function(status, userinfo){ - if(status === authstatus.LOGIN){ - if('password' in newinfo){ - sha1.update(newinfo.password); - newinfo.password = sha1.digest('hex'); - } - collection.update({smr_id: userinfo.smr_id}, - {$set: newinfo}, function(err){ - callback([{status: !err ? authstatus.UPSERT_SUCC : authstatus.UPSERT_FAIL}]); - }); - } - }); - }); -} \ No newline at end of file diff --git a/sumeru/server/clientSimulation.js b/sumeru/server/clientSimulation.js deleted file mode 100644 index 9925110..0000000 --- a/sumeru/server/clientSimulation.js +++ /dev/null @@ -1,32 +0,0 @@ -var http = require("http"), - net = require("net"); - - -var option = { - port : 2012, - method : 'POST' -}; - -var req = http.request(option, function(res){ - console.log('STATUS: ' + res.statusCode); - console.log('HEADERS: ' + JSON.stringify(res.headers)); - res.setEncoding('utf8'); - res.on('data', function (chunk) { - console.log('BODY: ' + chunk); - }); -}); - -req.write('data\n'); -req.write('data\n'); -req.end(); - - -//tcp connection -var tcpClient = net.connect(2013, function(){ - tcpClient.write('test tcp'); -}); - -tcpClient.setEncoding('utf8'); -tcpClient.on('data', function(data){ - console.log(data); -}); diff --git a/sumeru/server/groupManager.js b/sumeru/server/groupManager.js deleted file mode 100644 index 00a236d..0000000 --- a/sumeru/server/groupManager.js +++ /dev/null @@ -1,342 +0,0 @@ -/** - * 集群通信 - * - * 负责将server在DB中注册,用于server间的消息通知。 - * 在数据库中,存储以下结构的数据 - * - * Server : { - * s_id:'' - * serverPort : [ - * { - * addr:'192.168.0.110', // server 的ip地址 - * port:8080 // 接受通知的socket端口 - * }, - * { - * addr:'172.21.236.110', - * port:8088 - * }, - * ], - * online : "", // 上线时间, 时间戳 Date.now() - * } - * - * 服务每次启动时,向db中注册自己的网络接口信息,在db中注册结点时, - * 会先删除自己上一次的注册信息,并在db中查询使用自己ip位置的其它冗余记录,一并将其删除,然后再插入当前的新信息。 - * 并向netMessage中创建S2SMessage的输出方法,使netMessage支持S2S的消息发送, - * 其它位置需要向集群中的其它结点发送消息时,使用netMessage.sendS2SMessage(msg,onerror,onsuccess)。 - * 在向其它结点发送信息时,将从db中查询当前注册的所有结点,并逐一向每个server发送消息. - * - */ -var net = require('net'); -var os = require('os'); -var fs = require('fs'); - -var collectionName = 'SMR_SERVER_GROUP'; -var Unique_Key = process.env.IP_ADDRESS && process.env.APP_PORT ? process.env.IP_ADDRESS + ":" + process.env.APP_PORT : undefined; -var defaultCharset = 'utf8'; -var listenPort = 8089; - -var runnable = function(fw, getDbCollectionHandler,ObjectId) { - - fw.netMessage || require(__dirname + '/../sumeru/netMessage.js')(fw); - - var netMessage = fw.netMessage; - /** - * 如果unique_key没有值,表明当前不是bae环境,evn中没有任何可依赖的值,进行从文件取职的操作 - */ - var lastUK = null; - var tmpDir = __dirname + '/tmp'; - var fGroupInfo = tmpDir + '/groupInfo._info'; - var HOST_NAME = os.hostname(); - var fscontent = ''; - if(Unique_Key == undefined){ - if(!fs.existsSync(tmpDir)){ - fs.mkdirSync(tmpDir); - } - - if(fs.existsSync(fGroupInfo)){ - fscontent = fs.readFileSync(fGroupInfo,defaultCharset); - // 确认是已本机的hostName开头,保证当前server未在server间copy迁移过,防止误删 - if(fscontent.indexOf(HOST_NAME)===0){ - lastUK = fscontent.split("1")[1]; - } - } - - Unique_Key = ObjectId(); - fs.writeFileSync(fGroupInfo, HOST_NAME + ":" + Unique_Key, defaultCharset); - }else{ - lastUK = Unique_Key; - } - - // 记录当前所在的server,派发消息时,用于跳过自身. - var __current = Unique_Key; - var registered = false; - - - - - //================================================ - - var server = net.createServer(); - - server.on('connection',function(conn){ - // 默认编码utf8,以文本方式传输内容 - conn.setEncoding(defaultCharset); - - conn.on('data',function(data){ - fw.dev('retrieving S2S : ' + data); - netMessage.onData(data,conn); - }); - - // 1秒超时 - conn.setTimeout(1000,function(){ - fw.dev('s2s connection timeout. Disconnected'); - conn.end(); - }); - }); - - server.on('error',function(){ - fw.log('S2S Err:' + arguments); - }); - - server.on('close',function(){ - fw.log('S2S Disconnected:' + arguments); - }); - - server.listen(listenPort,function(){ - fw.log('S2S listen on port 8089'); - }); - - process.on('exit',function(){ - unregister(__current); - fw.log('server shutdown..'); - }); - - //================================================ - - var __connMsgTo = function(port,msg,onerror,onsuccess){ - var client = net.createConnection({host:port.addr,port:port.port}); - var writed = false; - client.setEncoding(defaultCharset); - - client.setTimeout(1000,function(){ - fw.log('connection timeout.' , port); - // 如果到超时为止,消息未发送,则认为发送失败. - if(!writed){ - onerror && onerror(); - } - client.destroy(); - }); - - client.on('error',function(){ - fw.log(arguments); - onerror && onerror(); - }); - - client.on('connect',function(){ - client.write(msg); - client.destroy(); - onsuccess && onsuccess(); - writed = true; // 标记消息发送成功 - }); - - client.on('data',function(msg){ - fw.dev(msg); - }); - }; - - var connectionTo = function(connInfo, msg, onerr, onsuccess){ - var err = null, success = null; - var sps = connInfo.serverPort; - var i = 0; - - err = function(){ - var sp = sps[++i]; - if(sp){ - __connMsgTo(sp, msg, err, success); - }else{ - fw.log('group manager all failed. ' + JSON.stringify(sps)); - onerr && onerr(); - /* - * FIXME - * - * 此处似乎应该有一个完善的重试的机制和网络验证机制用于区分自身网络故障或目标网络故障, - * ==== - * 否则如果是目标结果网络闪断一次消息无法发出,会出现结点仍在运行但直到该结点重启否则再也不会接受到通知的情况出现。 - * ==== - * 最坏情况,如果当前结点网络断开,但对DB的连接仍可用的情况,可能会出现清空整个集群信息的情况而破坏整个集群的通信,导至重启整个集群。 - * - * 所以此处暂时先不清理集群信息. - */ - // unregister(connInfo.s_id); - } - }; - - success = function(){ - onsuccess && onsuccess(); - }; - - if(sps[i]){ - __connMsgTo(sps[i],msg,err,success); - } - }; - - /** - * 注册一个server结点. - * - * serNam - * 为当前server的名称. 在数据库中,应是唯一的。 - * - * serverPorts  - * 为当前server监听server间消息的网络接口 - * - * 当传入数组中只有一个addr值为0.0.0.0,认为是server不明确知道自身的ip地址, - * 只监听当前server中所有ip地址的port地址,即 listen{0.0.0.0:8080}, - * 方法自动从当前系统中读取所有非本地的ip地址进行记录。 - * - * 当传入数据长度大于1时,认为server明确知道自身的IP地址,此时限制每一项中的addr值不能为0.0.0.0 - * - * 任意情况下,port值必须为大于0的整数 - * - * register([{addr:'192.168.0.110',port:8080},{addr:172.16.236.110:80}]); - * - * @param serverPorts [{servePorts}] 当前Server运行的端口 - * - */ - var register = function(sps){ - - if(registered || !Array.isArray(sps)){ - fw.log('register failed : invalid arguments or repeating record'); - return; - } - - var len = sps.length, sp = null; - var save = { - Unique_Key : Unique_Key, - serverPort:[], - online:Date.now(), - status:{} - }; - - if(len === 0){ - throw 'ERR: sps.length == 0'; - } - - if(len === 1){ - sp = sps[0]; - - if(sp.port <= 0){ - throw 'ERR: port <= 0'; - } - - if(sp.addr === '0.0.0.0'){ - // 读取本机网卡信息 - var nifs = os.networkInterfaces(); - for(var key in nifs){ - nifs[key].forEach(function(item){ - // 只记录非internal的IPV4地址. - if(!item.internal && item.family === "IPv4"){ - save.serverPort.push({ - addr:item.address, - port:sp.port - }); - } - }); - } - } - }else{ - sps.forEach(function(item){ - if(item.port <= 0){ - throw 'ERR: port <= 0'; - } - - if(item.addr === '0.0.0.0'){ - throw 'addr can not be [0.0.0.0]'; - } - - save.serverPort.push({ - addr : item.addr, - port : item.port - }); - - }); - } - - /* - * 在db中去重,去重后,插入新的记录. - * - * 去重规则: - * 1.删去指向同一个server的其它记录. - * db中所记录的所有记录的serverPort中,如果存在任意ip与port相同,即认为指相同一server. - * 2.删除当前server上次的记录,无论是否相同 - * 查询tmp目录下的groupInfo._info,删除其中记录的id, - * 为防止在不同server间复制文件所以删除时先验证是否groupInfo中的hostName是当前server名称,如果不是,则不删除该项. - * - */ - getDbCollectionHandler(collectionName,function(err,collection){ - var removeItems = []; - save.serverPort.forEach(function(item){ - removeItems.push({serverPort:{addr:item.addr,port:item.port}}); - }); - - // 如果存在lastUK,证明上次启动时在DB中存在记录,需要删除 - if(lastUK){ - removeItems.push({Unique_Key:lastUK}); - } - - collection.remove({"$or":removeItems},function(){ - // 去重后,将新值插入数据库 - collection.save(save,function(){ - fw.log("\n==========\nServer Port : "); - fw.log(save); - fw.log("Server Port END\n==========\n"); - }); - }); - - }); - }; - - /** - * 将目标server离线,从数据库删除记录。 - */ - var unregister = function(id){ - getDbCollectionHandler(collectionName,function(err,collection){ - collection.remove({Unique_Key:id},function(err,collection){ - fw.dev('unregister : ' + id); - }); - }); - }; - - /** - * 外发消息至其它server结点的实际发送方法, - * - * FIXME - * 现在只实现了轮训式广播,未实现组播和单播,即target参数无用. - */ - netMessage.setOutputToServer(function(msg/* , target */, onerror, onsuccess){ - fw.dev("server to server : " + msg); - getDbCollectionHandler(collectionName,function(err,collection){ - collection.find({},{}).toArray(function(err,results){ - if(err){ - throw err; - } - results.forEach(function(item){ - // 跳过自己 - if(item.Unique_Key.toString() !== __current.toString()){ - connectionTo(item, msg,onerror,onsuccess); - } - }); - }); - }); - }); - - - return { - register : register, - unregister : unregister, - getServerId : function(){ - return __current; - } - }; -}; - -module.exports = runnable; \ No newline at end of file diff --git a/sumeru/server/log2 b/sumeru/server/log2 deleted file mode 100644 index e69de29..0000000 diff --git a/sumeru/server/output.js b/sumeru/server/output.js deleted file mode 100644 index 3d6134a..0000000 --- a/sumeru/server/output.js +++ /dev/null @@ -1,16 +0,0 @@ -var runnable =function(fw, getDbCollectionHandler,ObjectId){ - var output = fw.addSubPackage('output'); - var handlebars = require("handlebars"); - - var render = function(template,data){ - var pageBuilder = handlebars.compile(template); - return pageBuilder(data); - } - output.__reg('render', render, 'private'); -} - -if(typeof module !='undefined' && module.exports){ - module.exports = runnable; -}else{//这里是前端 - // runnable(sumeru); -} \ No newline at end of file diff --git a/sumeru/server/pcsPollServer.js b/sumeru/server/pcsPollServer.js deleted file mode 100644 index dcbf0cf..0000000 --- a/sumeru/server/pcsPollServer.js +++ /dev/null @@ -1,276 +0,0 @@ -//pcs 轮询服务 - -var pcs = require(__dirname + '/driver/pcs.js'); -var applog = require('./driver/applog.js'); -// var log = require(__dirname + '/../sumeru/log.js')(fw); - -module.exports = function(fw, getDbCollectionHandler){ - - - require(__dirname + '/../config/app-baidualbum.js')(fw); - var im = require('./driver/imageMeta.js')(fw); - - - var pcspoll = fw.config.defineModule('pcspoll'); - pcspoll({ - pcsInterval:1000,//pcs轮询时间间隔 - albumTableName:"pics",//轮询插入的model name - albumTimelineTableName:"picsTimeline",//轮询插入的model name - thumbnailHost:"127.0.0.1",//port - thumbnailPort:"2013",//port - }); - - //compfunc 为比较函数。用于比较两个item是否一致 - var inArray = function(array,item,compfunc){ - if(typeof compfunc == "undefined"){ - compfunc = function(a,b){ - return (a["path"] == b["path"] - &&a["ctime"] == b["ctime"]); - } - } - for(var i=0,ilen = array.length;i2){ - type = 0; - }else if(r>0.5){ - type = 1; - }else{ - type = 2; - } - return type; - } - - //将从pcs取出来的数据,格式化为要插入mongo DB的数据 - var formatPcsData = function(userinfo,value){ - var _obj = {}; - _obj["username"] = userinfo["token"]; - _obj["userid"] = value["user_id"]; - _obj["fs_id"] = value["fs_id"]; - _obj["ctime"] = value["ctime"]; - _obj["path"] = value["path"]; - _obj["filename"] = value["server_filename"]; - - _obj["thumbnail"] = getThumbnailURL(_obj["path"],userinfo,640,1000); - - _obj["size"] = value["size"]; - _obj["src"] = value["s3_handle"]; - //_obj["width"] = 0; - //_obj["height"] = 0; - return _obj; - } - - - //offset目前只支持d,m - var getDateFromTime = function(timestamp,offset){ - var d,dd; - d = new Date(timestamp*1000); - dd = new Date(d.getFullYear()+"-"+(d.getMonth()+1)+'-'+d.getDate());//去掉小时分钟和秒 - d = dd;//变换 - if (typeof offset != 'undefined' ){ //explain the offset - if (offset.substr(-1) == "m") { - d.setMonth((d.getMonth() - parseInt(offset) )); - }else{ - d.setDate((d.getDate() - (parseInt(offset)) ));//1day表示当天 - } - } - return d.getFullYear()+"-"+(d.getMonth()+1); - } - - //将全量的pcs抓取数据,转换成timeline数据 - var createTimelineData = function(username, data){ - var timeline = []; - //console.log("---------------------------------------"); - //console.log(data); - //console.log("---------------------------------------"); - for(var i=0, ilen = data.length;i=0){ - dbTimeline.update({_id:items[i]["_id"]},//{"userid":items[i]["userid"],"timename":items[i]["timename"]}, - {$set:{"newsize":parseInt(timelineData[_index]["size"]) - parseInt(items[i]["size"]), - "size":timelineData[_index]["size"], - "etime":timelineData[_index]["etime"] - }}); - - timelineData.splice(_index,1); - }else{ - dbTimeline.remove({_id:items[i]["_id"]}); - } - } - if(timelineData.length>0){ - for(var i=0,ilen = timelineData.length;i=removeList.length - && - insertedLen>=newData.length){ - /*log.write("trigger model push++++"); - log.write("removedLen:"+removedLen); - log.write("insertedLen:"+insertedLen); - log.write("removeList.length:"+removeList.length); - log.write("newData.length:"+newData.length);*/ - callback(); - } - } - - //diff新老数据 - for(var i=0,ilen = items.length;i=0){ - dbPics.update({_id:items[i]["_id"]},//{"userid":items[i]["userid"],"timename":items[i]["timename"]}, - {$set:{"thumbnail":getThumbnailURL(newData[_index]["path"],userinfo,640,1000), - "src":newData[_index]["s3_handle"] - }}); - - newData.splice(_index,1); - }else{ - removeList.push({_id:items[i]["_id"]}); - dbPics.remove({_id:items[i]["_id"]},{safe:true},function(){ - _callback("insert"); - }); - } - } - - if(newData.length>0){ - - /*log.write("dbitems:"+items.length); - log.write("newData:"+newData.length); - log.write(items[0]); - log.write(newData[0]);*/ - for(var i=0,ilen = newData.length;i Date: Sat, 2 Aug 2014 07:37:16 +0800 Subject: [PATCH 23/24] Update README.md --- README.md | 85 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 91ea2ce..b46bdcf 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,7 @@ -[![Build Status](https://secure.travis-ci.org/brandnewera/clouda.png)](http://travis-ci.org/brandnewera/clouda) -[![Dependency Status](https://gemnasium.com/brandnewera/clouda.png)](https://gemnasium.com/brandnewera/clouda) -[![Coverage Status](https://coveralls.io/repos/brandnewera/clouda/badge.png)](https://coveralls.io/r/brandnewera/clouda) +## Clouda现已全面升级 +原云端一体框架Cloudajs(Sumeru)更名为(RapidJS)[https://github.com/Clouda-team/rapid-core],全面升级后,变得更加灵巧优雅。 -##What is Sumeru Framework? +我们更添加了(BlendUI)[https://github.com/Clouda-team/BlendUI]、(Blend API)[https://github.com/Clouda-team/BlendAPI]和Runtime到Clouda+家族中,让移动端的能力和交互得到质的飞跃。 - - -Sumeru Framework, a simple but powerful Web App framework, provides rich functionalities for building de-facto Web Applications based on cloud technology. Sumeru framework offers awesome features such as data-unify, adaptive feedback, realtime network connection etc. Developers benefits from Sumeru Framework in terms of coding efforts, performance and application distribution. - -## Live Demo: Manage To-Do Lists Across Multiple Devices - -![](docs/images/devices.png) - - -[Click and use multiple devices/browsers simultaneously to tryout the live demo with realtime sync of To-Do Lists](http://sumerutodolist.duapp.com) - - - -[Click to see a live video ====>](http://v.youku.com/v_show/id_XNTI5NzcxNTcy.html) -![](docs/images/youkuvideo.png) - - - - -##How to install and run sumeru - -### Install - - - npm install -g sumeru - -### Init a sumeru project - - sumeru init ./myproject - -### Run sumeru - - cd myproject - - sumeru start - - - -##How to update existing installation to the lastest version - -### Check current installed version - - npm ls -g sumeru - -or - - grep '"version"' /usr/local/lib/node_modules/sumeru/package.json - - -### Check the up-to-date version number - - npm view sumeru version - -### Update sumeru - - npm update -g sumeru - -### Update existing project to use the lastest version (with auto backup, of course) - - sumeru update ./myproject - - or - - sudo sumeru update ./myproject - - - -## Documents - - -Step1: [Getting Started](https://github.com/brandnewera/sumeru/blob/master/docs/step1_getting_started.md) - -Step2: [Your First App](https://github.com/brandnewera/sumeru/blob/master/docs/step2_your_first_app.md) - -Step3: [Overview](https://github.com/brandnewera/sumeru/blob/master/docs/step3_overview.md) - -Step4: [API Documentation](https://github.com/brandnewera/sumeru/blob/master/docs/step4_API_Documentation.md) +我们的官网是: http://clouda.com From cc81ad0a2bf1a9858faefc4c47539df13ac55f1a Mon Sep 17 00:00:00 2001 From: Berg Date: Sat, 2 Aug 2014 07:38:15 +0800 Subject: [PATCH 24/24] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b46bdcf..20df80f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Clouda现已全面升级 -原云端一体框架Cloudajs(Sumeru)更名为(RapidJS)[https://github.com/Clouda-team/rapid-core],全面升级后,变得更加灵巧优雅。 +原云端一体框架Cloudajs(Sumeru)更名为[RapidJS](https://github.com/Clouda-team/rapid-core),全面升级后,变得更加灵巧优雅。 -我们更添加了(BlendUI)[https://github.com/Clouda-team/BlendUI]、(Blend API)[https://github.com/Clouda-team/BlendAPI]和Runtime到Clouda+家族中,让移动端的能力和交互得到质的飞跃。 +我们更添加了[BlendUI](https://github.com/Clouda-team/BlendUI)、[Blend API](https://github.com/Clouda-team/BlendAPI)和Runtime到Clouda+家族中,让移动端的能力和交互得到质的飞跃。 我们的官网是: http://clouda.com