├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── app.nw ├── README.md ├── angular.min.js ├── angularfire.min.js ├── bootstrap.min.css ├── brise.coffee ├── brise.html ├── credits.html ├── firebase.js ├── index.html ├── package.json ├── rimekit.coffee ├── rimekit.css ├── sadebugger.coffee ├── sadebugger.html └── ui-bootstrap-tpls.min.js ├── package.json ├── rime ├── algebra.coffee ├── config.coffee ├── customizer.coffee ├── promise.coffee ├── recipe.coffee ├── sandbox.coffee └── table.coffee ├── sample ├── custom_phrase.recipe.coffee ├── dungfungpuo.recipe.coffee ├── horizontal_layout.recipe.coffee ├── run.coffee └── slash_symbols.recipe.coffee └── test ├── .placeholder ├── test-customizer.coffee └── test-recipe.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | **/node_modules/ 3 | .idea 4 | /app.nw/rime.js 5 | /app.nw/rimekit.js 6 | /test/download/ 7 | /sample/download/ 8 | /sample/*.yaml 9 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var exec = require('child_process').exec; 3 | module.exports = function (grunt) { 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | watch: { 8 | run: { 9 | files: 'app.nw/*', 10 | tasks: ['run'] 11 | }, 12 | coffee: { 13 | files: ['rime/*.coffee', 'app.nw/*.coffee'], 14 | tasks: ['coffee'] 15 | } 16 | }, 17 | coffee: { 18 | compile: { 19 | options: { 20 | join: true 21 | }, 22 | files: { 23 | 'app.nw/rime.js': ['rime/*.coffee'], 24 | 'app.nw/rimekit.js': ['app.nw/*.coffee'] 25 | } 26 | } 27 | }, 28 | nodeunit: { 29 | all: ['test/test-*.coffee'] 30 | } 31 | }); 32 | 33 | // These plugins provide necessary tasks. 34 | grunt.loadNpmTasks('grunt-contrib-watch'); 35 | grunt.loadNpmTasks('grunt-contrib-coffee'); 36 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 37 | 38 | // Default task. 39 | grunt.registerTask('default', ['watch']); 40 | 41 | grunt.registerTask('build', ['coffee']); 42 | 43 | grunt.registerTask('test', ['nodeunit']); 44 | 45 | grunt.registerTask('run', 'Run node-webkit app', function () { 46 | exec('nw app.nw'); 47 | }); 48 | }; 49 | 50 | // vim: set et sw=2 sts=2: 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 lotem 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rime Kit 2 | 3 | Tools for editing and sharing Rime schemata, powered by node-webkit. 4 | 5 | # Setup 6 | 7 | ``` 8 | npm install -g grunt-cli 9 | 10 | # install build dependencies 11 | cd rimekit 12 | npm install 13 | 14 | # install runtime dependencies 15 | (cd app.nw; npm install) 16 | 17 | grunt build 18 | grunt test 19 | grunt run 20 | ``` 21 | 22 | # Tools 23 | 24 | ## La brise 25 | 26 | The collaborative Rime schema repository. 27 | 28 | Input schemata and configuration scripts are available as recipes. 29 | 30 | Browse, find the recipes you need, and cook Rime to perfection. 31 | 32 | ## Spelling Algebra Debugger 33 | 34 | ## Color Scheme Editor 35 | -------------------------------------------------------------------------------- /app.nw/README.md: -------------------------------------------------------------------------------- 1 | # Rime Kit 2 | -------------------------------------------------------------------------------- /app.nw/angularfire.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(){var a,b;angular.module("firebase",[]).value("Firebase",Firebase),angular.module("firebase").factory("$firebase",["$q","$parse","$timeout",function(b,c,d){return function(e){var f=new a(b,c,d,e);return f.construct()}}]),angular.module("firebase").filter("orderByPriority",function(){return function(a){var b=[];if(a)if(a.$getIndex&&"function"==typeof a.$getIndex){var c=a.$getIndex();if(c.length>0)for(var d=0;d>>0;for(b=+b||0,1/0===Math.abs(b)&&(b=0),0>b&&(b+=c,0>b&&(b=0));c>b;b++)if(this[b]===a)return b;return-1}),a=function(a,b,c,d){if(this._q=a,this._parse=b,this._timeout=c,this._bound=!1,this._loaded=!1,this._index=[],this._on={value:[],change:[],loaded:[],child_added:[],child_moved:[],child_changed:[],child_removed:[]},"string"==typeof d)throw new Error("Please provide a Firebase reference instead of a URL, eg: new Firebase(url)");this._fRef=d},a.prototype={construct:function(){var b=this,c={};return c.$id=b._fRef.ref().name(),c.$bind=function(a,c,d){return b._bind(a,c,d)},c.$add=function(a){function c(a){a?e.reject(a):e.resolve(d)}var d,e=b._q.defer();return d="object"==typeof a?b._fRef.ref().push(b._parseObject(a),c):b._fRef.ref().push(a,c),e.promise},c.$save=function(a){function c(a){a?d.reject(a):d.resolve()}var d=b._q.defer();if(a){var e=b._parseObject(b._object[a]);b._fRef.ref().child(a).set(e,c)}else b._fRef.ref().set(b._parseObject(b._object),c);return d.promise},c.$set=function(a){var c=b._q.defer();return b._fRef.ref().set(b._parseObject(a),function(a){a?c.reject(a):c.resolve()}),c.promise},c.$update=function(a){var c=b._q.defer();return b._fRef.ref().update(b._parseObject(a),function(a){a?c.reject(a):c.resolve()}),c.promise},c.$transaction=function(a,c){var d=b._q.defer();return b._fRef.ref().transaction(a,function(a,b,c){a?d.reject(a):d.resolve(b?c:null)},c),d.promise},c.$remove=function(a){function c(a){a?d.reject(a):d.resolve()}var d=b._q.defer();return a?b._fRef.ref().child(a).remove(c):b._fRef.ref().remove(c),d.promise},c.$child=function(c){var d=new a(b._q,b._parse,b._timeout,b._fRef.ref().child(c));return d.construct()},c.$on=function(a,d){if(!b._on.hasOwnProperty(a))throw new Error("Invalid event type "+a+" specified");return b._sendInitEvent(a,d),"loaded"===a&&this._loaded||b._on[a].push(d),c},c.$off=function(a,c){if(b._on.hasOwnProperty(a))if(c){var d=b._on[a].indexOf(c);-1!==d&&b._on[a].splice(d,1)}else b._on[a]=[];else b._fRef.off()},c.$auth=function(a){var c=b._q.defer();return b._fRef.auth(a,function(a,b){null!==a?c.reject(a):c.resolve(b)},function(a){c.reject(a)}),c.promise},c.$getIndex=function(){return angular.copy(b._index)},c.$getRef=function(){return b._fRef.ref()},b._object=c,b._getInitialValue(),b._object},_getInitialValue:function(){function a(a,b){var c=a.name(),e=a.val(),f=g._index.indexOf(c);if(-1!==f&&g._index.splice(f,1),b){var h=g._index.indexOf(b);g._index.splice(h+1,0,c)}else g._index.unshift(c);d(e)||null===a.getPriority()||(e.$priority=a.getPriority()),g._updateModel(c,e)}function b(a,b){return function(c,d){b(c,d),g._broadcastEvent(a,g._makeEventSnapshot(c.name(),c.val(),d))}}function c(a,c){g._fRef.on(a,b(a,c))}function d(a){return null===a||"object"!=typeof a}function e(a){g._loaded=!0,g._broadcastEvent("loaded",a)}function f(a){if(g._bound&&null===a){var b=g._parseObject(g._parse(g._name)(g._scope));switch(typeof b){case"string":case"undefined":a="";break;case"number":a=0;break;case"boolean":a=!1}}return a}var g=this;c("child_added",a),c("child_moved",a),c("child_changed",a),c("child_removed",function(a){var b=a.name(),c=g._index.indexOf(b);g._index.splice(c,1),g._updateModel(b,null)}),g._fRef.on("value",function(a){var b=a.val();d(b)?(b=f(b),g._updatePrimitive(b)):delete g._object.$value,g._broadcastEvent("value",g._makeEventSnapshot(a.name(),b)),g._loaded||e(b)})},_updateModel:function(a,b){null==b?delete this._object[a]:this._object[a]=b,this._broadcastEvent("change",a),this._triggerModelUpdate()},_triggerModelUpdate:function(){if(!this._runningTimer){var a=this;this._runningTimer=a._timeout(function(){if(a._runningTimer=null,a._bound){var b=a._object,c=a._parse(a._name)(a._scope);angular.equals(b,c)||a._parse(a._name).assign(a._scope,angular.copy(b))}})}},_updatePrimitive:function(a){var b=this;b._timeout(function(){if(b._object.$value&&angular.equals(b._object.$value,a)||(b._object.$value=a),b._broadcastEvent("change"),b._bound){var c=b._parseObject(b._parse(b._name)(b._scope));angular.equals(c,a)||b._parse(b._name).assign(b._scope,a)}})},_broadcastEvent:function(a,b){function c(a,b){e._timeout(function(){a(b)})}var d=this._on[a]||[];"loaded"===a&&(this._on[a]=[]);var e=this;if(d.length>0)for(var f=0;f-1&&c._timeout(function(){var d=angular.isObject(c._object)?c._parseObject(c._object):c._object;switch(a){case"loaded":b(d);break;case"value":b(c._makeEventSnapshot(c._fRef.name(),d,null));break;case"child_added":c._iterateChildren(d,function(a,d,e){b(c._makeEventSnapshot(a,d,e))})}})},_iterateChildren:function(a,b){if(this._loaded&&angular.isObject(a)){var c=null;for(var d in a)a.hasOwnProperty(d)&&(b(d,a[d],c),c=d)}},_makeEventSnapshot:function(a,b,c){return angular.isUndefined(c)&&(c=null),{snapshot:{name:a,value:b},prevChild:c}},_bind:function(a,b,c){var d=this,e=d._q.defer();d._name=b,d._bound=!0,d._scope=a;var f=d._parse(b)(a);void 0!==f&&"object"==typeof f&&d._fRef.ref().update(d._parseObject(f));var g=a.$watch(b,function(){var c=d._parseObject(d._parse(b)(a));void 0!==d._object.$value&&angular.equals(c,d._object.$value)||angular.equals(c,d._parseObject(d._object))||void 0!==c&&d._loaded&&(d._fRef.set?d._fRef.set(c):d._fRef.ref().update(c))},!0);return a.$on("$destroy",function(){g()}),d._fRef.once("value",function(f){d._timeout(function(){"object"!=typeof f.val()?(null==f.val()&&"function"==typeof c&&(a[b]=c()),e.resolve(g)):d._timeout(function(){null==f.val()&&"function"==typeof c&&(a[b]=c()),e.resolve(g)})})}),e.promise},_parseObject:function(a){function b(a){for(var c in a)a.hasOwnProperty(c)&&("$priority"==c?(a[".priority"]=a.$priority,delete a.$priority):"object"==typeof a[c]&&b(a[c]));return a}var c=b(angular.copy(a));return angular.fromJson(angular.toJson(c))}},angular.module("firebase").factory("$firebaseSimpleLogin",["$q","$timeout","$rootScope",function(a,c,d){return function(e){var f=new b(a,c,d,e);return f.construct()}}]),b=function(a,b,c,d){if(this._q=a,this._timeout=b,this._rootScope=c,this._loginDeferred=null,this._getCurrentUserDeferred=[],this._currentUserData=void 0,"string"==typeof d)throw new Error("Please provide a Firebase reference instead of a URL, eg: new Firebase(url)");this._fRef=d},b.prototype={construct:function(){var a={user:null,$login:this.login.bind(this),$logout:this.logout.bind(this),$createUser:this.createUser.bind(this),$changePassword:this.changePassword.bind(this),$removeUser:this.removeUser.bind(this),$getCurrentUser:this.getCurrentUser.bind(this),$sendPasswordResetEmail:this.sendPasswordResetEmail.bind(this)};if(this._object=a,!window.FirebaseSimpleLogin){var b=new Error("FirebaseSimpleLogin is undefined. Did you forget to include firebase-simple-login.js?");throw this._rootScope.$broadcast("$firebaseSimpleLogin:error",b),b}var c=new FirebaseSimpleLogin(this._fRef,this._onLoginEvent.bind(this));return this._authClient=c,this._object},login:function(a,b){var c=this._q.defer(),d=this;return this.getCurrentUser().then(function(){d._loginDeferred=c,d._authClient.login(a,b)}),c.promise},logout:function(){this._authClient.logout(),delete this._currentUserData},createUser:function(a,b){var c=this,d=this._q.defer();return c._authClient.createUser(a,b,function(a,b){a?(c._rootScope.$broadcast("$firebaseSimpleLogin:error",a),d.reject(a)):d.resolve(b)}),d.promise},changePassword:function(a,b,c){var d=this,e=this._q.defer();return d._authClient.changePassword(a,b,c,function(a){a?(d._rootScope.$broadcast("$firebaseSimpleLogin:error",a),e.reject(a)):e.resolve()}),e.promise},getCurrentUser:function(){var a=this,b=this._q.defer();return void 0!==a._currentUserData?b.resolve(a._currentUserData):a._getCurrentUserDeferred.push(b),b.promise},removeUser:function(a,b){var c=this,d=this._q.defer();return c._authClient.removeUser(a,b,function(a){a?(c._rootScope.$broadcast("$firebaseSimpleLogin:error",a),d.reject(a)):d.resolve()}),d.promise},sendPasswordResetEmail:function(a){var b=this,c=this._q.defer();return b._authClient.sendPasswordResetEmail(a,function(a){a?(b._rootScope.$broadcast("$firebaseSimpleLogin:error",a),c.reject(a)):c.resolve()}),c.promise},_onLoginEvent:function(a,b){if(this._currentUserData!==b||null!==a){var c=this;a?(c._loginDeferred&&(c._loginDeferred.reject(a),c._loginDeferred=null),c._rootScope.$broadcast("$firebaseSimpleLogin:error",a)):(this._currentUserData=b,c._timeout(function(){for(c._object.user=b,b?c._rootScope.$broadcast("$firebaseSimpleLogin:login",b):c._rootScope.$broadcast("$firebaseSimpleLogin:logout"),c._loginDeferred&&(c._loginDeferred.resolve(b),c._loginDeferred=null);c._getCurrentUserDeferred.length>0;){var a=c._getCurrentUserDeferred.pop();a.resolve(b)}}))}}}}(); -------------------------------------------------------------------------------- /app.nw/brise.coffee: -------------------------------------------------------------------------------- 1 | app.controller 'BriseCtrl', ($scope, rimekitService) -> 2 | -------------------------------------------------------------------------------- /app.nw/brise.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /app.nw/credits.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
程序設計
4 |
佛振
5 |
封面題詩
6 |
寍寍醬
7 |
參謀
8 |
瑾昀、攴君、雪齋
9 |
10 |
11 | -------------------------------------------------------------------------------- /app.nw/firebase.js: -------------------------------------------------------------------------------- 1 | (function() {function g(a){throw a;}var j=void 0,k=!0,m=null,o=!1;function aa(a){return function(){return this[a]}}function s(a){return function(){return a}}var t,ba=this;function ca(){}function da(a){a.nb=function(){return a.jd?a.jd:a.jd=new a}} 2 | function ea(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; 3 | else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function u(a){return a!==j}function fa(a){var b=ea(a);return"array"==b||"object"==b&&"number"==typeof a.length}function v(a){return"string"==typeof a}function ga(a){return"number"==typeof a}function ha(a){var b=typeof a;return"object"==b&&a!=m||"function"==b}Math.floor(2147483648*Math.random()).toString(36);function ia(a,b,c){return a.call.apply(a.bind,arguments)} 4 | function ja(a,b,c){a||g(Error());if(2b?e+="000":256>b?e+="00":4096>b&&(e+="0");return pa[a]=e+b.toString(16)}),'"')};function ra(a){return"undefined"!==typeof JSON&&u(JSON.parse)?JSON.parse(a):la(a)}function y(a){if("undefined"!==typeof JSON&&u(JSON.stringify))a=JSON.stringify(a);else{var b=[];na(new ma,a,b);a=b.join("")}return a};function sa(a){for(var b=[],c=0,d=0;d=e&&(e-=55296,d++,z(de?b[c++]=e:(2048>e?b[c++]=e>>6|192:(65536>e?b[c++]=e>>12|224:(b[c++]=e>>18|240,b[c++]=e>>12&63|128),b[c++]=e>>6&63|128),b[c++]=e&63|128)}return b};function A(a,b,c,d){var e;dc&&(e=0===c?"none":"no more than "+c);e&&g(Error(a+" failed: Was called with "+d+(1===d?" argument.":" arguments.")+" Expects "+e+"."))}function B(a,b,c){var d="";switch(b){case 1:d=c?"first":"First";break;case 2:d=c?"second":"Second";break;case 3:d=c?"third":"Third";break;case 4:d=c?"fourth":"Fourth";break;default:ta.assert(o,"errorPrefix_ called with argumentNumber > 4. Need to update it?")}return a+" failed: "+(d+" argument ")} 8 | function C(a,b,c,d){(!d||u(c))&&"function"!=ea(c)&&g(Error(B(a,b,d)+"must be a valid function."))}function ua(a,b,c){u(c)&&(!ha(c)||c===m)&&g(Error(B(a,b,k)+"must be a valid context object."))};function D(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function va(a,b){if(Object.prototype.hasOwnProperty.call(a,b))return a[b]};var ta={},wa=/[\[\].#$\/]/,xa=/[\[\].#$]/;function ya(a){return v(a)&&0!==a.length&&!wa.test(a)}function za(a,b,c){(!c||u(b))&&Aa(B(a,1,c),b)} 9 | function Aa(a,b,c,d){c||(c=0);d||(d=[]);u(b)||g(Error(a+"contains undefined"+Ba(d)));"function"==ea(b)&&g(Error(a+"contains a function"+Ba(d)+" with contents: "+b.toString()));Ca(b)&&g(Error(a+"contains "+b.toString()+Ba(d)));1E310485760/3&&10485760=a)&&g("Query.limit: First argument must be a positive integer.");return new H(this.n,this.path,a,this.ca,this.va,this.za,this.Ra)};H.prototype.limit=H.prototype.Md;H.prototype.be=function(a,b){A("Query.startAt",0,2,arguments.length);Ea("Query.startAt",1,a,k);Ga("Query.startAt",b);u(a)||(b=a=m);return new H(this.n,this.path,this.Ba,a,b,this.za,this.Ra)};H.prototype.startAt=H.prototype.be; 16 | H.prototype.Gd=function(a,b){A("Query.endAt",0,2,arguments.length);Ea("Query.endAt",1,a,k);Ga("Query.endAt",b);return new H(this.n,this.path,this.Ba,this.ca,this.va,a,b)};H.prototype.endAt=H.prototype.Gd;function Ja(a){var b={};u(a.ca)&&(b.sp=a.ca);u(a.va)&&(b.sn=a.va);u(a.za)&&(b.ep=a.za);u(a.Ra)&&(b.en=a.Ra);u(a.Ba)&&(b.l=a.Ba);u(a.ca)&&(u(a.va)&&a.ca===m&&a.va===m)&&(b.vf="l");return b}H.prototype.La=function(){var a=Ka(Ja(this));return"{}"===a?"default":a}; 17 | function Ia(a,b,c){var d={};b&&c?(d.cancel=b,C(a,3,d.cancel,k),d.T=c,ua(a,4,d.T)):b&&("object"===typeof b&&b!==m?d.T=b:"function"===typeof b?d.cancel=b:g(Error(B(a,3,k)+"must either be a cancel callback or a context object.")));return d};function K(a){if(a instanceof K)return a;if(1==arguments.length){this.m=a.split("/");for(var b=0,c=0;c=a.m.length?m:a.m[a.Z]}function La(a){var b=a.Z;b=this.m.length)return m;for(var a=[],b=this.Z;b=this.m.length}; 19 | function Ma(a,b){var c=F(a);if(c===m)return b;if(c===F(b))return Ma(La(a),La(b));g("INTERNAL ERROR: innerPath ("+b+") is not within outerPath ("+a+")")}t.contains=function(a){var b=0;if(this.m.length>a.m.length)return o;for(;bb?1:0}t=Ta.prototype;t.oa=function(a,b){return new Ta(this.Oa,this.ba.oa(a,b,this.Oa).copy(m,m,o,m,m))};t.remove=function(a){return new Ta(this.Oa,this.ba.remove(a,this.Oa).copy(m,m,o,m,m))};t.get=function(a){for(var b,c=this.ba;!c.f();){b=this.Oa(a,c.key);if(0===b)return c.value;0>b?c=c.left:0c?d=d.left:0d?e.copy(m,m,m,e.left.oa(a,b,c),m):0===d?e.copy(m,b,m,m,m):e.copy(m,m,m,m,e.right.oa(a,b,c));return ab(e)};function bb(a){if(a.left.f())return Va;!a.left.N()&&!a.left.left.N()&&(a=cb(a));a=a.copy(m,m,m,bb(a.left),m);return ab(a)} 26 | t.remove=function(a,b){var c,d;c=this;if(0>b(a,c.key))!c.left.f()&&(!c.left.N()&&!c.left.left.N())&&(c=cb(c)),c=c.copy(m,m,m,c.left.remove(a,b),m);else{c.left.N()&&(c=fb(c));!c.right.f()&&(!c.right.N()&&!c.right.left.N())&&(c=gb(c),c.left.left.N()&&(c=fb(c),c=gb(c)));if(0===b(a,c.key)){if(c.right.f())return Va;d=$a(c.right);c=c.copy(d.key,d.value,m,m,bb(c.right))}c=c.copy(m,m,m,m,c.right.remove(a,b))}return ab(c)};t.N=aa("color"); 27 | function ab(a){a.right.N()&&!a.left.N()&&(a=hb(a));a.left.N()&&a.left.left.N()&&(a=fb(a));a.left.N()&&a.right.N()&&(a=gb(a));return a}function cb(a){a=gb(a);a.right.left.N()&&(a=a.copy(m,m,m,m,fb(a.right)),a=hb(a),a=gb(a));return a}function hb(a){var b;b=a.copy(m,m,k,m,a.right.left);return a.right.copy(m,m,a.color,b,m)}function fb(a){var b;b=a.copy(m,m,k,a.left.right,m);return a.left.copy(m,m,a.color,m,b)} 28 | function gb(a){var b,c;b=a.left.copy(m,m,!a.left.color,m,m);c=a.right.copy(m,m,!a.right.color,m,m);return a.copy(m,m,!a.color,b,c)}function ib(){}t=ib.prototype;t.copy=function(){return this};t.oa=function(a,b){return new Za(a,b,j,j,j)};t.remove=function(){return this};t.count=s(0);t.f=s(k);t.Aa=s(o);t.Ma=s(o);t.ub=s(m);t.Wa=s(m);t.N=s(o);var Va=new ib;function jb(a){this.Rb=a;this.ec="firebase:"}jb.prototype.set=function(a,b){b==m?this.Rb.removeItem(this.ec+a):this.Rb.setItem(this.ec+a,y(b))};jb.prototype.get=function(a){a=this.Rb.getItem(this.ec+a);return a==m?m:ra(a)};jb.prototype.remove=function(a){this.Rb.removeItem(this.ec+a)};jb.prototype.ld=o;function kb(){this.jb={}}kb.prototype.set=function(a,b){b==m?delete this.jb[a]:this.jb[a]=b};kb.prototype.get=function(a){return D(this.jb,a)?this.jb[a]:m};kb.prototype.remove=function(a){delete this.jb[a]};kb.prototype.ld=k;function lb(a){try{if("undefined"!==typeof window&&"undefined"!==typeof window[a]){var b=window[a];b.setItem("firebase:sentinel","cache");b.removeItem("firebase:sentinel");return new jb(b)}}catch(c){}return new kb}var mb=lb("localStorage"),nb=lb("sessionStorage");function ob(a,b,c,d){this.host=a.toLowerCase();this.domain=this.host.substr(this.host.indexOf(".")+1);this.kc=b;this.Vb=c;this.de=d;this.ea=mb.get("host:"+a)||this.host}function pb(a,b){b!==a.ea&&(a.ea=b,"s-"===a.ea.substr(0,2)&&mb.set("host:"+a.host,a.ea))}ob.prototype.toString=function(){return(this.kc?"https://":"http://")+this.host};function qb(){};function rb(){this.B=[];this.tc=[];this.Bd=[];this.bc=[];this.bc[0]=128;for(var a=1;64>a;++a)this.bc[a]=0;this.reset()}ka(rb,qb);rb.prototype.reset=function(){this.B[0]=1732584193;this.B[1]=4023233417;this.B[2]=2562383102;this.B[3]=271733878;this.B[4]=3285377520;this.Zc=this.pb=0}; 29 | function sb(a,b){var c;c||(c=0);for(var d=a.Bd,e=c;ee;e++){var f=d[e-3]^d[e-8]^d[e-14]^d[e-16];d[e]=(f<<1|f>>>31)&4294967295}c=a.B[0];for(var h=a.B[1],i=a.B[2],l=a.B[3],n=a.B[4],p,e=0;80>e;e++)40>e?20>e?(f=l^h&(i^l),p=1518500249):(f=h^i^l,p=1859775393):60>e?(f=h&i|l&(h|i),p=2400959708):(f=h^i^l,p=3395469782),f=(c<<5|c>>>27)+f+n+p+d[e]&4294967295,n=l,l=i,i=(h<<30|h>>>2)&4294967295,h=c,c=f;a.B[0]=a.B[0]+c&4294967295;a.B[1]=a.B[1]+h& 30 | 4294967295;a.B[2]=a.B[2]+i&4294967295;a.B[3]=a.B[3]+l&4294967295;a.B[4]=a.B[4]+n&4294967295}rb.prototype.update=function(a,b){u(b)||(b=a.length);var c=this.tc,d=this.pb,e=0;if(v(a))for(;ec;c++)Jb[c]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(c),Kb[c]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.".charAt(c)}for(var c=b?Kb:Jb,d=[],e=0;e>2,f=(f&3)<<4|i>>4,i=(i&15)<<2|n>>6,n=n&63;l||(n=64,h||(i=64));d.push(c[p],c[f],c[i],c[n])}return d.join("")} 33 | ;var Mb,Nb=1;Mb=function(){return Nb++};function z(a,b){a||g(Error("Firebase INTERNAL ASSERT FAILED:"+b))}function Ob(a){var b=sa(a),a=new rb;a.update(b);var b=[],c=8*a.Zc;56>a.pb?a.update(a.bc,56-a.pb):a.update(a.bc,64-(a.pb-56));for(var d=63;56<=d;d--)a.tc[d]=c&255,c/=256;sb(a,a.tc);for(d=c=0;5>d;d++)for(var e=24;0<=e;e-=8)b[c++]=a.B[d]>>e&255;return Lb(b)} 34 | function Pb(){for(var a="",b=0;bb?1:-1:0}function Xb(a,b){if(a===b)return 0;var c=Yb(a),d=Yb(b);return c!==m?d!==m?c-d:-1:d!==m?1:aa?c.push(a.substring(d,a.length)):c.push(a.substring(d,d+b));return c}function ac(a,b){if("array"==ea(a))for(var c=0;ca,a=Math.abs(a),a>=Math.pow(2,-1022)?(d=Math.min(Math.floor(Math.log(a)/Math.LN2),1023),c=d+1023,d=Math.round(a*Math.pow(2,52-d)-Math.pow(2,52))):(c=0,d=Math.round(a/Math.pow(2,-1074))));e=[];for(a=52;a;a-=1)e.push(d%2?1:0),d=Math.floor(d/2);for(a=11;a;a-=1)e.push(c%2?1:0),c=Math.floor(c/2);e.push(b?1:0);e.reverse();b=e.join("");c="";for(a=0;64>a;a+=8)d=parseInt(b.substr(a,8),2).toString(16),1===d.length&&(d="0"+d),c+=d; 39 | return c.toLowerCase()}var dc=/^-?\d{1,10}$/;function Yb(a){return dc.test(a)&&(a=Number(a),-2147483648<=a&&2147483647>=a)?a:m}function ec(a){try{a()}catch(b){setTimeout(function(){g(b)})}};function fc(a,b){this.D=a;z(this.D!==m,"LeafNode shouldn't be created with null value.");this.Za="undefined"!==typeof b?b:m}t=fc.prototype;t.M=s(k);t.j=aa("Za");t.Ea=function(a){return new fc(this.D,a)};t.L=function(){return O};t.Q=function(a){return F(a)===m?this:O};t.da=s(m);t.H=function(a,b){return(new Q).H(a,b).Ea(this.Za)};t.xa=function(a,b){var c=F(a);return c===m?b:this.H(c,O.xa(La(a),b))};t.f=s(o);t.Xb=s(0);t.V=function(a){return a&&this.j()!==m?{".value":this.k(),".priority":this.j()}:this.k()}; 40 | t.hash=function(){var a="";this.j()!==m&&(a+="priority:"+gc(this.j())+":");var b=typeof this.D,a=a+(b+":"),a="number"===b?a+cc(this.D):a+this.D;return Ob(a)};t.k=aa("D");t.toString=function(){return"string"===typeof this.D?'"'+this.D+'"':this.D};function hc(a,b){return Wb(a.ha,b.ha)||Xb(a.name,b.name)}function ic(a,b){return Xb(a.name,b.name)}function jc(a,b){return Xb(a,b)};function Q(a,b){this.o=a||new Ta(jc);this.Za="undefined"!==typeof b?b:m}t=Q.prototype;t.M=s(o);t.j=aa("Za");t.Ea=function(a){return new Q(this.o,a)};t.H=function(a,b){var c=this.o.remove(a);b&&b.f()&&(b=m);b!==m&&(c=c.oa(a,b));return b&&b.j()!==m?new kc(c,m,this.Za):new Q(c,this.Za)};t.xa=function(a,b){var c=F(a);if(c===m)return b;var d=this.L(c).xa(La(a),b);return this.H(c,d)};t.f=function(){return this.o.f()};t.Xb=function(){return this.o.count()};var lc=/^\d+$/;t=Q.prototype; 41 | t.V=function(a){if(this.f())return m;var b={},c=0,d=0,e=k;this.z(function(f,h){b[f]=h.V(a);c++;e&&lc.test(f)?d=Math.max(d,Number(f)):e=o});if(!a&&e&&d<2*c){var f=[],h;for(h in b)f[h]=b[h];return f}a&&this.j()!==m&&(b[".priority"]=this.j());return b};t.hash=function(){var a="";this.j()!==m&&(a+="priority:"+gc(this.j())+":");this.z(function(b,c){var d=c.hash();""!==d&&(a+=":"+b+":"+d)});return""===a?"":Ob(a)};t.L=function(a){a=this.o.get(a);return a===m?O:a}; 42 | t.Q=function(a){var b=F(a);return b===m?this:this.L(b).Q(La(a))};t.da=function(a){return Wa(this.o,a)};t.gd=function(){return this.o.ub()};t.hd=function(){return this.o.Wa()};t.z=function(a){return this.o.Aa(a)};t.Ac=function(a){return this.o.Ma(a)};t.Ua=function(){return this.o.Ua()};t.toString=function(){var a="{",b=k;this.z(function(c,d){b?b=o:a+=", ";a+='"'+c+'" : '+d.toString()});return a+="}"};var O=new Q;function kc(a,b,c){Q.call(this,a,c);b===m&&(b=new Ta(hc),a.Aa(function(a,c){b=b.oa({name:a,ha:c.j()},c)}));this.ua=b}ka(kc,Q);t=kc.prototype;t.H=function(a,b){var c=this.L(a),d=this.o,e=this.ua;c!==m&&(d=d.remove(a),e=e.remove({name:a,ha:c.j()}));b&&b.f()&&(b=m);b!==m&&(d=d.oa(a,b),e=e.oa({name:a,ha:b.j()},b));return new kc(d,e,this.j())};t.da=function(a,b){var c=Wa(this.ua,{name:a,ha:b.j()});return c?c.name:m};t.z=function(a){return this.ua.Aa(function(b,c){return a(b.name,c)})}; 43 | t.Ac=function(a){return this.ua.Ma(function(b,c){return a(b.name,c)})};t.Ua=function(){return this.ua.Ua(function(a,b){return{key:a.name,value:b}})};t.gd=function(){return this.ua.f()?m:this.ua.ub().name};t.hd=function(){return this.ua.f()?m:this.ua.Wa().name};function R(a,b){if(a===m)return O;var c=m;"object"===typeof a&&".priority"in a?c=a[".priority"]:"undefined"!==typeof b&&(c=b);z(c===m||"string"===typeof c||"number"===typeof c||"object"===typeof c&&".sv"in c);"object"===typeof a&&(".value"in a&&a[".value"]!==m)&&(a=a[".value"]);if("object"!==typeof a||".sv"in a)return new fc(a,c);if(a instanceof Array){var d=O;bc(a,function(b,c){if(D(a,c)&&"."!==c.substring(0,1)){var e=R(b);if(e.M()||!e.f())d=d.H(c,e)}});return d.Ea(c)}var e=[],f={},h=o;ac(a,function(b, 44 | c){if("string"!==typeof c||"."!==c.substring(0,1)){var d=R(a[c]);d.f()||(h=h||d.j()!==m,e.push({name:c,ha:d.j()}),f[c]=d)}});var i=mc(e,f,o);if(h){var l=mc(e,f,k);return new kc(i,l,c)}return new Q(i,c)}var nc=Math.log(2);function pc(a){this.count=parseInt(Math.log(a+1)/nc);this.dd=this.count-1;this.Dd=a+1&parseInt(Array(this.count+1).join("1"),2)} 45 | function mc(a,b,c){function d(d,f){var h=n-d,p=n;n-=d;var q=a[h].name,h=new Za(c?a[h]:q,b[q],f,m,e(h+1,p));i?i.left=h:l=h;i=h}function e(d,f){var h=f-d;if(0==h)return m;if(1==h){var h=a[d].name,i=c?a[d]:h;return new Za(i,b[h],o,m,m)}var i=parseInt(h/2)+d,l=e(d,i),n=e(i+1,f),h=a[i].name,i=c?a[i]:h;return new Za(i,b[h],o,l,n)}var f=c?hc:ic;a.sort(f);var h,f=new pc(a.length),i=m,l=m,n=a.length;for(h=0;hd?m:v(c)?c.charAt(d):c[d],"Unknown event: "+b)};function sc(){qc.call(this,["visible"]);var a,b;"undefined"!==typeof document&&"undefined"!==typeof document.addEventListener&&("undefined"!==typeof document.hidden?(b="visibilitychange",a="hidden"):"undefined"!==typeof document.mozHidden?(b="mozvisibilitychange",a="mozHidden"):"undefined"!==typeof document.msHidden?(b="msvisibilitychange",a="msHidden"):"undefined"!==typeof document.webkitHidden&&(b="webkitvisibilitychange",a="webkitHidden"));this.hb=k;if(b){var c=this;document.addEventListener(b, 51 | function(){var b=!document[a];if(b!==c.hb){c.hb=b;c.ad("visible",b)}},o)}}ka(sc,qc);da(sc);sc.prototype.Cc=function(a){z("visible"===a);return[this.hb]};function bc(a,b){for(var c in a)b.call(j,a[c],c,a)}function tc(a){var b={},c;for(c in a)b[c]=a[c];return b};function uc(){this.kb={}}function vc(a,b,c){u(c)||(c=1);D(a.kb,b)||(a.kb[b]=0);a.kb[b]+=c}uc.prototype.get=function(){return tc(this.kb)};function wc(a){this.Ed=a;this.Tb=m}wc.prototype.get=function(){var a=this.Ed.get(),b=tc(a);if(this.Tb)for(var c in this.Tb)b[c]-=this.Tb[c];this.Tb=a;return b};function xc(a,b){this.Wc={};this.nc=new wc(a);this.u=b;setTimeout(w(this.qd,this),10+6E4*Math.random())}xc.prototype.qd=function(){var a=this.nc.get(),b={},c=o,d;for(d in a)0= 53 | a.length){var b=Number(a);if(!isNaN(b)){c.Yc=b;c.frames=[];a=m;break a}}c.Yc=1;c.frames=[]}a!==m&&Fc(c,a)}};this.W.onerror=function(a){c.e("WebSocket error. Closing connection.");a.data&&c.e(a.data);c.Ka()}};Cc.prototype.start=function(){};Cc.isAvailable=function(){var a=o;if("undefined"!==typeof navigator&&navigator.userAgent){var b=navigator.userAgent.match(/Android ([0-9]{0,}\.[0-9]{0,})/);b&&1parseFloat(b[1])&&(a=k)}return!a&&Bc!==m&&!Dc};Cc.responsesRequiredToBeHealthy=2; 54 | Cc.healthyTimeout=3E4;t=Cc.prototype;t.Ic=function(){mb.remove("previous_websocket_failure")};function Fc(a,b){a.frames.push(b);if(a.frames.length==a.Yc){var c=a.frames.join("");a.frames=m;c=ra(c);a.Qd(c)}}t.send=function(a){Ec(this);a=y(a);vc(this.$,"bytes_sent",a.length);a=$b(a,16384);1document.domain="'+document.domain+'";<\/script>');a=""+a+"";try{this.Y.ya.open(),this.Y.ya.write(a),this.Y.ya.close()}catch(f){M("frame writing exception"),f.stack&&M(f.stack),M(f)}} 64 | Oc.prototype.close=function(){this.rc=o;if(this.Y){this.Y.ya.body.innerHTML="";var a=this;setTimeout(function(){a.Y!==m&&(document.body.removeChild(a.Y),a.Y=m)},0)}var b=this.ga;b&&(this.ga=m,b())}; 65 | function Qc(a){if(a.rc&&a.lc&&a.Oc.count()<(0=a.Db[0].ed.length+30+c.length){var e=a.Db.shift(),c=c+"&seg"+d+"="+e.Yd+"&ts"+d+"="+e.ce+"&d"+d+"="+e.ed;d++}else break;var b=b+c,f=a.xc;a.Oc.add(f);var h=function(){a.Oc.remove(f);Qc(a)},i=setTimeout(h,25E3);Pc(a,b,function(){clearTimeout(i);h()});return k}return o} 66 | function Pc(a,b,c){setTimeout(function(){try{if(a.lc){var d=a.Y.ya.createElement("script");d.type="text/javascript";d.async=k;d.src=b;d.onload=d.onreadystatechange=function(){var a=d.readyState;if(!a||"loaded"===a||"complete"===a)d.onload=d.onreadystatechange=m,d.parentNode&&d.parentNode.removeChild(d),c()};d.onerror=function(){M("Long-poll script failed to load: "+b);a.lc=o;a.close()};a.Y.ya.body.appendChild(d)}}catch(e){}},1)};function Rc(a){var b=Cc&&Cc.isAvailable(),c=b&&!(mb.ld||mb.get("previous_websocket_failure")===k);a.de&&(b||N("wss:// URL used, but browser isn't known to support websockets. Trying anyway."),c=k);if(c)this.Kb=[Cc];else{var d=this.Kb=[];ac(Sc,function(a,b){b&&b.isAvailable()&&d.push(b)})}}var Sc=[Lc,{isAvailable:s(o)},Cc];function Tc(a,b,c,d,e,f){this.id=a;this.e=Tb("c:"+this.id+":");this.Mc=c;this.yb=d;this.R=e;this.Lc=f;this.K=b;this.cc=[];this.bd=0;this.$c=new Rc(b);this.ka=0;this.e("Connection created");Uc(this)} 67 | function Uc(a){var b;var c=a.$c;0=a.ud?(a.e("Secondary connection is healthy."),a.Va=k,a.w.Ic(),a.w.start(),a.e("sending client ack on secondary"),a.w.send({t:"c",d:{t:"a",d:{}}}),a.e("Ending transmission on primary"),a.I.send({t:"c",d:{t:"n",d:{}}}),a.Lb=a.w,Zc(a)):(a.e("sending ping on secondary."),a.w.send({t:"c",d:{t:"p",d:{}}}))}Tc.prototype.$b=function(a){ad(this);this.Mc(a)}; 73 | function ad(a){a.Va||(a.Qc--,0>=a.Qc&&(a.e("Primary connection is healthy."),a.Va=k,a.I.Ic()))}function Xc(a){!a.Va&&1===a.ka&&(a.e("sending ping on primary."),cd(a,{t:"c",d:{t:"p",d:{}}}))}function cd(a,b){1!==a.ka&&g("Connection is not connected");a.Lb.send(b)}Tc.prototype.close=function(){2!==this.ka&&(this.e("Closing realtime connection."),this.ka=2,$c(this),this.R&&(this.R(),this.R=m))}; 74 | function $c(a){a.e("Shutting down all connections");a.I&&(a.I.close(),a.I=m);a.w&&(a.w.close(),a.w=m);a.Sb&&(clearTimeout(a.Sb),a.Sb=m)};function dd(){qc.call(this,["online"]);this.zb=k;if("undefined"!==typeof window&&"undefined"!==typeof window.addEventListener){var a=this;window.addEventListener("online",function(){a.zb||a.ad("online",k);a.zb=k},o);window.addEventListener("offline",function(){a.zb&&a.ad("online",o);a.zb=o},o)}}ka(dd,qc);da(dd);dd.prototype.Cc=function(a){z("online"===a);return[this.zb]};function ed(a,b,c,d,e,f){this.id=fd++;this.e=Tb("p:"+this.id+":");this.Na=k;this.fa={};this.U=[];this.Ab=0;this.xb=[];this.P=o;this.qa=1E3;this.Ub=3E5;this.ac=b||ca;this.Zb=c||ca;this.wb=d||ca;this.Nc=e||ca;this.Dc=f||ca;this.K=a;this.Sc=m;this.Hb={};this.Xd=0;this.sb=this.Hc=m;gd(this,0);sc.nb().Ya("visible",this.Sd,this);-1===a.host.indexOf("fblocal")&&dd.nb().Ya("online",this.Rd,this)}var fd=0,hd=0;t=ed.prototype; 75 | t.Da=function(a,b,c){var d=++this.Xd,a={r:d,a:a,b:b};this.e(y(a));z(this.P,"sendRequest_ call when we're not connected not allowed.");this.ia.vd(a);c&&(this.Hb[d]=c)};function id(a,b,c,d,e){a.e("Listen on "+b+" for "+c);var f={p:b},d=vb(d,function(a){return Ja(a)});"{}"!==c&&(f.q=d);f.h=a.Dc(b);a.Da("l",f,function(d){a.e("listen response",d);d=d.s;"ok"!==d&&jd(a,b,c);e&&e(d)})} 76 | t.ib=function(a,b,c){this.Ga={Fd:a,fd:o,X:b,Pb:c};this.e("Authenticating using credential: "+this.Ga);kd(this);if(!(b=40==a.length))a:{var d;try{var e=a.split(".");if(3!==e.length){b=o;break a}var f;b:{try{if("undefined"!==typeof atob){f=atob(e[1]);break b}}catch(h){M("base64DecodeIfNativeSupport failed: ",h)}f=m}f!==m&&(d=ra(f))}catch(i){M("isAdminAuthToken_ failed",i)}b="object"===typeof d&&va(d,"admin")===k}b&&(this.e("Admin auth credential detected. Reducing max reconnect time."),this.Ub=3E4)}; 77 | t.Mb=function(a){delete this.Ga;this.wb(o);this.P&&this.Da("unauth",{},function(b){a(b.s,b.d)})};function kd(a){var b=a.Ga;a.P&&b&&a.Da("auth",{cred:b.Fd},function(c){var d=c.s,c=c.d||"error";"ok"!==d&&a.Ga===b&&delete a.Ga;a.wb("ok"===d);b.fd?"ok"!==d&&b.Pb&&b.Pb(d,c):(b.fd=k,b.X&&b.X(d,c))})}function ld(a,b,c,d){b=b.toString();jd(a,b,c)&&a.P&&(a.e("Unlisten on "+b+" for "+c),b={p:b},d=vb(d,function(a){return Ja(a)}),"{}"!==c&&(b.q=d),a.Da("u",b))} 78 | function md(a,b,c,d){a.P?nd(a,"o",b,c,d):a.xb.push({Pc:b,action:"o",data:c,C:d})}t.Kc=function(a,b){this.P?nd(this,"oc",a,m,b):this.xb.push({Pc:a,action:"oc",data:m,C:b})};function nd(a,b,c,d,e){c={p:c,d:d};a.e("onDisconnect "+b,c);a.Da(b,c,function(a){e&&setTimeout(function(){e(a.s,a.d)},0)})}t.put=function(a,b,c,d){od(this,"p",a,b,c,d)};function od(a,b,c,d,e,f){c={p:c,d:d};u(f)&&(c.h=f);a.U.push({action:b,rd:c,C:e});a.Ab++;b=a.U.length-1;a.P&&pd(a,b)} 79 | function pd(a,b){var c=a.U[b].action,d=a.U[b].rd,e=a.U[b].C;a.U[b].Ud=a.P;a.Da(c,d,function(d){a.e(c+" response",d);delete a.U[b];a.Ab--;0===a.Ab&&(a.U=[]);e&&e(d.s,d.d)})} 80 | t.$b=function(a){if("r"in a){this.e("from server: "+y(a));var b=a.r,c=this.Hb[b];c&&(delete this.Hb[b],c(a.b))}else"error"in a&&g("A server-side error has occurred: "+a.error),"a"in a&&(b=a.a,a=a.b,this.e("handleServerMessage",b,a),"d"===b?this.ac(a.p,a.d):"m"===b?this.ac(a.p,a.d,k):"c"===b?(b=a.p,a=(a=a.q)?vb(a,function(a){return Ka(a)}).join("$"):"{}",(a=jd(this,b,a))&&a.C&&a.C("permission_denied")):"ac"===b?(b=a.s,a=a.d,c=this.Ga,delete this.Ga,c&&c.Pb&&c.Pb(b,a),this.wb(o)):"sd"===b?this.Sc?this.Sc(a): 81 | "msg"in a&&"undefined"!==typeof console&&console.log("FIREBASE: "+a.msg.replace("\n","\nFIREBASE: ")):Ub("Unrecognized action received from server: "+y(b)+"\nAre you using the latest client?"))}; 82 | t.yb=function(a){this.e("connection ready");this.P=k;this.sb=(new Date).getTime();this.Nc({serverTimeOffset:a-(new Date).getTime()});kd(this);for(var b in this.fa)for(var c in this.fa[b])a=this.fa[b][c],id(this,b,c,a.$a,a.C);for(b=0;bc)f=va(q,I.key),u(f)?(n.push({Bc:I,Xc:i[f]}),i[f]=m):(r[I.key]=l.length,l.push(I)),f=k,I=Ya(x);else{if(0c||0===c&&0>=Xb(a,d.Ra)}):c.push(function(a,b){return 0>=Wb(b,d.za)}));var e=m,f=m;if(u(this.J.Ba))if(u(this.J.ca)){if(e=Rd(a,c,this.J.Ba,o)){var h=a.L(e).j();c.push(function(a,b){var c=Wb(b,h);return 0>c||0===c&&0>=Xb(a,e)})}}else if(f= 97 | Rd(a,c,this.J.Ba,k)){var i=a.L(f).j();c.push(function(a,b){var c=Wb(b,i);return 0d;d++)ve[d]=Math.floor(64*Math.random());for(d=0;12>d;d++)a+="-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(ve[d]);z(20===a.length,"NextPushId: Length should be 20.");return a};function J(){var a,b,c;if(arguments[0]instanceof ee)c=arguments[0],a=arguments[1];else{A("new Firebase",1,2,arguments.length);var d=arguments[0];b=a="";var e=k,f="";if(v(d)){var h=d.indexOf("//");if(0<=h)var i=d.substring(0,h-1),d=d.substring(h+2);h=d.indexOf("/");-1===h&&(h=d.length);a=d.substring(0,h);var d=d.substring(h+1),l=a.split(".");if(3==l.length){h=l[2].indexOf(":");e=0<=h?"https"===i||"wss"===i:k;if("firebase"===l[1])Vb(a+" is no longer supported. Please use .firebaseio.com instead"); 136 | else{b=l[0];f="";d=("/"+d).split("/");for(h=0;h 2 | 3 | 4 | 5 | Rime 工具箱 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 |
19 | 20 | 21 |
22 |

Rime 工具箱 23 | 分享打字的樂趣 24 |

25 |
    26 |
  • 方案浩如星 碼表源淸平
  • 27 |
  • 配方隨性揀 設定遂能成
  • 28 |
29 |
30 | 開始 31 |
32 |
33 |
34 | 42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /app.nw/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rimekit", 3 | "description": "Rime Kit", 4 | "version": "0.1.0", 5 | "homepage": "https://github.com/lotem/rimekit", 6 | "author": { 7 | "name": "lotem", 8 | "email": "chen.sst@gmail.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/lotem/rimekit.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/lotem/rimekit/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/lotem/rimekit/blob/master/LICENSE" 21 | } 22 | ], 23 | "main": "index.html", 24 | "engines": { 25 | "node": ">= 0.8.0" 26 | }, 27 | "dependencies": { 28 | "bluebird": "*", 29 | "coffee-script": "*", 30 | "diff": "*", 31 | "escape-html": "*", 32 | "js-yaml": "*", 33 | "node-marisa-trie": ">= 0.1.2", 34 | "request": "*" 35 | }, 36 | "optionalDependencies": { 37 | "windows": "*" 38 | }, 39 | "keywords": [] 40 | } 41 | -------------------------------------------------------------------------------- /app.nw/rimekit.coffee: -------------------------------------------------------------------------------- 1 | # done in index.html instead 2 | #app = angular.module 'rimekit', ['ui.bootstrap'] 3 | 4 | customRimeUserDir = -> 5 | try 6 | key = require('windows')?.registry('HKCU/Software/Rime/Weasel') 7 | catch 8 | console.warn 'could not access Windows registry.' 9 | key?.RimeUserDir?.value 10 | 11 | weaselInstallDir = -> 12 | try 13 | key = require('windows')?.registry('HKLM/Software/Rime/Weasel') 14 | catch 15 | console.warn 'could not access Windows registry.' 16 | key?.WeaselRoot?.value 17 | 18 | app.factory 'rimekitService', -> 19 | console.log "version: #{process.version}" 20 | console.log "platform: #{process.platform}" 21 | if process.platform == 'darwin' 22 | home = process.env['HOME'] ? '.' 23 | rimeUserDir = "#{home}/Library/Rime" 24 | rimeSharedDir = "/Library/Input Methods/Squirrel.app/Contents/SharedSupport" 25 | else if process.platform == 'linux' 26 | home = process.env['HOME'] ? '.' 27 | rimeUserDir = "#{home}/.config/ibus/rime" 28 | rimeSharedDir = "/usr/share/rime-data" 29 | else if process.platform == 'win32' 30 | appdata = process.env['APPDATA'] 31 | rimeUserDir = customRimeUserDir() or "#{appdata}\\Rime" 32 | rimeSharedDir = "#{weaselInstallDir()}\\data" 33 | console.log "Rime user directory: #{rimeUserDir}" 34 | console.log "Rime shared directory: #{rimeSharedDir}" 35 | nodeVersion: process.version 36 | platform: process.platform 37 | rimeUserDir: rimeUserDir 38 | rimeSharedDir: rimeSharedDir 39 | 40 | app.controller 'MainCtrl', ($scope) -> 41 | $scope.tabs = 42 | b_brise: 43 | title: '東風破.net' 44 | source: 'brise.html' 45 | c_sadebugger: 46 | title: '拼寫運算調試器' 47 | source: 'sadebugger.html' 48 | d_cseditor: 49 | title: '配色編輯器' 50 | e_credits: 51 | title: '創作者' 52 | source: 'credits.html' 53 | $scope.links = 54 | blog: 'http://rimeime.github.io' 55 | project: 'http://code.google.com/p/rimeime/' 56 | $scope.openLink = (url) -> 57 | require('nw.gui').Shell.openExternal url 58 | -------------------------------------------------------------------------------- /app.nw/rimekit.css: -------------------------------------------------------------------------------- 1 | .code { 2 | font-family: monospace; 3 | } 4 | .error-false { 5 | color: blue; 6 | } 7 | .error-true { 8 | color: red; 9 | } 10 | ins { 11 | text-decoration: none; 12 | color: darkblue; 13 | background-color: #d1e4ff; 14 | -webkit-border-radius: 3px; 15 | -moz-border-radius: 3px; 16 | border-radius: 3px; 17 | border: 1px solid #eaeaef; 18 | } 19 | del { 20 | text-decoration: line-through; 21 | color: gray; 22 | background-color: lightgray; 23 | -webkit-border-radius: 3px; 24 | -moz-border-radius: 3px; 25 | border-radius: 3px; 26 | border: 1px solid #eaeaef; 27 | } 28 | em { 29 | font-style: normal; 30 | color: darkgreen; 31 | background-color: #d0ffd0; 32 | -webkit-border-radius: 3px; 33 | -moz-border-radius: 3px; 34 | border-radius: 3px; 35 | border: 1px solid #eaeaef; 36 | } 37 | -------------------------------------------------------------------------------- /app.nw/sadebugger.coffee: -------------------------------------------------------------------------------- 1 | fs = require('fs') 2 | diff = require('diff') 3 | escape = require('escape-html') 4 | rime = require('./rime') 5 | 6 | stringDiff = (x, element, attrs) -> 7 | oldValue = x?.previous?.toString() ? '' 8 | newValue = x?.toString() ? '' 9 | if oldValue == newValue 10 | element.text newValue 11 | return 12 | diffMethod = 13 | if attrs.unit is 'char' then diff.diffChars else diff.diffWords 14 | changes = diffMethod(oldValue, newValue) 15 | element.html diff.convertChangesToXML(changes) 16 | 17 | scriptDiff = (x, element, attrs) -> 18 | unless x?.previous? 19 | element.text x?.toString() ? '' 20 | return 21 | compareSpellingByText = (a, b) -> 22 | if a.text < b.text then -1 else if a.text > b.text then 1 else 0 23 | os = x.previous.getSpellings().sort compareSpellingByText 24 | ns = x.getSpellings().sort compareSpellingByText 25 | changes = [] 26 | while os.length > 0 and ns.length > 0 27 | ot = os[0].text 28 | nt = ns[0].text 29 | if ot < nt 30 | os.shift() 31 | changes.push '' + escape(ot) + '' 32 | else if ot > nt 33 | ns.shift() 34 | changes.push '' + escape(nt) + '' 35 | else 36 | if os.shift() isnt ns.shift() 37 | changes.push '' + escape(nt) + '' 38 | else # no change 39 | changes.push escape(nt) 40 | while os.length > 0 41 | changes.push '' + escape(os.shift().text) + '' 42 | while ns.length > 0 43 | changes.push '' + escape(ns.shift().text) + '' 44 | element.html changes.join ' ' 45 | 46 | app.directive 'diff', -> 47 | restrict: 'E' 48 | link: (scope, element, attrs) -> 49 | scope.$watch attrs.value, (x) -> 50 | if attrs.type is 'script' 51 | scriptDiff(x, element, attrs) 52 | else 53 | stringDiff(x, element, attrs) 54 | 55 | app.directive 'query', -> 56 | restrict: 'E' 57 | scope: 58 | update: '&' 59 | visible: '@' 60 | controller: ($scope) -> 61 | $scope.change = -> 62 | console.debug 'change:', @value 63 | @pattern = @error = null 64 | if @value 65 | try 66 | @pattern = new RegExp @value 67 | catch error 68 | console.error "bad query: #{error}" 69 | @error = error 70 | template: '''
71 | 77 |
''' 78 | 79 | app.controller 'AlgebraCtrl', ($scope, rimekitService) -> 80 | $scope.configKeys = [ 81 | 'speller/algebra' 82 | 'translator/preedit_format' 83 | 'translator/comment_format' 84 | 'reverse_lookup/preedit_format' 85 | 'reverse_lookup/comment_format' 86 | ] 87 | 88 | $scope.rimeUserDir = rimekitService.rimeUserDir 89 | $scope.schemaId = 'luna_pinyin' 90 | $scope.configKey = 'speller/algebra' 91 | $scope.rules = [] 92 | $scope.syllabary = [] 93 | $scope.alerts = [] 94 | 95 | $scope.init = -> 96 | 97 | $scope.loadSchema = -> 98 | @rules = [] 99 | @syllabary = [] 100 | @alerts.length = 0 101 | return unless @schemaId && @configKey 102 | filePath = "#{@rimeUserDir ? '.'}/#{@schemaId}.schema.yaml" 103 | unless fs.existsSync filePath 104 | console.warn "file does not exist: #{filePath}" 105 | @alerts.push type: 'error', msg: '找不到輸入方案' 106 | return 107 | config = new rime.Config 108 | config.loadFile(filePath) 109 | .then => 110 | @$apply => 111 | @dictName = config.get 'translator/dictionary' ? '' 112 | rules = config.get @configKey 113 | @rules = (new rime.Rule(x) for x in rules) if rules 114 | console.log "#{@rules.length} rules loaded." 115 | if @rules.length != 0 116 | @rules.unshift new rime.Rule # initial state 117 | @isProjector = @configKey.match(/\/algebra$/) != null 118 | @isFormatter = @configKey.match(/format$/) != null 119 | @calculate() 120 | .catch (err) => 121 | @$apply => 122 | @alerts.push type: 'error', msg: '載入輸入方案錯誤' 123 | 124 | $scope.loadDict = -> 125 | @syllabary = [] 126 | @alerts.length = 0 127 | return unless @dictName 128 | filePath = "#{@rimeUserDir ? '.'}/#{@dictName}.table.bin" 129 | table = new rime.Table 130 | table.loadFile(filePath) 131 | .then => 132 | @$apply => 133 | @syllabary = table.syllabary 134 | console.log "#{@syllabary.length} syllables loaded." 135 | @calculate() 136 | .catch (err) => 137 | @$apply => 138 | @alerts.push type: 'error', msg: '載入詞典錯誤' 139 | 140 | $scope.calculate = -> 141 | if @rules.length == 0 142 | @alerts.push type: 'error', msg: '無有定義拼寫運算規則' 143 | return 144 | algebra = new rime.Algebra @rules 145 | if @isProjector and @syllabary.length 146 | console.log "calulate: [#{@syllabary.length} syllables]" 147 | algebra.makeProjection rime.Script.fromSyllabary @syllabary 148 | for r in @rules 149 | r.queryResult = r.script 150 | if @isFormatter and @testString 151 | console.log "calulate: \"#{@testString}\"" 152 | algebra.formatString @testString ? '' 153 | 154 | $scope.closeAlert = (index) -> 155 | @alerts.splice index, 1 156 | 157 | $scope.querySpellings = (index, pattern) -> 158 | console.log 'querySpellings:', index, pattern 159 | return unless @rules[index]?.script 160 | 161 | unless pattern 162 | for r in @rules 163 | r.queryResult = r.script 164 | console.log 'cleared query result.' 165 | return 166 | 167 | q = @rules[index].queryResult = @rules[index].script.query pattern 168 | 169 | r = q 170 | for j in [index - 1..0] by -1 171 | r = @rules[j].queryResult = r.queryPrevious @rules[j].script 172 | 173 | r = q 174 | for j in [index + 1...@rules.length] 175 | r = @rules[j].queryResult = r.queryNext @rules[j].script 176 | -------------------------------------------------------------------------------- /app.nw/sadebugger.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 |
21 | 24 | 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | {{alert.msg}} 45 | 46 | 47 | 51 | 54 | 57 | 58 |
48 |
{{rule.formula}}
49 | 50 |
52 | 53 | 55 | 56 |
59 |
60 | -------------------------------------------------------------------------------- /app.nw/ui-bootstrap-tpls.min.js: -------------------------------------------------------------------------------- 1 | angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/popup.html","template/dialog/message.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset-titles.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(e,t,n){function a(e){for(var t in e)if(void 0!==o.style[t])return e[t]}var i=function(a,o,r){r=r||{};var l=e.defer(),s=i[r.animation?"animationEndEventName":"transitionEndEventName"],c=function(){n.$apply(function(){a.unbind(s,c),l.resolve(a)})};return s&&a.bind(s,c),t(function(){angular.isString(o)?a.addClass(o):angular.isFunction(o)?o(a):angular.isObject(o)&&a.css(o),s||l.resolve(a)}),l.promise.cancel=function(){s&&a.unbind(s,c),l.reject("Transition cancelled")},l.promise},o=document.createElement("trans"),r={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},l={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return i.transitionEndEventName=a(r),i.animationEndEventName=a(l),i}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(e){var t=function(e,t,n){t.removeClass("collapse"),t.css({height:n}),t[0].offsetWidth,t.addClass("collapse")};return{link:function(n,a,i){var o,r=!0;n.$watch(function(){return a[0].scrollHeight},function(){0!==a[0].scrollHeight&&(o||(r?t(n,a,a[0].scrollHeight+"px"):t(n,a,"auto")))}),n.$watch(i.collapse,function(e){e?u():c()});var l,s=function(t){return l&&l.cancel(),l=e(a,t),l.then(function(){l=void 0},function(){l=void 0}),l},c=function(){r?(r=!1,o||t(n,a,"auto")):s({height:a[0].scrollHeight+"px"}).then(function(){o||t(n,a,"auto")}),o=!1},u=function(){o=!0,r?(r=!1,t(n,a,0)):(t(n,a,a[0].scrollHeight+"px"),s({height:"0"}))}}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(e,t,n){this.groups=[],this.closeOthers=function(a){var i=angular.isDefined(t.closeOthers)?e.$eval(t.closeOthers):n.closeOthers;i&&angular.forEach(this.groups,function(e){e!==a&&(e.isOpen=!1)})},this.addGroup=function(e){var t=this;this.groups.push(e),e.$on("$destroy",function(){t.removeGroup(e)})},this.removeGroup=function(e){var t=this.groups.indexOf(e);-1!==t&&this.groups.splice(this.groups.indexOf(e),1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse","$transition","$timeout",function(e){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:["$scope",function(){this.setHeading=function(e){this.heading=e}}],link:function(t,n,a,i){var o,r;i.addGroup(t),t.isOpen=!1,a.isOpen&&(o=e(a.isOpen),r=o.assign,t.$watch(function(){return o(t.$parent)},function(e){t.isOpen=e}),t.isOpen=o?o(t.$parent):!1),t.$watch("isOpen",function(e){e&&i.closeOthers(t),r&&r(t.$parent,e)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(e,t,n){return function(e,t,a,i){i.setHeading(n(e,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(e,t,n,a){e.$watch(function(){return a[n.accordionTransclude]},function(e){e&&(t.html(""),t.append(e))})}}}),angular.module("ui.bootstrap.alert",[]).directive("alert",function(){return{restrict:"EA",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"},link:function(e,t,n){e.closeable="close"in n}}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).directive("btnRadio",["buttonConfig",function(e){var t=e.activeClass||"active",n=e.toggleEvent||"click";return{require:"ngModel",link:function(e,a,i,o){o.$render=function(){a.toggleClass(t,angular.equals(o.$modelValue,e.$eval(i.btnRadio)))},a.bind(n,function(){a.hasClass(t)||e.$apply(function(){o.$setViewValue(e.$eval(i.btnRadio)),o.$render()})})}}}]).directive("btnCheckbox",["buttonConfig",function(e){var t=e.activeClass||"active",n=e.toggleEvent||"click";return{require:"ngModel",link:function(e,a,i,o){function r(){var t=e.$eval(i.btnCheckboxTrue);return angular.isDefined(t)?t:!0}function l(){var t=e.$eval(i.btnCheckboxFalse);return angular.isDefined(t)?t:!1}o.$render=function(){a.toggleClass(t,angular.equals(o.$modelValue,r()))},a.bind(n,function(){e.$apply(function(){o.$setViewValue(a.hasClass(t)?l():r()),o.$render()})})}}}]),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition","$q",function(e,t,n){function a(){function n(){o?(e.next(),a()):e.pause()}i&&t.cancel(i);var r=+e.interval;!isNaN(r)&&r>=0&&(i=t(n,r))}var i,o,r=this,l=r.slides=[],s=-1;r.currentSlide=null,r.select=function(i,o){function c(){r.currentSlide&&angular.isString(o)&&!e.noTransition&&i.$element?(i.$element.addClass(o),i.$element[0].offsetWidth=i.$element[0].offsetWidth,angular.forEach(l,function(e){angular.extend(e,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(i,{direction:o,active:!0,entering:!0}),angular.extend(r.currentSlide||{},{direction:o,leaving:!0}),e.$currentTransition=n(i.$element,{}),function(t,n){e.$currentTransition.then(function(){u(t,n)},function(){u(t,n)})}(i,r.currentSlide)):u(i,r.currentSlide),r.currentSlide=i,s=p,a()}function u(t,n){angular.extend(t,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(n||{},{direction:"",active:!1,leaving:!1,entering:!1}),e.$currentTransition=null}var p=l.indexOf(i);void 0===o&&(o=p>s?"next":"prev"),i&&i!==r.currentSlide&&(e.$currentTransition?(e.$currentTransition.cancel(),t(c)):c())},r.indexOfSlide=function(e){return l.indexOf(e)},e.next=function(){var t=(s+1)%l.length;return e.$currentTransition?void 0:r.select(l[t],"next")},e.prev=function(){var t=0>s-1?l.length-1:s-1;return e.$currentTransition?void 0:r.select(l[t],"prev")},e.select=function(e){r.select(e)},e.isActive=function(e){return r.currentSlide===e},e.slides=function(){return l},e.$watch("interval",a),e.play=function(){o||(o=!0,a())},e.pause=function(){e.noPause||(o=!1,i&&t.cancel(i))},r.addSlide=function(t,n){t.$element=n,l.push(t),1===l.length||t.active?(r.select(l[l.length-1]),1==l.length&&e.play()):t.active=!1},r.removeSlide=function(e){var t=l.indexOf(e);l.splice(t,1),l.length>0&&e.active?t>=l.length?r.select(l[t-1]):r.select(l[t]):s>t&&s--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",["$parse",function(e){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{},link:function(t,n,a,i){if(a.active){var o=e(a.active),r=o.assign,l=t.active=o(t.$parent);t.$watch(function(){var e=o(t.$parent);return e!==t.active&&(e!==l?l=t.active=e:r(t.$parent,e=l=t.active)),e})}i.addSlide(t,n),t.$on("$destroy",function(){i.removeSlide(t)}),t.$watch("active",function(e){e&&i.select(t)})}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(e,t){function n(e,n){return e.currentStyle?e.currentStyle[n]:t.getComputedStyle?t.getComputedStyle(e)[n]:e.style[n]}function a(e){return"static"===(n(e,"position")||"static")}var i,o;e.bind("mousemove",function(e){i=e.pageX,o=e.pageY});var r=function(t){for(var n=e[0],i=t.offsetParent||n;i&&i!==n&&a(i);)i=i.offsetParent;return i||n};return{position:function(t){var n=this.offset(t),a={top:0,left:0},i=r(t[0]);return i!=e[0]&&(a=this.offset(angular.element(i)),a.top+=i.clientTop-i.scrollTop,a.left+=i.clientLeft-i.scrollLeft),{width:t.prop("offsetWidth"),height:t.prop("offsetHeight"),top:n.top-a.top,left:n.left-a.left}},offset:function(n){var a=n[0].getBoundingClientRect();return{width:n.prop("offsetWidth"),height:n.prop("offsetHeight"),top:a.top+(t.pageYOffset||e[0].body.scrollTop),left:a.left+(t.pageXOffset||e[0].body.scrollLeft)}},mouse:function(){return{x:i,y:o}}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.position"]).constant("datepickerConfig",{dayFormat:"dd",monthFormat:"MMMM",yearFormat:"yyyy",dayHeaderFormat:"EEE",dayTitleFormat:"MMMM yyyy",monthTitleFormat:"yyyy",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","dateFilter","datepickerConfig",function(e,t,n,a){function i(t,n){return angular.isDefined(t)?e.$parent.$eval(t):n}function o(e,t){return new Date(e,t,0).getDate()}function r(e,t){for(var n=Array(t),a=e,i=0;t>i;)n[i++]=new Date(a),a.setDate(a.getDate()+1);return n}function l(e,t,a,i){return{date:e,label:n(e,t),selected:!!a,secondary:!!i}}var s={day:i(t.dayFormat,a.dayFormat),month:i(t.monthFormat,a.monthFormat),year:i(t.yearFormat,a.yearFormat),dayHeader:i(t.dayHeaderFormat,a.dayHeaderFormat),dayTitle:i(t.dayTitleFormat,a.dayTitleFormat),monthTitle:i(t.monthTitleFormat,a.monthTitleFormat)},c=i(t.startingDay,a.startingDay),u=i(t.yearRange,a.yearRange);this.minDate=a.minDate?new Date(a.minDate):null,this.maxDate=a.maxDate?new Date(a.maxDate):null,this.modes=[{name:"day",getVisibleDates:function(e,t){var a=e.getFullYear(),i=e.getMonth(),u=new Date(a,i,1),p=c-u.getDay(),d=p>0?7-p:-p,m=new Date(u),g=0;d>0&&(m.setDate(-d+1),g+=d),g+=o(a,i+1),g+=(7-g%7)%7;for(var f=r(m,g),h=Array(7),v=0;g>v;v++){var b=new Date(f[v]);f[v]=l(b,s.day,t&&t.getDate()===b.getDate()&&t.getMonth()===b.getMonth()&&t.getFullYear()===b.getFullYear(),b.getMonth()!==i)}for(var $=0;7>$;$++)h[$]=n(f[$].date,s.dayHeader);return{objects:f,title:n(e,s.dayTitle),labels:h}},compare:function(e,t){return new Date(e.getFullYear(),e.getMonth(),e.getDate())-new Date(t.getFullYear(),t.getMonth(),t.getDate())},split:7,step:{months:1}},{name:"month",getVisibleDates:function(e,t){for(var a=Array(12),i=e.getFullYear(),o=0;12>o;o++){var r=new Date(i,o,1);a[o]=l(r,s.month,t&&t.getMonth()===o&&t.getFullYear()===i)}return{objects:a,title:n(e,s.monthTitle)}},compare:function(e,t){return new Date(e.getFullYear(),e.getMonth())-new Date(t.getFullYear(),t.getMonth())},split:3,step:{years:1}},{name:"year",getVisibleDates:function(e,t){for(var n=Array(u),a=e.getFullYear(),i=parseInt((a-1)/u,10)*u+1,o=0;u>o;o++){var r=new Date(i+o,0,1);n[o]=l(r,s.year,t&&t.getFullYear()===r.getFullYear())}return{objects:n,title:[n[0].label,n[u-1].label].join(" - ")}},compare:function(e,t){return e.getFullYear()-t.getFullYear()},split:5,step:{years:u}}],this.isDisabled=function(t,n){var a=this.modes[n||0];return this.minDate&&0>a.compare(t,this.minDate)||this.maxDate&&a.compare(t,this.maxDate)>0||e.dateDisabled&&e.dateDisabled({date:t,mode:a.name})}}]).directive("datepicker",["dateFilter","$parse","datepickerConfig","$log",function(e,t,n,a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(e,i,o,r){function l(){e.showWeekNumbers=0===g&&h}function s(e,t){for(var n=[];e.length>0;)n.push(e.splice(0,t));return n}function c(t){var n=null,i=!0;m.$modelValue&&(n=new Date(m.$modelValue),isNaN(n)?(i=!1,a.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):t&&(f=n)),m.$setValidity("date",i);var o=d.modes[g],r=o.getVisibleDates(f,n);angular.forEach(r.objects,function(e){e.disabled=d.isDisabled(e.date,g)}),m.$setValidity("date-disabled",!n||!d.isDisabled(n)),e.rows=s(r.objects,o.split),e.labels=r.labels||[],e.title=r.title}function u(e){g=e,l(),c()}function p(e){var t=new Date(e);t.setDate(t.getDate()+4-(t.getDay()||7));var n=t.getTime();return t.setMonth(0),t.setDate(1),Math.floor(Math.round((n-t)/864e5)/7)+1}var d=r[0],m=r[1];if(m){var g=0,f=new Date,h=n.showWeeks;o.showWeeks?e.$parent.$watch(t(o.showWeeks),function(e){h=!!e,l()}):l(),o.min&&e.$parent.$watch(t(o.min),function(e){d.minDate=e?new Date(e):null,c()}),o.max&&e.$parent.$watch(t(o.max),function(e){d.maxDate=e?new Date(e):null,c()}),m.$render=function(){c(!0)},e.select=function(e){if(0===g){var t=new Date(m.$modelValue);t.setFullYear(e.getFullYear(),e.getMonth(),e.getDate()),m.$setViewValue(t),c(!0)}else f=e,u(g-1)},e.move=function(e){var t=d.modes[g].step;f.setMonth(f.getMonth()+e*(t.months||0)),f.setFullYear(f.getFullYear()+e*(t.years||0)),c()},e.toggleMode=function(){u((g+1)%d.modes.length)},e.getWeekNumber=function(t){return 0===g&&e.showWeekNumbers&&7===t.length?p(t[0].date):null}}}}}]).constant("datepickerPopupConfig",{dateFormat:"yyyy-MM-dd",closeOnDateSelection:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","datepickerPopupConfig",function(e,t,n,a,i,o){return{restrict:"EA",require:"ngModel",link:function(r,l,s,c){function u(e){return e?i(e,h):null}function p(e){if(e){var t=new Date(e);if(!isNaN(t))return t}return e}function d(e){$?$(r,!!e):v.isOpen=!!e}function m(e,n,a){e&&(r.$watch(t(e),function(e){v[n]=e}),D.attr(a||n,n))}function g(){v.position=a.position(l),v.position.top=v.position.top+l.prop("offsetHeight")}var f=angular.isDefined(s.closeOnDateSelection)?v.$eval(s.closeOnDateSelection):o.closeOnDateSelection,h=s.datepickerPopup||o.dateFormat,v=r.$new();r.$on("$destroy",function(){v.$destroy()}),c.$formatters.push(u),c.$parsers.push(p);var b,$;s.open&&(b=t(s.open),$=b.assign,r.$watch(b,function(e){v.isOpen=!!e})),v.isOpen=b?b(r):!1;var y=function(e){v.isOpen&&e.target!==l[0]&&v.$apply(function(){d(!1)})},w=function(){v.$apply(function(){d(!0)})},k=angular.element("");k.attr({"ng-model":"date","ng-change":"dateSelection()"});var D=k.find("datepicker");s.datepickerOptions&&D.attr(angular.extend({},r.$eval(s.datepickerOptions)));var x=t(s.ngModel).assign;v.dateSelection=function(){x(r,v.date),f&&d(!1)},v.$watch(function(){return c.$modelValue},function(e){if(angular.isString(e)){var t=p(e);if(e&&!t)throw x(r,null),Error(e+" cannot be parsed to a date object.");e=t}v.date=e,g()}),m(s.min,"min"),m(s.max,"max"),s.showWeeks?m(s.showWeeks,"showWeeks","show-weeks"):(v.showWeeks=!0,D.attr("show-weeks","showWeeks")),s.dateDisabled&&D.attr("date-disabled",s.dateDisabled),v.$watch("isOpen",function(e){e?(g(),n.bind("click",y),l.unbind("focus",w),l.focus()):(n.unbind("click",y),l.bind("focus",w)),$&&$(r,e)}),v.today=function(){x(r,new Date)},v.clear=function(){x(r,null)},l.after(e(k)(v))}}}]).directive("datepickerPopupWrap",[function(){return{restrict:"E",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(e,t){t.bind("click",function(e){e.preventDefault(),e.stopPropagation()})}}}]);var dialogModule=angular.module("ui.bootstrap.dialog",["ui.bootstrap.transition"]);dialogModule.controller("MessageBoxController",["$scope","dialog","model",function(e,t,n){e.title=n.title,e.message=n.message,e.buttons=n.buttons,e.close=function(e){t.close(e)}}]),dialogModule.provider("$dialog",function(){var e={backdrop:!0,dialogClass:"modal",backdropClass:"modal-backdrop",transitionClass:"fade",triggerClass:"in",resolve:{},backdropFade:!1,dialogFade:!1,keyboard:!0,backdropClick:!0},t={},n={value:0};this.options=function(e){t=e},this.$get=["$http","$document","$compile","$rootScope","$controller","$templateCache","$q","$transition","$injector",function(a,i,o,r,l,s,c,u,p){function d(e){var t=angular.element("
");return t.addClass(e),t}function m(n){var a=this,i=this.options=angular.extend({},e,t,n);this._open=!1,this.backdropEl=d(i.backdropClass),i.backdropFade&&(this.backdropEl.addClass(i.transitionClass),this.backdropEl.removeClass(i.triggerClass)),this.modalEl=d(i.dialogClass),i.dialogFade&&(this.modalEl.addClass(i.transitionClass),this.modalEl.removeClass(i.triggerClass)),this.handledEscapeKey=function(e){27===e.which&&(a.close(),e.preventDefault(),a.$scope.$apply())},this.handleBackDropClick=function(e){a.close(),e.preventDefault(),a.$scope.$apply()}}var g=i.find("body");return m.prototype.isOpen=function(){return this._open},m.prototype.open=function(e,t){var n=this,a=this.options;if(e&&(a.templateUrl=e),t&&(a.controller=t),!a.template&&!a.templateUrl)throw Error("Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.");return this._loadResolves().then(function(e){var t=e.$scope=n.$scope=e.$scope?e.$scope:r.$new();if(n.modalEl.html(e.$template),n.options.controller){var a=l(n.options.controller,e);n.modalEl.children().data("ngControllerController",a)}o(n.modalEl)(t),n._addElementsToDom(),setTimeout(function(){n.options.dialogFade&&n.modalEl.addClass(n.options.triggerClass),n.options.backdropFade&&n.backdropEl.addClass(n.options.triggerClass)}),n._bindEvents()}),this.deferred=c.defer(),this.deferred.promise},m.prototype.close=function(e){function t(e){e.removeClass(a.options.triggerClass)}function n(){a._open&&a._onCloseComplete(e)}var a=this,i=this._getFadingElements();if(i.length>0)for(var o=i.length-1;o>=0;o--)u(i[o],t).then(n);else this._onCloseComplete(e)},m.prototype._getFadingElements=function(){var e=[];return this.options.dialogFade&&e.push(this.modalEl),this.options.backdropFade&&e.push(this.backdropEl),e},m.prototype._bindEvents=function(){this.options.keyboard&&g.bind("keydown",this.handledEscapeKey),this.options.backdrop&&this.options.backdropClick&&this.backdropEl.bind("click",this.handleBackDropClick)},m.prototype._unbindEvents=function(){this.options.keyboard&&g.unbind("keydown",this.handledEscapeKey),this.options.backdrop&&this.options.backdropClick&&this.backdropEl.unbind("click",this.handleBackDropClick)},m.prototype._onCloseComplete=function(e){this._removeElementsFromDom(),this._unbindEvents(),this.deferred.resolve(e)},m.prototype._addElementsToDom=function(){g.append(this.modalEl),this.options.backdrop&&(0===n.value&&g.append(this.backdropEl),n.value++),this._open=!0},m.prototype._removeElementsFromDom=function(){this.modalEl.remove(),this.options.backdrop&&(n.value--,0===n.value&&this.backdropEl.remove()),this._open=!1},m.prototype._loadResolves=function(){var e,t=[],n=[],i=this;return this.options.template?e=c.when(this.options.template):this.options.templateUrl&&(e=a.get(this.options.templateUrl,{cache:s}).then(function(e){return e.data})),angular.forEach(this.options.resolve||[],function(e,a){n.push(a),t.push(angular.isString(e)?p.get(e):p.invoke(e))}),n.push("$template"),t.push(e),c.all(t).then(function(e){var t={};return angular.forEach(e,function(e,a){t[n[a]]=e}),t.dialog=i,t})},{dialog:function(e){return new m(e)},messageBox:function(e,t,n){return new m({templateUrl:"template/dialog/message.html",controller:"MessageBoxController",resolve:{model:function(){return{title:e,message:t,buttons:n}}}})}}}]}),angular.module("ui.bootstrap.dropdownToggle",[]).directive("dropdownToggle",["$document","$location",function(e){var t=null,n=angular.noop;return{restrict:"CA",link:function(a,i){a.$watch("$location.path",function(){n()}),i.parent().bind("click",function(){n()}),i.bind("click",function(a){var o=i===t;a.preventDefault(),a.stopPropagation(),t&&n(),o||(i.parent().addClass("open"),t=i,n=function(a){a&&(a.preventDefault(),a.stopPropagation()),e.unbind("click",n),i.parent().removeClass("open"),n=angular.noop,t=null},e.bind("click",n))})}}}]),angular.module("ui.bootstrap.modal",["ui.bootstrap.dialog"]).directive("modal",["$parse","$dialog",function(e,t){return{restrict:"EA",terminal:!0,link:function(n,a,i){var o,r=angular.extend({},n.$eval(i.uiOptions||i.bsOptions||i.options)),l=i.modal||i.show;r=angular.extend(r,{template:a.html(),resolve:{$scope:function(){return n}}});var s=t.dialog(r);a.remove(),o=i.close?function(){e(i.close)(n)}:function(){angular.isFunction(e(l).assign)&&e(l).assign(n,!1)},n.$watch(l,function(e){e?s.open().then(function(){o()}):s.isOpen()&&s.close()})}}}]),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$interpolate",function(e,t){this.currentPage=1,this.noPrevious=function(){return 1===this.currentPage},this.noNext=function(){return this.currentPage===e.numPages},this.isActive=function(e){return this.currentPage===e},this.reset=function(){e.pages=[],this.currentPage=parseInt(e.currentPage,10),this.currentPage>e.numPages&&e.selectPage(e.numPages)};var n=this;e.selectPage=function(t){!n.isActive(t)&&t>0&&e.numPages>=t&&(e.currentPage=t,e.onSelectPage({page:t}))},this.getAttributeValue=function(n,a,i){return angular.isDefined(n)?i?t(n)(e.$parent):e.$parent.$eval(n):a}}]).constant("paginationConfig",{boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["paginationConfig",function(e){return{restrict:"EA",scope:{numPages:"=",currentPage:"=",maxSize:"=",onSelectPage:"&"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(t,n,a,i){function o(e,t,n,a){return{number:e,text:t,active:n,disabled:a}}var r=i.getAttributeValue(a.boundaryLinks,e.boundaryLinks),l=i.getAttributeValue(a.directionLinks,e.directionLinks),s=i.getAttributeValue(a.firstText,e.firstText,!0),c=i.getAttributeValue(a.previousText,e.previousText,!0),u=i.getAttributeValue(a.nextText,e.nextText,!0),p=i.getAttributeValue(a.lastText,e.lastText,!0),d=i.getAttributeValue(a.rotate,e.rotate);t.$watch("numPages + currentPage + maxSize",function(){i.reset();var e=1,n=t.numPages,a=angular.isDefined(t.maxSize)&&t.maxSizet.numPages&&(n=t.numPages,e=n-t.maxSize+1)):(e=(Math.ceil(i.currentPage/t.maxSize)-1)*t.maxSize+1,n=Math.min(e+t.maxSize-1,t.numPages)));for(var m=e;n>=m;m++){var g=o(m,m,i.isActive(m),!1);t.pages.push(g)}if(a&&!d){if(e>1){var f=o(e-1,"...",!1,!1);t.pages.unshift(f)}if(t.numPages>n){var h=o(n+1,"...",!1,!1);t.pages.push(h)}}if(l){var v=o(i.currentPage-1,c,!1,i.noPrevious());t.pages.unshift(v);var b=o(i.currentPage+1,u,!1,i.noNext());t.pages.push(b)}if(r){var $=o(1,s,!1,i.noPrevious());t.pages.unshift($);var y=o(t.numPages,p,!1,i.noNext());t.pages.push(y)}})}}}]).constant("pagerConfig",{previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(e){return{restrict:"EA",scope:{numPages:"=",currentPage:"=",onSelectPage:"&"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(t,n,a,i){function o(e,t,n,a,i){return{number:e,text:t,disabled:n,previous:s&&a,next:s&&i}}var r=i.getAttributeValue(a.previousText,e.previousText,!0),l=i.getAttributeValue(a.nextText,e.nextText,!0),s=i.getAttributeValue(a.align,e.align);t.$watch("numPages + currentPage",function(){i.reset();var e=o(i.currentPage-1,r,i.noPrevious(),!0,!1);t.pages.unshift(e);var n=o(i.currentPage+1,l,i.noNext(),!1,!0);t.pages.push(n)})}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position"]).provider("$tooltip",function(){function e(e){var t=/[A-Z]/g,n="-";return e.replace(t,function(e,t){return(t?n:"")+e.toLowerCase()})}var t={placement:"top",animation:!0,popupDelay:0},n={mouseenter:"mouseleave",click:"click",focus:"blur"},a={};this.options=function(e){angular.extend(a,e)},this.setTriggers=function(e){angular.extend(n,e)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(i,o,r,l,s,c,u){return function(i,p,d){function m(e){var t=e||g.trigger||d,a=n[t]||t;return{show:t,hide:a}}var g=angular.extend({},t,a),f=e(i),h=u.startSymbol(),v=u.endSymbol(),b="<"+f+"-popup "+'title="'+h+"tt_title"+v+'" '+'content="'+h+"tt_content"+v+'" '+'placement="'+h+"tt_placement"+v+'" '+'animation="tt_animation()" '+'is-open="tt_isOpen"'+">"+"";return{restrict:"EA",scope:!0,link:function(e,t,n){function a(){e.tt_isOpen?d():u()}function u(){e.tt_popupDelay?$=r(f,e.tt_popupDelay):e.$apply(f)}function d(){e.$apply(function(){h()})}function f(){var n,a,i,o;if(e.tt_content){switch(v&&r.cancel(v),w.css({top:0,left:0,display:"block"}),k?(y=y||s.find("body"),y.append(w)):t.after(w),n=k?c.offset(t):c.position(t),a=w.prop("offsetWidth"),i=w.prop("offsetHeight"),e.tt_placement){case"mouse":var l=c.mouse();o={top:l.y,left:l.x};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width};break;case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-a/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-a};break;default:o={top:n.top-i,left:n.left+n.width/2-a/2}}o.top+="px",o.left+="px",w.css(o),e.tt_isOpen=!0}}function h(){e.tt_isOpen=!1,r.cancel($),angular.isDefined(e.tt_animation)&&e.tt_animation()?v=r(function(){w.remove()},500):w.remove()}var v,$,y,w=o(b)(e),k=angular.isDefined(g.appendToBody)?g.appendToBody:!1,D=m(void 0),x=!1;e.tt_isOpen=!1,n.$observe(i,function(t){e.tt_content=t}),n.$observe(p+"Title",function(t){e.tt_title=t}),n.$observe(p+"Placement",function(t){e.tt_placement=angular.isDefined(t)?t:g.placement}),n.$observe(p+"Animation",function(t){e.tt_animation=angular.isDefined(t)?l(t):function(){return g.animation}}),n.$observe(p+"PopupDelay",function(t){var n=parseInt(t,10);e.tt_popupDelay=isNaN(n)?g.popupDelay:n}),n.$observe(p+"Trigger",function(e){x&&(t.unbind(D.show,u),t.unbind(D.hide,d)),D=m(e),D.show===D.hide?t.bind(D.show,a):(t.bind(D.show,u),t.bind(D.hide,d)),x=!0}),n.$observe(p+"AppendToBody",function(t){k=angular.isDefined(t)?l(t)(e):k}),k&&e.$on("$locationChangeSuccess",function(){e.tt_isOpen&&h()}),e.$on("$destroy",function(){e.tt_isOpen?h():w.remove()})}}}}]}).directive("tooltipPopup",function(){return{restrict:"E",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(e){return e("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"E",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(e){return e("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$compile","$timeout","$parse","$window","$tooltip",function(e,t,n,a,i){return i("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",["ui.bootstrap.transition"]).constant("progressConfig",{animate:!0,autoType:!1,stackedTypes:["success","info","warning","danger"]}).controller("ProgressBarController",["$scope","$attrs","progressConfig",function(e,t,n){function a(e){return r[e]}var i=angular.isDefined(t.animate)?e.$eval(t.animate):n.animate,o=angular.isDefined(t.autoType)?e.$eval(t.autoType):n.autoType,r=angular.isDefined(t.stackedTypes)?e.$eval("["+t.stackedTypes+"]"):n.stackedTypes;this.makeBar=function(e,t,n){var r=angular.isObject(e)?e.value:e||0,l=angular.isObject(t)?t.value:t||0,s=angular.isObject(e)&&angular.isDefined(e.type)?e.type:o?a(n||0):null;return{from:l,to:r,type:s,animate:i}},this.addBar=function(t){e.bars.push(t),e.totalPercent+=t.to},this.clearBars=function(){e.bars=[],e.totalPercent=0},this.clearBars()}]).directive("progress",function(){return{restrict:"EA",replace:!0,controller:"ProgressBarController",scope:{value:"=percent",onFull:"&",onEmpty:"&"},templateUrl:"template/progressbar/progress.html",link:function(e,t,n,a){e.$watch("value",function(e,t){if(a.clearBars(),angular.isArray(e))for(var n=0,i=e.length;i>n;n++)a.addBar(a.makeBar(e[n],t[n],n));else a.addBar(a.makeBar(e,t))},!0),e.$watch("totalPercent",function(t){t>=100?e.onFull():0>=t&&e.onEmpty()},!0)}}}).directive("progressbar",["$transition",function(e){return{restrict:"EA",replace:!0,scope:{width:"=",old:"=",type:"=",animate:"="},templateUrl:"template/progressbar/bar.html",link:function(t,n){t.$watch("width",function(a){t.animate?(n.css("width",t.old+"%"),e(n,{width:a+"%"})):n.css("width",a+"%")})}}}]),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5}).directive("rating",["ratingConfig","$parse",function(e,t){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},templateUrl:"template/rating/rating.html",replace:!0,link:function(n,a,i){var o=angular.isDefined(i.max)?n.$parent.$eval(i.max):e.max;n.range=[];for(var r=1;o>=r;r++)n.range.push(r);n.rate=function(e){n.readonly||(n.value=e)},n.enter=function(e){n.readonly||(n.val=e),n.onHover({value:e})},n.reset=function(){n.val=angular.copy(n.value),n.onLeave()},n.reset(),n.$watch("value",function(e){n.val=e}),n.readonly=!1,i.readonly&&n.$parent.$watch(t(i.readonly),function(e){n.readonly=!!e})}}}]),angular.module("ui.bootstrap.tabs",[]).directive("tabs",function(){return function(){throw Error("The `tabs` directive is deprecated, please migrate to `tabset`. Instructions can be found at http://github.com/angular-ui/bootstrap/tree/master/CHANGELOG.md")}}).controller("TabsetController",["$scope","$element",function(e){var t=this,n=t.tabs=e.tabs=[];t.select=function(e){angular.forEach(n,function(e){e.active=!1}),e.active=!0},t.addTab=function(e){n.push(e),(1===n.length||e.active)&&t.select(e)},t.removeTab=function(e){var a=n.indexOf(e);if(e.active&&n.length>1){var i=a==n.length-1?a-1:a+1;t.select(n[i])}n.splice(a,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,require:"^tabset",scope:{},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",compile:function(e,t,n){return function(e,t,a,i){e.vertical=angular.isDefined(a.vertical)?e.$eval(a.vertical):!1,e.type=angular.isDefined(a.type)?e.$parent.$eval(a.type):"tabs",e.direction=angular.isDefined(a.direction)?e.$parent.$eval(a.direction):"top",e.tabsAbove="below"!=e.direction,i.$scope=e,i.$transcludeFn=n}}}}).directive("tab",["$parse","$http","$templateCache","$compile",function(e){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(t,n,a){return function(t,n,i,o){var r,l;i.active?(r=e(i.active),l=r.assign,t.$parent.$watch(r,function(e){t.active=!!e}),t.active=r(t.$parent)):l=r=angular.noop,t.$watch("active",function(e){l(t.$parent,e),e?(o.select(t),t.onSelect()):t.onDeselect()}),t.disabled=!1,i.disabled&&t.$parent.$watch(e(i.disabled),function(e){t.disabled=!!e 2 | }),t.select=function(){t.disabled||(t.active=!0)},o.addTab(t),t.$on("$destroy",function(){o.removeTab(t)}),t.active&&l(t.$parent,!0),t.$transcludeFn=a}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(e,t){e.$watch("headingElement",function(e){e&&(t.html(""),t.append(e))})}}}]).directive("tabContentTransclude",["$compile","$parse",function(){function e(e){return e.tagName&&(e.hasAttribute("tab-heading")||e.hasAttribute("data-tab-heading")||"tab-heading"===e.tagName.toLowerCase()||"data-tab-heading"===e.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(t,n,a){var i=t.$eval(a.tabContentTransclude);i.$transcludeFn(i.$parent,function(t){angular.forEach(t,function(t){e(t)?i.headingElement=t:n.append(t)})})}}}]).directive("tabsetTitles",function(){return{restrict:"A",require:"^tabset",templateUrl:"template/tabs/tabset-titles.html",replace:!0,link:function(e,t,n,a){e.$eval(n.tabsetTitles)?a.$transcludeFn(a.$scope.$parent,function(e){t.append(e)}):t.remove()}}}),angular.module("ui.bootstrap.timepicker",[]).filter("pad",function(){return function(e){return angular.isDefined(e)&&2>(""+e).length&&(e="0"+e),e}}).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:["AM","PM"],readonlyInput:!1,mousewheel:!0}).directive("timepicker",["padFilter","$parse","timepickerConfig",function(e,t,n){return{restrict:"EA",require:"ngModel",replace:!0,templateUrl:"template/timepicker/timepicker.html",scope:{model:"=ngModel"},link:function(a,i,o){function r(){var e=parseInt(a.hours,10),t=a.showMeridian?e>0&&13>e:e>=0&&24>e;return t?(a.showMeridian&&(12===e&&(e=0),a.meridian===u[1]&&(e+=12)),e):void 0}function l(){var t=c.getHours();a.showMeridian&&(t=0===t||12===t?12:t%12),a.hours="h"===b?t:e(t),a.validHours=!0;var n=c.getMinutes();a.minutes="m"===b?n:e(n),a.validMinutes=!0,a.meridian=a.showMeridian?12>c.getHours()?u[0]:u[1]:"",b=!1}function s(e){var t=new Date(c.getTime()+6e4*e);c.setHours(t.getHours()),c.setMinutes(t.getMinutes()),a.model=new Date(c)}var c=new Date,u=n.meridians,p=n.hourStep;o.hourStep&&a.$parent.$watch(t(o.hourStep),function(e){p=parseInt(e,10)});var d=n.minuteStep;o.minuteStep&&a.$parent.$watch(t(o.minuteStep),function(e){d=parseInt(e,10)}),a.showMeridian=n.showMeridian,o.showMeridian&&a.$parent.$watch(t(o.showMeridian),function(e){if(a.showMeridian=!!e,a.model)l();else{var t=new Date(c),n=r();angular.isDefined(n)&&t.setHours(n),a.model=new Date(t)}});var m=i.find("input"),g=m.eq(0),f=m.eq(1),h=angular.isDefined(o.mousewheel)?a.$eval(o.mousewheel):n.mousewheel;if(h){var v=function(e){e.originalEvent&&(e=e.originalEvent);var t=e.wheelDelta?e.wheelDelta:-e.deltaY;return e.detail||t>0};g.bind("mousewheel wheel",function(e){a.$apply(v(e)?a.incrementHours():a.decrementHours()),e.preventDefault()}),f.bind("mousewheel wheel",function(e){a.$apply(v(e)?a.incrementMinutes():a.decrementMinutes()),e.preventDefault()})}var b=!1;a.readonlyInput=angular.isDefined(o.readonlyInput)?a.$eval(o.readonlyInput):n.readonlyInput,a.readonlyInput?(a.updateHours=angular.noop,a.updateMinutes=angular.noop):(a.updateHours=function(){var e=r();angular.isDefined(e)?(b="h",null===a.model&&(a.model=new Date(c)),a.model.setHours(e)):(a.model=null,a.validHours=!1)},g.bind("blur",function(){a.validHours&&10>a.hours&&a.$apply(function(){a.hours=e(a.hours)})}),a.updateMinutes=function(){var e=parseInt(a.minutes,10);e>=0&&60>e?(b="m",null===a.model&&(a.model=new Date(c)),a.model.setMinutes(e)):(a.model=null,a.validMinutes=!1)},f.bind("blur",function(){a.validMinutes&&10>a.minutes&&a.$apply(function(){a.minutes=e(a.minutes)})})),a.$watch(function(){return+a.model},function(e){!isNaN(e)&&e>0&&(c=new Date(e),l())}),a.incrementHours=function(){s(60*p)},a.decrementHours=function(){s(60*-p)},a.incrementMinutes=function(){s(d)},a.decrementMinutes=function(){s(-d)},a.toggleMeridian=function(){s(720*(12>c.getHours()?1:-1))}}}}]),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position"]).factory("typeaheadParser",["$parse",function(e){var t=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(n){var a=n.match(t);if(!a)throw Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+n+"'.");return{itemName:a[3],source:e(a[4]),viewMapper:e(a[2]||a[1]),modelMapper:e(a[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(e,t,n,a,i,o,r){var l=[9,13,27,38,40];return{require:"ngModel",link:function(s,c,u,p){var d=s.$eval(u.typeaheadMinLength)||1,m=s.$eval(u.typeaheadWaitMs)||0,g=s.$eval(u.typeaheadEditable)!==!1,f=t(u.typeaheadLoading).assign||angular.noop,h=t(u.typeaheadOnSelect),v=u.typeaheadInputFormatter?t(u.typeaheadInputFormatter):void 0,b=t(u.ngModel).assign,$=r.parse(u.typeahead),y=angular.element("");y.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(u.typeaheadTemplateUrl)&&y.attr("template-url",u.typeaheadTemplateUrl);var w=s.$new();s.$on("$destroy",function(){w.$destroy()});var k=function(){w.matches=[],w.activeIdx=-1},D=function(e){var t={$viewValue:e};f(s,!0),n.when($.source(w,t)).then(function(n){if(e===p.$viewValue){if(n.length>0){w.activeIdx=0,w.matches.length=0;for(var a=0;n.length>a;a++)t[$.itemName]=n[a],w.matches.push({label:$.viewMapper(w,t),model:n[a]});w.query=e,w.position=o.position(c),w.position.top=w.position.top+c.prop("offsetHeight")}else k();f(s,!1)}},function(){k(),f(s,!1)})};k(),w.query=void 0;var x;p.$parsers.push(function(e){return k(),e&&e.length>=d&&(m>0?(x&&a.cancel(x),x=a(function(){D(e)},m)):D(e)),g?e:void 0}),p.$formatters.push(function(e){var t,n,a={};return v?(a.$model=e,v(s,a)):(a[$.itemName]=e,t=$.viewMapper(s,a),n=$.viewMapper(s,{}),t!==n?t:e)}),w.select=function(e){var t,n,a={};a[$.itemName]=n=w.matches[e].model,t=$.modelMapper(s,a),b(s,t),h(s,{$item:n,$model:t,$label:$.viewMapper(s,a)}),k(),c[0].focus()},c.bind("keydown",function(e){0!==w.matches.length&&-1!==l.indexOf(e.which)&&(e.preventDefault(),40===e.which?(w.activeIdx=(w.activeIdx+1)%w.matches.length,w.$digest()):38===e.which?(w.activeIdx=(w.activeIdx?w.activeIdx:w.matches.length)-1,w.$digest()):13===e.which||9===e.which?w.$apply(function(){w.select(w.activeIdx)}):27===e.which&&(e.stopPropagation(),k(),w.$digest()))}),i.bind("click",function(){k(),w.$digest()}),c.after(e(y)(w))}}}]).directive("typeaheadPopup",function(){return{restrict:"E",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(e,t,n){e.templateUrl=n.templateUrl,e.isOpen=function(){return e.matches.length>0},e.isActive=function(t){return e.active==t},e.selectActive=function(t){e.active=t},e.selectMatch=function(t){e.select({activeIdx:t})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(e,t,n,a){return{restrict:"E",scope:{index:"=",match:"=",query:"="},link:function(i,o,r){var l=a(r.templateUrl)(i.$parent)||"template/typeahead/typeahead-match.html";e.get(l,{cache:t}).success(function(e){o.replaceWith(n(e.trim())(i))})}}}]).filter("typeaheadHighlight",function(){function e(e){return e.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(t,n){return n?t.replace(RegExp(e(n),"gi"),"$&"):n}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(e){e.put("template/accordion/accordion-group.html",'
\n \n
\n
\n
')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(e){e.put("template/accordion/accordion.html",'
')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(e){e.put("template/alert/alert.html","
\n \n
\n
\n")}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(e){e.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(e){e.put("template/carousel/slide.html","
\n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(e){e.put("template/datepicker/datepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
#{{label}}
{{ getWeekNumber(row) }}\n \n
\n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(e){e.put("template/datepicker/popup.html",'')}]),angular.module("template/dialog/message.html",[]).run(["$templateCache",function(e){e.put("template/dialog/message.html",'\n\n\n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(e){e.put("template/modal/backdrop.html",'')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(e){e.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(e){e.put("template/pagination/pager.html",'
\n \n
\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(e){e.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(e){e.put("template/tooltip/tooltip-html-unsafe-popup.html",'
\n
\n
\n
\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(e){e.put("template/tooltip/tooltip-popup.html",'
\n
\n
\n
\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(e){e.put("template/popover/popover.html",'
\n
\n\n
\n

\n
\n
\n
\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(e){e.put("template/progressbar/bar.html",'
')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(e){e.put("template/progressbar/progress.html",'
')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(e){e.put("template/rating/rating.html",'\n \n')}]),angular.module("template/tabs/pane.html",[]).run(["$templateCache",function(e){e.put("template/tabs/pane.html",'
\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(e){e.put("template/tabs/tab.html",'
  • \n {{heading}}\n
  • \n')}]),angular.module("template/tabs/tabs.html",[]).run(["$templateCache",function(e){e.put("template/tabs/tabs.html",'
    \n \n
    \n
    \n')}]),angular.module("template/tabs/tabset-titles.html",[]).run(["$templateCache",function(e){e.put("template/tabs/tabset-titles.html","
      \n
    \n")}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(e){e.put("template/tabs/tabset.html",'\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(e){e.put("template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
     
    :
     
    ')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(e){e.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(e){e.put("template/typeahead/typeahead-popup.html",'')}]),angular.module("template/typeahead/typeahead.html",[]).run(["$templateCache",function(e){e.put("template/typeahead/typeahead.html",'')}]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rimekit", 3 | "description": "Rime Kit", 4 | "version": "0.1.0", 5 | "homepage": "https://github.com/lotem/rimekit", 6 | "author": { 7 | "name": "lotem", 8 | "email": "chen.sst@gmail.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/lotem/rimekit.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/lotem/rimekit/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/lotem/rimekit/blob/master/LICENSE" 21 | } 22 | ], 23 | "main": "index.html", 24 | "engines": { 25 | "node": ">= 0.8.0" 26 | }, 27 | "devDependencies": { 28 | "grunt": "~0.4.1", 29 | "grunt-contrib-watch": "~0.2.0", 30 | "grunt-contrib-coffee": "~0.7.0", 31 | "grunt-contrib-nodeunit": "~0.2.2" 32 | }, 33 | "keywords": [] 34 | } 35 | -------------------------------------------------------------------------------- /rime/algebra.coffee: -------------------------------------------------------------------------------- 1 | exports.Spelling = class Spelling 2 | constructor: (props) -> 3 | @[key] = value for key, value of props 4 | @text ?= '' 5 | @type ?= 'normal' 6 | @syllables ?= [@text] 7 | toString: -> @text 8 | 9 | # add support for Perl regexp \U, \L, \E 10 | enhancedRegexpReplace = (str, left, right) -> 11 | str = str.replace left, right 12 | caseChangeExpr = /\\([UL]).*?(\\E|$)/ 13 | if caseChangeExpr.test right 14 | while caseChangeExpr.test str 15 | str = str.replace caseChangeExpr, (text) -> 16 | if text.slice(0, 2) == '\\U' 17 | caseChange = String::toUpperCase 18 | else if text.slice(0, 2) == '\\L' 19 | caseChange = String::toLowerCase 20 | else 21 | return text # what? 22 | end = if text.slice(-2) == '\\E' then -2 else text.length 23 | caseChange.apply text.slice 2, end 24 | str 25 | 26 | exports.Calculation = class Calculation 27 | @parse: (formula) -> 28 | prefix = null 29 | a = formula.split '' 30 | sep = formula.search /[^a-z]/ 31 | if sep == -1 32 | console.error "invalid calculation: missing separator" 33 | return null 34 | separator = formula.charAt(sep) 35 | operands = formula.split(separator) 36 | if formula.charAt(formula.length - 1) == separator 37 | operands.pop() # trailing separator 38 | operator = operands.shift() 39 | unless operator of @factories 40 | console.error "unknown calculation: #{operator}" 41 | return null 42 | @factories[operator].parse operands 43 | 44 | class Transliteration extends Calculation 45 | @parse: (args) -> 46 | return null if args.length != 2 47 | x = new Transliteration 48 | [x.left, x.right] = args[0..1] 49 | if x.left.length != x.right.length 50 | console.error "error parsing transliteration" 51 | return null 52 | return x 53 | calculate: (spelling) -> 54 | chars = spelling.text.split '' 55 | xlit_chars = ((if (index = @left.indexOf ch) == -1 then ch \ 56 | else @right.charAt(index)) for ch in chars) 57 | result = xlit_chars.join '' 58 | if result == spelling.text 59 | [spelling] 60 | else [ 61 | new Spelling 62 | text: result 63 | type: spelling.type 64 | syllables: spelling.syllables.slice 0 65 | ancestor: spelling 66 | modifier: @ 67 | ] 68 | 69 | class Transformation extends Calculation 70 | @parse: (args, Klass = Transformation) -> 71 | return null if args.length != 2 72 | x = new Klass 73 | try 74 | x.left = new RegExp args[0], 'g' 75 | x.right = args[1] 76 | catch error 77 | console.error "error parsing transformation: #{error}" 78 | return null 79 | return x 80 | calculate: (spelling) -> 81 | result = enhancedRegexpReplace spelling.text, @left, @right 82 | if result == spelling.text 83 | [spelling] 84 | else [ 85 | new Spelling 86 | text: result 87 | type: spelling.type 88 | syllables: spelling.syllables.slice 0 89 | ancestor: spelling 90 | modifier: @ 91 | ] 92 | 93 | class Erasion extends Calculation 94 | @parse: (args) -> 95 | return null if args.length != 1 96 | x = new Erasion 97 | try 98 | x.pattern = new RegExp args[0], 'g' 99 | catch error 100 | console.error "error parsing erasion: #{error}" 101 | return null 102 | return x 103 | calculate: (spelling) -> 104 | if @pattern.test spelling.text 105 | [] 106 | else 107 | [spelling] 108 | 109 | class Derivation extends Transformation 110 | @parse: (args) -> super args, Derivation 111 | calculate: (spelling) -> 112 | result = super spelling 113 | if result[0] != spelling 114 | result[0].type = 'derived' 115 | result[0].modifier = @ 116 | result.push spelling 117 | result 118 | 119 | class Fuzzing extends Derivation 120 | @parse: (args) -> super args, Fuzzing 121 | calculate: (spelling) -> 122 | result = super spelling 123 | if result[0] != spelling 124 | result[0].type = 'fuzzy' 125 | result[0].modifier = @ 126 | result 127 | 128 | class Abbreviation extends Derivation 129 | @parse: (args) -> super args, Abbreviation 130 | calculate: (spelling) -> 131 | result = super spelling 132 | if result[0] != spelling 133 | result[0].type = 'abbrev' 134 | result[0].modifier = @ 135 | result 136 | 137 | Calculation.factories = 138 | xlit: Transliteration 139 | xform: Transformation 140 | erase: Erasion 141 | derive: Derivation 142 | fuzz: Fuzzing 143 | abbrev: Abbreviation 144 | 145 | exports.Rule = class Rule 146 | constructor: (@formula) -> 147 | unless @formula 148 | @formula = '<無>' 149 | else 150 | @calc = Calculation.parse(@formula) 151 | @error = !@calc 152 | calculate: (spelling) -> 153 | if @error 154 | [spelling] 155 | else 156 | @calc.calculate spelling 157 | 158 | exports.Script = class Script 159 | @fromSyllabary: (syllabary) -> 160 | script = new Script 161 | for x in syllabary 162 | script.mapping[x] = new Spelling 163 | text: x 164 | script 165 | 166 | constructor: (@mapping = {}) -> 167 | 168 | toString: -> 169 | (k for k, v of @mapping).join ' ' 170 | 171 | getSpellings: -> 172 | (v for k, v of @mapping) 173 | 174 | query: (pattern) -> 175 | s = new Script 176 | for k, v of @mapping 177 | s.mapping[k] = v if pattern.test k 178 | s 179 | 180 | queryPrevious: (previous) -> 181 | s = new Script 182 | nodes = previous.getSpellings() 183 | return s if nodes.length == 0 184 | for k, v of @mapping 185 | if v in nodes 186 | s.mapping[k] = v 187 | else if v.ancestor? and v.ancestor in nodes 188 | s.mapping[v.ancestor.text] = v.ancestor 189 | else if v.threads? 190 | for w in v.threads 191 | if w in nodes 192 | s.mapping[w.text] = w 193 | else if w.ancestor? and w.ancestor in nodes 194 | s.mapping[w.ancestor.text] = w.ancestor 195 | @previous = s 196 | s 197 | 198 | queryNext: (next) -> 199 | s = new Script 200 | nodes = @getSpellings() 201 | return s if nodes.length == 0 202 | for k, v of next.mapping 203 | if v in nodes or v.ancestor? and v.ancestor in nodes 204 | s.mapping[k] = v 205 | else if v.threads? 206 | for w in v.threads 207 | if w in nodes or w.ancestor? and w.ancestor in nodes 208 | s.mapping[k] = v 209 | break 210 | s.previous = @ 211 | s 212 | 213 | exports.Algebra = class Algebra 214 | constructor: (@rules) -> 215 | 216 | formatString: (str) -> 217 | spelling = new Spelling 218 | text: str 219 | for r in @rules 220 | a = r.calculate spelling 221 | return '' if a.length == 0 222 | next = a[0] 223 | if next is spelling 224 | next = new Spelling spelling # copy 225 | next.previous = spelling 226 | spelling = r.spelling = next 227 | spelling.text 228 | 229 | makeProjection: (script) -> 230 | for r in @rules 231 | next = new Script {} 232 | for k, w of script.mapping 233 | a = r.calculate w 234 | for x in a 235 | unless x.text of next.mapping 236 | next.mapping[x.text] = x 237 | continue 238 | y = next.mapping[x.text] 239 | unless y.type is 'merged' 240 | y = next.mapping[x.text] = new Spelling 241 | text: x.text 242 | type: 'merged' 243 | syllables: y.syllables.slice 0 244 | threads: [y] 245 | modifier: r.calc 246 | for s in x.syllables 247 | if not s in y.syllables 248 | y.syllables.push s 249 | y.threads.push x 250 | next.previous = script 251 | script = r.script = next 252 | #console.debug r.formula, script.mapping 253 | script 254 | -------------------------------------------------------------------------------- /rime/config.coffee: -------------------------------------------------------------------------------- 1 | jsyaml = require 'js-yaml' 2 | 3 | exports.Config = class Config 4 | constructor: (yaml) -> 5 | @root = if yaml then jsyaml.safeLoad yaml else null 6 | 7 | loadFileSync: (filePath) -> 8 | data = fs.readFileSync filePath, {encoding: 'utf8'} 9 | # remove potential BOM character 10 | yaml = data.replace /^\ufeff/, '' 11 | @root = jsyaml.safeLoad yaml, filename: filePath 12 | return @ 13 | 14 | loadFile: (filePath) -> 15 | new Promise (resolve, reject) => 16 | fs.readFile filePath, {encoding: 'utf8'}, (err, data) => 17 | if err 18 | console.error "error loading config: #{err}" 19 | reject err 20 | return 21 | # remove potential BOM character 22 | yaml = data.replace /^\ufeff/, '' 23 | try 24 | @root = jsyaml.safeLoad yaml, filename: filePath 25 | catch err 26 | console.error "error loading config: #{err}" 27 | reject err 28 | return 29 | resolve() 30 | 31 | saveFile: (filePath) -> 32 | new Promise (resolve, reject) => 33 | fs.writeFile filePath, @toString(), (err) -> 34 | if err 35 | reject err 36 | else 37 | resolve() 38 | 39 | toString: -> 40 | jsyaml.safeDump @root, flowLevel: 3 41 | 42 | get: (key) -> 43 | ks = (x for x in key.split '/' when x) 44 | if ks.length == 0 # '', '/' 45 | return @root 46 | node = @root 47 | for k in ks 48 | break unless node? 49 | node = node[k] 50 | node 51 | 52 | set: (key, value) -> 53 | ks = (x for x in key.split '/' when x) 54 | if ks.length == 0 # '', '/' 55 | return @root = value 56 | @root ?= {} 57 | node = @root 58 | kend = ks.pop() 59 | for k in ks 60 | node[k] = {} unless node[k]? 61 | node = node[k] 62 | node[kend] = value 63 | -------------------------------------------------------------------------------- /rime/customizer.coffee: -------------------------------------------------------------------------------- 1 | exports.Customizer = class Customizer extends Config 2 | 3 | constructor: (yaml) -> 4 | super(yaml) 5 | @root ?= {} 6 | @root.patch ?= {} 7 | 8 | patch: (key, value) -> 9 | @root.patch[key] = value 10 | 11 | applyPatch: (config) -> 12 | for key, value of @root.patch 13 | config.set key, value 14 | -------------------------------------------------------------------------------- /rime/promise.coffee: -------------------------------------------------------------------------------- 1 | unless Promise? 2 | exports.Promise = Promise = require('bluebird') 3 | -------------------------------------------------------------------------------- /rime/recipe.coffee: -------------------------------------------------------------------------------- 1 | crypto = require 'crypto' 2 | fs = require 'fs' 3 | path = require 'path' 4 | request = require 'request' 5 | url = require 'url' 6 | 7 | exports.RecipeList = class RecipeList 8 | 9 | constructor: (@fileName = 'recipes.yaml')-> 10 | @loaded = false 11 | @loading = false 12 | @list = [] 13 | @pending = [] 14 | @autoSave = true 15 | 16 | schedule: (work) -> 17 | @pending.push work 18 | if @loading 19 | return 20 | doPendingWork = => 21 | while @pending.length != 0 22 | @pending.shift()() 23 | @save() if @autoSave 24 | if @loaded 25 | doPendingWork() 26 | else 27 | @load (err) => 28 | throw err if err 29 | doPendingWork() 30 | 31 | load: -> 32 | new Promise (resolve, reject) => 33 | filePath = "#{Recipe.rimeUserDir}/#{@fileName}" 34 | c = new Config 35 | ( 36 | if fs.existsSync filePath 37 | @loading = true 38 | c.loadFile(filePath) 39 | else 40 | Promise.resolve() 41 | ) 42 | .then => 43 | @loading = false 44 | @loaded = true 45 | @list = c.get('recipes') or [] 46 | resolve() 47 | .catch (err) => 48 | @loading = false 49 | reject err 50 | 51 | save: -> 52 | filePath = "#{Recipe.rimeUserDir}/#{@fileName}" 53 | c = new Config 54 | c.set 'recipes', @list 55 | c.saveFile filePath 56 | 57 | clear: -> 58 | @schedule => 59 | @list = [] 60 | 61 | add: (recipe) -> 62 | @schedule => 63 | @list.push 64 | name: recipe.props.name 65 | version: recipe.props.version 66 | params: recipe.params 67 | 68 | exports.recipes = recipes = new RecipeList 69 | 70 | # recipe: Recipe or {...} 71 | # ingredients: {...} 72 | exports.cook = cook = (recipe, ingredients) -> 73 | try 74 | unless recipe instanceof Recipe # cook {...}, ... 75 | recipe = new Recipe recipe 76 | if recipe.props.params 77 | recipe.collectParams(ingredients ? {}) 78 | catch e 79 | console.error "error parsing recipe: #{e}" 80 | return Promise.reject e 81 | recipe.downloadFiles() 82 | .then -> 83 | if recipe.props.setup 84 | recipe.props.setup.call recipe 85 | .then -> 86 | # save recipe list 87 | recipes.add recipe 88 | .catch (e) -> 89 | console.error "error cooking recipe: #{e}" 90 | return Promise.reject e 91 | 92 | exports.Recipe = class Recipe 93 | 94 | @rimeUserDir: '.' 95 | 96 | @rimeSharedDir: '.' 97 | 98 | constructor: (@props) -> 99 | @validate() 100 | 101 | validate: -> 102 | # name and version are required 103 | throw Error 'missing recipe name.' unless @props.name 104 | throw Error 'missing recipe version.' unless @props.version 105 | unless /^[_0-9A-Za-z]+$/.test @props.name 106 | throw Error 'recipe name should be alpha_numeric.' 107 | unless typeof @props.version is 'string' 108 | throw Error 'recipe version should be string type.' 109 | @rimeUserDir = @props.rimeUserDir ? Recipe.rimeUserDir 110 | unless @rimeUserDir and fs.existsSync @rimeUserDir 111 | throw Error 'Rime user directory not accessible.' 112 | @rimeSharedDir = @props.rimeSharedDir ? Recipe.rimeSharedDir 113 | 114 | collectParams: (ingredients) -> 115 | @params ?= {} 116 | for param in @props.params 117 | unless param and typeof param is 'object' 118 | throw Error('invalid parameter definition.') 119 | name = param.name 120 | if param.required and not ingredients[name]? 121 | throw Error("missing ingredient: #{name}") 122 | @params[name] = ingredients[name] 123 | 124 | downloadFiles: -> 125 | unless @props.files # no files to download 126 | return Promise.resolve() 127 | download = "#{@rimeUserDir}/download" 128 | fs.mkdirSync download unless fs.existsSync download 129 | @downloadDirectory = "#{download}/#{@props.name}" 130 | fs.mkdirSync @downloadDirectory unless fs.existsSync @downloadDirectory 131 | downloadFile = (fileUrl) => 132 | fileName = url.parse(fileUrl).pathname.split('/').pop() 133 | dest = "#{@downloadDirectory}/#{fileName}" 134 | ( 135 | if @props.sha1sum?[fileName] and fs.existsSync dest 136 | @checksum fileName, dest 137 | else 138 | Promise.reject() 139 | ) 140 | .catch => 141 | new Promise (resolve, reject) => 142 | console.log "downloading #{fileName}" 143 | request.get(fileUrl) 144 | .on('error', (e) -> 145 | console.log "failed to download #{fileName}: #{e.message}" 146 | reject e 147 | ) 148 | .on('end', => 149 | console.log "#{fileName} downloaded to #{@downloadDirectory}" 150 | resolve() 151 | ) 152 | .pipe fs.createWriteStream(dest) 153 | .then => 154 | if @props.sha1sum?[fileName] 155 | @checksum fileName, dest 156 | Promise.all(@props.files.map downloadFile) 157 | 158 | checksum: (fileName, filePath) -> 159 | expected = @props.sha1sum?[fileName] 160 | return Promise.reject() unless expected 161 | new Promise (resolve, reject) => 162 | shasum = crypto.createHash 'sha1' 163 | stream = fs.ReadStream filePath 164 | stream.on 'data', (data) -> shasum.update(data) 165 | stream.on 'error', (err) -> reject err 166 | stream.on 'end', -> 167 | if shasum.digest('hex') == expected 168 | console.log "#{fileName}: #{expected}" 169 | resolve() 170 | else 171 | reject new Error "checksum mismatch: #{fileName}" 172 | 173 | copyFile: (src) -> 174 | new Promise (resolve, reject) => 175 | fileName = path.basename src 176 | dest = "#{@rimeUserDir}/#{fileName}" 177 | fs.createReadStream(src) 178 | .on('error', (e) -> 179 | console.log "error copying file: #{e.message}" 180 | reject e 181 | ) 182 | .on('end', => 183 | console.log "#{fileName} copied to #{@rimeUserDir}" 184 | resolve() 185 | ) 186 | .pipe fs.createWriteStream(dest) 187 | 188 | installSchema: (schemaId) -> 189 | unless @downloadDirectory? 190 | return Promise.reject( 191 | new Error "no files to install for schema '#{schemaId}'" 192 | ) 193 | schemaFile = "#{@downloadDirectory}/#{schemaId}.schema.yaml" 194 | files = [schemaFile] 195 | c = new Config 196 | c.loadFile(schemaFile) 197 | .then => 198 | dictId = c.get 'translator/dictionary' 199 | if dictId 200 | dictFile = "#{@downloadDirectory}/#{dictId}.dict.yaml" 201 | if fs.existsSync dictFile 202 | files.push dictFile 203 | Promise.all(files.map (x) => @copyFile x) 204 | 205 | findConfigFile: (fileName) -> 206 | filePath = "#{@rimeUserDir}/#{fileName}" 207 | if fs.existsSync filePath 208 | c = new Config 209 | try 210 | c.loadFileSync filePath 211 | unless typeof c.get('customization') is 'number' 212 | return filePath 213 | catch e 214 | if @rimeSharedDir != @rimeUserDir 215 | filePath = "#{@rimeSharedDir}/#{fileName}" 216 | if fs.existsSync filePath 217 | return filePath 218 | null 219 | 220 | getDefaultSchemaList: -> 221 | c = new Config 222 | c.loadFile(@findConfigFile 'default.yaml') 223 | .then -> 224 | c.get('schema_list') or [] 225 | .catch -> 226 | [] 227 | 228 | # edit: (schemaList) -> 229 | editSchemaList: (edit) -> 230 | @customize 'default', (c) => 231 | ( 232 | list = c.root.patch['schema_list'] 233 | if list? 234 | Promise.resolve(list) 235 | else 236 | @getDefaultSchemaList() 237 | ) 238 | .then (list) -> 239 | edit list 240 | c.patch 'schema_list', list 241 | 242 | enableSchema: (schemaId) -> 243 | @editSchemaList (schemaList) -> 244 | unless schemaId in schemaList 245 | schemaList.push schemaId 246 | 247 | disableSchema: (schemaId) -> 248 | @editSchemaList (schemaList) -> 249 | if ~(index = schemaList.indexOf schemaId) 250 | schemaList.splice index, 1 251 | 252 | # edit: (customizer) -> 253 | customize: (configId, edit) -> 254 | configPath = "#{@rimeUserDir}/#{configId}.custom.yaml" 255 | c = new Customizer 256 | ( 257 | if fs.existsSync configPath 258 | c.loadFile configPath 259 | else 260 | Promise.resolve() 261 | ) 262 | .then -> 263 | edit c 264 | .then -> 265 | c.saveFile configPath 266 | -------------------------------------------------------------------------------- /rime/sandbox.coffee: -------------------------------------------------------------------------------- 1 | vm = require 'vm' 2 | path = require 'path' 3 | 4 | exports.UserScript = class UserScript 5 | constructor (@code) -> 6 | 7 | loadFile: (filePath) -> 8 | new Promise (resolve, reject) => 9 | fs.readFile filePath, {encoding: 'utf8'}, (err, data) => 10 | if err 11 | reject err 12 | else 13 | @code = data 14 | resolve() 15 | 16 | compile: -> 17 | @script = vm.createScript @code 18 | 19 | run: (sandbox = {}) -> 20 | @script.runInNewContext sandbox 21 | 22 | exports.UserCoffeeScript = class UserCoffeeScript extends UserScript 23 | compile: -> 24 | coffee = require 'coffee-script' 25 | @script = vm.createScript(coffee.compile @code) 26 | 27 | exports.runUserScript = (filePath, ingredients) -> 28 | script = new ( 29 | if path.extname(filePath) is '.coffee' then UserCoffeeScript else UserScript 30 | ) 31 | script.loadFile(filePath) 32 | .then -> 33 | try 34 | script.compile() 35 | sandbox = {} 36 | for key, value of exports 37 | sandbox[key] = value 38 | sandbox.ingredients = ingredients 39 | sandbox.cook = (recipe, ingredients) -> 40 | sandbox.result = Promise.resolve().then -> 41 | cook recipe, ingredients 42 | script.run sandbox 43 | sandbox.result 44 | catch e 45 | return Promise.reject e 46 | -------------------------------------------------------------------------------- /rime/table.coffee: -------------------------------------------------------------------------------- 1 | fs = require('fs') 2 | os = require('os') 3 | path = require('path') 4 | marisa = null 5 | 6 | EPSILON = 1e-10 7 | 8 | exports.Table = class Table 9 | loadFile: (filePath) -> 10 | new Promise (resolve, reject) => 11 | fs.readFile filePath, (err, data) => 12 | if err 13 | console.error "error loading table: #{err}" 14 | reject err 15 | else 16 | console.log "read table #{filePath}" 17 | @dictName = path.basename filePath, '.table.bin' 18 | @formatVersion = @getFormatVersion data 19 | if @formatVersion > 2.0 - EPSILON 20 | @stringTable = @loadStringTable data 21 | @syllabary = @getSyllabary data 22 | @stringTable = null 23 | resolve() 24 | 25 | getFormatVersion: (buf) -> 26 | format = @getString buf, 0 27 | match = /^Rime::Table\/(\d+\.\d+)/.exec(format) 28 | parseFloat match?[1] 29 | 30 | loadStringTable: (buf) -> 31 | offset = 60 # metadata.string_table 32 | stringTableOffset = offset + buf.readInt32LE(offset) 33 | offset += 4 34 | stringTableSize = buf.readUInt32LE(offset) 35 | offset += 4 36 | trieData = buf.slice stringTableOffset, stringTableOffset + stringTableSize 37 | marisa ?= require('node-marisa-trie') 38 | trie = marisa.createTrie() 39 | trie.map trieData 40 | trie 41 | 42 | getSyllabary: (buf) -> 43 | getSyllable = switch 44 | when @formatVersion > 2.0 - EPSILON then @getSyllable_v2 45 | else @getSyllable_v1 46 | result = [] 47 | offset = 44 # metadata.syllabary 48 | offset += buf.readInt32LE(offset) # syllabary 49 | syllabarySize = buf.readUInt32LE(offset) 50 | offset += 4 51 | for i in [0...syllabarySize] 52 | result.push getSyllable.call @, buf, offset 53 | offset += 4 54 | result 55 | 56 | getSyllable_v2: (buf, offset) -> 57 | stringId = buf.readUInt32LE(offset) 58 | agent = marisa.createAgent() 59 | agent.set_query stringId 60 | @stringTable.reverse_lookup agent 61 | key = agent.key() 62 | key.ptr().substring(0, key.length()) 63 | 64 | getSyllable_v1: (buf, offset) -> 65 | entryOffset = offset + buf.readInt32LE(offset) 66 | @getString buf, entryOffset 67 | 68 | getString: (buf, offset) -> 69 | end = offset 70 | ++end while buf[end] != 0 # find '\0' 71 | buf.toString('utf8', offset, end) 72 | -------------------------------------------------------------------------------- /sample/custom_phrase.recipe.coffee: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | recipe = new Recipe 4 | name: 'custom_phrase' 5 | version: '1.0' 6 | category: 'settings' 7 | description: '啓用自定義短語' 8 | params: [ 9 | { 10 | name: 'schema' 11 | label: '選取輸入方案' 12 | required: true 13 | } 14 | ] 15 | files: [ 16 | 'https://gist.github.com/lotem/5440677/raw/custom_phrase.txt' 17 | ] 18 | setup: -> 19 | schemaId = @params['schema'] 20 | schemaFile = @findConfigFile "#{schemaId}.schema.yaml" 21 | unless schemaFile 22 | return Promise.reject( 23 | new Error "schema '#{schemaId}' could not be found." 24 | ) 25 | s = new Config 26 | s.loadFile(schemaFile) 27 | .then => 28 | mainTranslators = [ 29 | /^r10n_translator/ 30 | /^reverse_lookup_translator/ 31 | /^script_translator/ 32 | /^table_translator/ 33 | ] 34 | customPhraseTranslator = 'table_translator@custom_phrase' 35 | translators = s.get 'engine/translators' 36 | unless customPhraseTranslator in translators 37 | # insert custom phrase translator before main translators 38 | index = 0 39 | for x in translators 40 | if mainTranslators.some((elem) -> elem.test x) 41 | break 42 | ++index 43 | translators.splice index, 0, customPhraseTranslator 44 | @customize schemaId, (c) -> 45 | c.patch 'engine/translators', translators 46 | c.patch 'custom_phrase', 47 | dictionary: '' 48 | user_dict: 'custom_phrase' 49 | db_class: 'stabledb' 50 | enable_completion: false 51 | enable_sentence: false 52 | initial_quality: 1 53 | .then => 54 | @copyFile "#{@downloadDirectory}/custom_phrase.txt" 55 | 56 | cook recipe, ingredients 57 | -------------------------------------------------------------------------------- /sample/dungfungpuo.recipe.coffee: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | cook new Recipe 4 | name: 'dungfungpuo' 5 | version: '0.2' 6 | category: 'schema' 7 | description: '東風破古韻輸入法' 8 | files: [ 9 | 'https://raw.github.com/lotem/rime-forge/master/dungfungpuo/dungfungpuo.schema.yaml' 10 | 'https://raw.github.com/lotem/rime-forge/master/dungfungpuo/dungfungpuo.dict.yaml' 11 | ] 12 | setup: -> 13 | @installSchema('dungfungpuo') 14 | .then => 15 | @enableSchema('dungfungpuo') 16 | sha1sum: 17 | "dungfungpuo.dict.yaml": "7615c6e1402ef122bddc7c65205954cb1e653450" 18 | "dungfungpuo.schema.yaml": "b0f9c5f45ff05158c94c61a736fc26c142470e22" 19 | -------------------------------------------------------------------------------- /sample/horizontal_layout.recipe.coffee: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | recipe = new Recipe 4 | name: 'horizontal_layout' 5 | version: '1.0' 6 | category: 'settings' 7 | description: '候選窗横排' 8 | params: [ 9 | { 10 | name: 'distro' 11 | options: 12 | 'weasel': '小狼毫' 13 | 'squirrel': '鼠鬚管' 14 | required: true 15 | } 16 | ] 17 | setup: -> 18 | @customize @params['distro'], (c) -> 19 | c.patch 'style/horizontal', true 20 | 21 | cook recipe, ingredients 22 | -------------------------------------------------------------------------------- /sample/run.coffee: -------------------------------------------------------------------------------- 1 | rime = require '../app.nw/rime' 2 | path = require 'path' 3 | 4 | argv = process.argv 5 | 6 | if argv.length < 3 7 | console.log "usage: #{argv[0]} #{path.basename argv[1]} recipe [key=value ...]" 8 | process.exit 1 9 | 10 | recipeScript = argv[2] 11 | console.log "recipe: #{recipeScript}" 12 | 13 | ingredients = {} 14 | for x in argv.slice(3) 15 | [k, v] = x.split '=' 16 | unless k and v 17 | console.error "invalid ingredient: #{x}" 18 | ingredients[k] = v 19 | console.log "ingredients: #{JSON.stringify ingredients}" 20 | 21 | rime.runUserScript(recipeScript, ingredients) 22 | .then -> console.log 'done.' 23 | .catch console.error 24 | -------------------------------------------------------------------------------- /sample/slash_symbols.recipe.coffee: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | recipe = new Recipe 4 | name: 'slash_symbols' 5 | version: '1.0' 6 | category: 'settings' 7 | description: '啓用特殊符號(/fh)' 8 | params: [ 9 | { 10 | name: 'schema' 11 | label: '選取輸入方案' 12 | default: 'luna_pinyin' 13 | required: true 14 | } 15 | ] 16 | setup: -> 17 | @customize @params['schema'], (c) -> 18 | c.patch 'punctuator/import_preset', 'symbols' 19 | c.patch 'recognizer/patterns/punct', '^/[a-z]*$' 20 | 21 | cook recipe, ingredients 22 | -------------------------------------------------------------------------------- /test/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lotem/rimekit/984afe041cb2f908b7a9494c17ef3c426312d21a/test/.placeholder -------------------------------------------------------------------------------- /test/test-customizer.coffee: -------------------------------------------------------------------------------- 1 | rime = require '../app.nw/rime' 2 | 3 | exports.testCustomizer = (test) -> 4 | c = new rime.Config """ 5 | abc: 6 | abcd: 1234 7 | def: 8 | defg: 5678 9 | """ 10 | test.equal c.get('abc/abcd'), 1234 11 | test.equal c.get('def/defg'), 5678 12 | x = new rime.Customizer 13 | x.patch 'abc/abcd', 4321 14 | x.patch 'def', null 15 | x.patch 'opq', true 16 | x.patch 'xyz', {uvw: 'rst'} 17 | test.deepEqual x.root, 18 | patch: 19 | 'abc/abcd': 4321 20 | 'def': null 21 | 'opq': true 22 | 'xyz': {uvw: 'rst'} 23 | x.applyPatch c 24 | test.equal c.get('abc/abcd'), 4321 25 | test.equal c.get('def'), null 26 | test.equal c.get('def/defg'), null 27 | test.equal c.get('opq'), true 28 | test.equal c.get('xyz/uvw'), 'rst' 29 | test.done() 30 | -------------------------------------------------------------------------------- /test/test-recipe.coffee: -------------------------------------------------------------------------------- 1 | rime = require '../app.nw/rime' 2 | fs = require 'fs' 3 | 4 | exports.testRecipeValidation = (test) -> 5 | test.throws (-> new rime.Recipe), Error, 'Should fail without required params.' 6 | test.throws (-> new rime.Recipe 7 | name: 'an-invalid-name' 8 | version: '1.0' 9 | ), Error, 'Should fail for recipe name.' 10 | test.throws (-> new rime.Recipe 11 | name: 'a_name' 12 | version: 1.0 13 | ), Error, 'Should fail for recipe version.' 14 | test.throws (-> new rime.Recipe 15 | name: 'a_name' 16 | version: '1.0' 17 | rimeUserDir: './nonexistent/path/3.1415926' 18 | ), Error, 'Should fail when missing rimeUserDir.' 19 | test.doesNotThrow (-> new rime.Recipe 20 | name: 'a_name' 21 | version: '1.0' 22 | rimeUserDir: '.' 23 | ), Error, 'Should pass recipe validation.' 24 | test.done() 25 | 26 | exports.testParametrizedRecipe = (test) -> 27 | rime.recipes.autoSave = false 28 | recipe = new rime.Recipe 29 | name: 'a_name' 30 | version: '1.0' 31 | params: [ 32 | {name: 'required_param', required: true} 33 | ] 34 | test.throws -> recipe.collectParams {} 35 | recipe = new rime.Recipe 36 | name: 'a_name' 37 | version: '1.0' 38 | params: [ 39 | {name: 'required_param', required: true} 40 | ] 41 | test.doesNotThrow -> recipe.collectParams {required_param: 'value'} 42 | test.done() 43 | 44 | exports.recipeCustomize = 45 | 46 | setUp: (callback) -> 47 | @recipe = new rime.Recipe 48 | name: 'test_recipe_customize' 49 | version: '1.0' 50 | rimeUserDir: 'test' 51 | @configPath = "#{@recipe.props.rimeUserDir}/#{@recipe.props.name}.custom.yaml" 52 | if fs.existsSync @configPath 53 | fs.unlinkSync @configPath 54 | callback() 55 | 56 | tearDown: (callback) -> 57 | if fs.existsSync @configPath 58 | fs.unlinkSync @configPath 59 | callback() 60 | 61 | testRecipeCustomize: (test) -> 62 | @recipe.customize @recipe.props.name, (c) -> 63 | test.ok c 64 | c.patch 'foo/bar', 'test' 65 | .then => 66 | c = new rime.Config 67 | c.loadFile(@configPath).then -> 68 | test.equal c.get('patch')['foo/bar'], 'test' 69 | .catch (err) -> 70 | test.ifError err 71 | .then -> 72 | test.done() 73 | 74 | #### 75 | exports.testRecipeDownload = (test) -> 76 | recipe = new rime.Recipe 77 | name: 'sample_recipes' 78 | version: '1.0' 79 | rimeUserDir: 'test' 80 | files: [ 81 | 'https://raw.github.com/lotem/rimekit/develop/sample/horizontal_layout.recipe.coffee' 82 | 'https://raw.github.com/lotem/rimekit/develop/sample/slash_symbols.recipe.coffee' 83 | ] 84 | sha1sum: 85 | "horizontal_layout.recipe.coffee": "956eea0d7d76b6f7fb006268d4c68fba4af23590" 86 | "slash_symbols.recipe.coffee": "1b5a8d788a0ac54983f6eb1493dd6c7f6187ca22" 87 | recipe.downloadFiles() 88 | .catch (err) -> 89 | test.ifError err 90 | .then -> 91 | test.done() 92 | #### 93 | --------------------------------------------------------------------------------