<commit_before>$('#close-upload').on('click', function(){
    $('#fileForm').foundation('reveal', 'close');
});

var files_added = 0; /* Track files added to the upload. */
var files_processed = 0; /* Track files that are processed.*/
var files_uploaded = 0; /* Track successful uploads. */
$( function() {
    'use strict';

    /*
        Initialize the jQuery File Upload widget.

        Default options are defined by the package,
        however users may extend and define their
        own options.
    */

    var formData = $('#fileupload').serializeArray();
    formData.push(
        {name:"redirect",value:""}
    );

    $('#fileupload').fileupload({
        formData: formData,
        url: $(this).attr('action'),
        method:'POST',
        dataType:'text',
        sequentialUploads: true,
        recalculateProgress: false,
        previewMaxWidth:100,
        previewMaxHeight:100,
        maxFileSize: $('#fileupload input[name=max_file_size]').val()
    })

    // Keep a counter of files as they are added.
    .bind('fileuploadadd', function (e, data) {
        files_added += 1;
		if ($('#fileForm').hasClass('open') == false) {
			$('#fileForm').foundation('reveal','open');
		};
    })

    .bind('fileuploaddone', function (e, data) {
        files_uploaded += 1;
    })

    .bind('fileuploadprocessstart', function (e) {
        $('#preloadmsg').hide();
		$('.fileupload-progress').show();
        $('#start-upload').removeClass('disabled');
        $('#cancel-upload').removeClass('disabled');
    })

	.bind('fileuploadprocessdone', function (e, data) {
        if (files_added > 1) {
            $('.file-progress').show();
        }
	})

    .bind('fileuploadsend', function (e, data) {
        Foundation.libs.reveal.settings.close_on_background_click = false;
        $('#close-upload').off().addClass('disabled');
        $.each(data.files, function (index, file) {
            $('#fileForm').foundation({reveal : {close_on_background_click: false,close_on_esc:false}});
        });
    })

	.bind('fileuploadfail', function (e, data) {
		files_added -= 1;
		if (files_added == 0) {
			$('#preloadmsg').show();
			$('.fileupload-progress').hide();
		}
	})

    // Keep a counter of files as they are processed.
    .bind('fileuploadalways', function (e, data) {
        files_processed += 1;

        // If all fiels have been processed, close the form.
        if (files_processed == files_added) {
            files_added = 0;
            files_processed = 0;
            files_uploaded = 0;
            closeForm();
            loadTable();
        }
    })
    .addClass('fileupload-processing');

});
$('#cancel-upload').click(function(){
	$('#fileForm').foundation({reveal : {close_on_background_click: true,close_on_esc:true}});
	$('#preloadmsg').show();
	$('.fileupload-progress').hide();
	files_uploaded = 0;
	files_added = 0;
	files_processed = 0;
    $('#start-upload').addClass('disabled');
    $('#cancel-upload').addClass('disabled');
    $('#close-upload').on('click', function(){
        $('#fileForm').foundation('reveal', 'close');
    }).removeClass('disabled');
});

function closeForm() {
    $('#fileForm').foundation({reveal : {close_on_background_click: true,close_on_esc:true}});

    setTimeout(function(){
        $('#fileForm').foundation('reveal', 'close');
        setTimeout(function(){
            $('#preloadmsg').show();
            $('.fileupload-progress').hide();
            $('#start-upload').addClass('disabled');
            $('#cancel-upload').addClass('disabled');
            $('#close-upload').on('click', function(){
                $('#fileForm').foundation('reveal', 'close');
            }).removeClass('disabled');
        }, 500);
    }, 250);

    if(files_uploaded > 0) {
        $('#messages').html(
            '
' +
                '
' +
                    '
' +
                        'Success. ' + files_uploaded +
                        ' files were added. ×' +
                    '
' +
                '
' +
            '
'
        );
    }
	files_uploaded = 0;
	files_added = 0;
	files_processed = 0;
}

/*
    Modify jquery fileupload's onAdd method. This method recreates
    the file object pointer and changes the name. The name change allows
    users to drag and drop directories and those directories are
    recreated on swift.

*/
$.blueimp.fileupload.prototype._onAdd = function (e, data) {
    var that = this,
        result = true,
        options = $.extend({}, this.options, data),
        files = data.files,
        filesLength = files.length,
        limit = options.limitMultiFileUploads,
        limitSize = options.limitMultiFileUploadSize,
        overhead = options.limitMultiFileUploadSizeOverhead,
        batchSize = 0,
        paramName = this._getParamName(options),
        paramNameSet,
        paramNameSlice,
        fileSet,
        i,
        j = 0;
    if (limitSize && (!filesLength || files[0].size === undefined)) {
        limitSize = undefined;
    }
    if (!(options.singleFileUploads || limit || limitSize) ||
            !this._isXHRUpload(options)) {
        fileSet = [files];
        paramNameSet = [paramName];
    } else if (!(options.singleFileUploads || limitSize) && limit) {
        fileSet = [];
        paramNameSet = [];
        for (i = 0; i < filesLength; i += limit) {
            fileSet.push(files.slice(i, i + limit));
            paramNameSlice = paramName.slice(i, i + limit);
            if (!paramNameSlice.length) {
                paramNameSlice = paramName;
            }
            paramNameSet.push(paramNameSlice);
        }
    } else if (!options.singleFileUploads && limitSize) {
        fileSet = [];
        paramNameSet = [];
        for (i = 0; i < filesLength; i = i + 1) {
            batchSize += files[i].size + overhead;
            if (i + 1 === filesLength ||
                    ((batchSize + files[i + 1].size + overhead) > limitSize) ||
                    (limit && i + 1 - j >= limit)) {
                fileSet.push(files.slice(j, i + 1));
                paramNameSlice = paramName.slice(j, i + 1);
                if (!paramNameSlice.length) {
                    paramNameSlice = paramName;
                }
                paramNameSet.push(paramNameSlice);
                j = i + 1;
                batchSize = 0;
            }
        }
    } else {
        paramNameSet = paramName;
    }
    data.originalFiles = files;
    $.each(fileSet || files, function (index, element) {

        /****************** Swift browser edits here ******************/
        /*
            A new File object is created using "element". This new file
            has a different ".name". This new name allows swift to
            create pseudo folders. The end result is the file structure
            that is dragged and dropped in swiftbrowser is preserved on
            swift.
        */

        if (element.relativePath) {
            var element = new File([element], element.relativePath + element.name, {type: "text/plain"});

        } else if (element.webkitRelativePath){
            console.log(typeof element.webkitRelativePath);
            var element = new File([element], element.webkitRelativePath + element.name, {type: "text/plain"});

        }
        var newData = $.extend({}, data);
        newData.files = fileSet ? element : [element];
        newData.paramName = paramNameSet[index];
        that._initResponseObject(newData);
        that._initProgressObject(newData);
        that._addConvenienceMethods(e, newData);
        result = that._trigger(
            'add',
            $.Event('add', {delegatedEvent: e}),
            newData
        );
        return result;
    });
    return result;
}
<commit_msg>Reset files_uploaded counter.<commit_after>$('#close-upload').on('click', function(){
    $('#fileForm').foundation('reveal', 'close');
});

var files_added = 0; /* Track files added to the upload. */
var files_processed = 0; /* Track files that are processed.*/
var files_uploaded = 0; /* Track successful uploads. */
$( function() {
    'use strict';

    /*
        Initialize the jQuery File Upload widget.

        Default options are defined by the package,
        however users may extend and define their
        own options.
    */

    var formData = $('#fileupload').serializeArray();
    formData.push(
        {name:"redirect",value:""}
    );

    $('#fileupload').fileupload({
        formData: formData,
        url: $(this).attr('action'),
        method:'POST',
        dataType:'text',
        sequentialUploads: true,
        recalculateProgress: false,
        previewMaxWidth:100,
        previewMaxHeight:100,
        maxFileSize: $('#fileupload input[name=max_file_size]').val()
    })

    // Keep a counter of files as they are added.
    .bind('fileuploadadd', function (e, data) {
        files_added += 1;
		if ($('#fileForm').hasClass('open') == false) {
			$('#fileForm').foundation('reveal','open');
		};
    })

    .bind('fileuploaddone', function (e, data) {
        files_uploaded += 1;
    })

    .bind('fileuploadprocessstart', function (e) {
        $('#preloadmsg').hide();
		$('.fileupload-progress').show();
        $('#start-upload').removeClass('disabled');
        $('#cancel-upload').removeClass('disabled');
    })

	.bind('fileuploadprocessdone', function (e, data) {
        if (files_added > 1) {
            $('.file-progress').show();
        }
	})

    .bind('fileuploadsend', function (e, data) {
        Foundation.libs.reveal.settings.close_on_background_click = false;
        $('#close-upload').off().addClass('disabled');
        $.each(data.files, function (index, file) {
            $('#fileForm').foundation({reveal : {close_on_background_click: false,close_on_esc:false}});
        });
    })

    // Keep a counter of files as they are processed.
    .bind('fileuploadalways', function (e, data) {
        files_processed += 1;

        // If all fiels have been processed, close the form.
        if (files_processed == files_added) {
            closeForm();
            loadTable();
            files_added = 0;
            files_processed = 0;
            files_uploaded = 0;

        }
    })
    .addClass('fileupload-processing');

});
$('#cancel-upload').click(function(){
	$('#fileForm').foundation({reveal : {close_on_background_click: true,close_on_esc:true}});
	$('#preloadmsg').show();
	$('.fileupload-progress').hide();
	files_uploaded = 0;
	files_added = 0;
	files_processed = 0;
    $('#start-upload').addClass('disabled');
    $('#cancel-upload').addClass('disabled');
    $('#close-upload').on('click', function(){
        $('#fileForm').foundation('reveal', 'close');
    }).removeClass('disabled');
});

function closeForm() {
    $('#fileForm').foundation({reveal : {close_on_background_click: true,close_on_esc:true}});

    setTimeout(function(){
        $('#fileForm').foundation('reveal', 'close');
        setTimeout(function(){
            $('#preloadmsg').show();
            $('.fileupload-progress').hide();
            $('#start-upload').addClass('disabled');
            $('#cancel-upload').addClass('disabled');
            $('#close-upload').on('click', function(){
                $('#fileForm').foundation('reveal', 'close');
            }).removeClass('disabled');
        }, 500);
    }, 250);

    if(files_uploaded > 0) {
        $('#messages').html(
            '
' +
                '
' +
                    '
' +
                        'Success. ' + files_uploaded +
                        ' files were added. ×' +
                    '
' +
                '
' +
            '
'
        );
    }
	files_uploaded = 0;
	files_added = 0;
	files_processed = 0;
}

/*
    Modify jquery fileupload's onAdd method. This method recreates
    the file object pointer and changes the name. The name change allows
    users to drag and drop directories and those directories are
    recreated on swift.

*/
$.blueimp.fileupload.prototype._onAdd = function (e, data) {
    var that = this,
        result = true,
        options = $.extend({}, this.options, data),
        files = data.files,
        filesLength = files.length,
        limit = options.limitMultiFileUploads,
        limitSize = options.limitMultiFileUploadSize,
        overhead = options.limitMultiFileUploadSizeOverhead,
        batchSize = 0,
        paramName = this._getParamName(options),
        paramNameSet,
        paramNameSlice,
        fileSet,
        i,
        j = 0;
    if (limitSize && (!filesLength || files[0].size === undefined)) {
        limitSize = undefined;
    }
    if (!(options.singleFileUploads || limit || limitSize) ||
            !this._isXHRUpload(options)) {
        fileSet = [files];
        paramNameSet = [paramName];
    } else if (!(options.singleFileUploads || limitSize) && limit) {
        fileSet = [];
        paramNameSet = [];
        for (i = 0; i < filesLength; i += limit) {
            fileSet.push(files.slice(i, i + limit));
            paramNameSlice = paramName.slice(i, i + limit);
            if (!paramNameSlice.length) {
                paramNameSlice = paramName;
            }
            paramNameSet.push(paramNameSlice);
        }
    } else if (!options.singleFileUploads && limitSize) {
        fileSet = [];
        paramNameSet = [];
        for (i = 0; i < filesLength; i = i + 1) {
            batchSize += files[i].size + overhead;
            if (i + 1 === filesLength ||
                    ((batchSize + files[i + 1].size + overhead) > limitSize) ||
                    (limit && i + 1 - j >= limit)) {
                fileSet.push(files.slice(j, i + 1));
                paramNameSlice = paramName.slice(j, i + 1);
                if (!paramNameSlice.length) {
                    paramNameSlice = paramName;
                }
                paramNameSet.push(paramNameSlice);
                j = i + 1;
                batchSize = 0;
            }
        }
    } else {
        paramNameSet = paramName;
    }
    data.originalFiles = files;
    $.each(fileSet || files, function (index, element) {

        /****************** Swift browser edits here ******************/
        /*
            A new File object is created using "element". This new file
            has a different ".name". This new name allows swift to
            create pseudo folders. The end result is the file structure
            that is dragged and dropped in swiftbrowser is preserved on
            swift.
        */

        if (element.relativePath) {
            var element = new File([element], element.relativePath + element.name, {type: "text/plain"});

        } else if (element.webkitRelativePath){
            console.log(typeof element.webkitRelativePath);
            var element = new File([element], element.webkitRelativePath + element.name, {type: "text/plain"});

        }
        var newData = $.extend({}, data);
        newData.files = fileSet ? element : [element];
        newData.paramName = paramNameSet[index];
        that._initResponseObject(newData);
        that._initProgressObject(newData);
        that._addConvenienceMethods(e, newData);
        result = that._trigger(
            'add',
            $.Event('add', {delegatedEvent: e}),
            newData
        );
        return result;
    });
    return result;
}
<|endoftext|>


<commit_before>/**
 ludo Model. A Model should be passed to a ludo.View object using the config.model property
 A Model creates dynamic getters and setters. Updates made to the model will be done to form components
 which have the same name as a column in the model.

 @namespace model
 @class Model
 @example
 model:{
	 type:'model.Model',
	 name:'user',
	 columns:['firstname', 'lastname', 'email','timezone','locale'],
	 autoLoad:true
	}
 This is an example of a model config sent to a component.
 */
ludo.model.Model = new Class({
	Extends:Events,
	type:'model.Model',

	/**
	 * @attribute {String} model name
	 * @description Name of model
	 * @default undefined
	 */
	name:undefined,
	/**
	 * Column specifications
	 @attribute columns
	 @type Array
	 @default undefined
	 @example
	 ['firstname','lastname', { name:'age', defaultValue:20 }]
	 */
	columns:undefined,
	columnKeys:[],
	currentRecord:{},

	progressBar:undefined,
	formComponents:{},
	views:[],

	/**
	 * URL for save and load. If not set, it will use the URL of nearest component.
	 * @attribute url
	 * @param url
	 * @type String
	 * @default undefined
	 */
	url:undefined,

	/**
	 * Event listeners, example : listeners :{ submit : doSomething(); }, submitfail : doSomethingElse()}
	 * @attribute Object listeners
	 * @default undefined
	 */
	listeners:undefined,
	/**
	 * Initial record id
	 * @attribute {String} recordId
	 * @default undefined
	 */
	recordId:undefined,

	/**
	 * Send initial server request even if no id is set. The model will then be populated from server with default data
	 * @attribute {Boolean} autoLoad
	 */
	autoLoad:false,

	initialize:function (config) {
		if (config.name !== undefined)this.name = config.name;
		if (config.columns !== undefined)this.columns = config.columns;
		if (config.recordId !== undefined)this.recordId = config.recordId;
		ludo.CmpMgr.registerComponent(this);

		this._validateColumns();

		if (config.listeners) {
			this.listeners = config.listeners;
		}
		if (config.url)this.url = config.url;
		this.createSettersAndGetters();
		if (this.listeners) {
			this.addEvents(this.listeners);
		}
		if (config.id || config.autoLoad) {
			this.load(config.id);
		}
	},

	_setUrl:function (url) {
		this.url = url;
	},

	_validateColumns:function () {
		var ret = {};
		for (var i = 0; i < this.columns.length; i++) {
			var obj = {};
			var name;
			if (!this.columns[i].name) {
				obj.name = name = this.columns[i];
				obj.defaultValue = '';
			} else {
				obj.name = name = this.columns[i].name;
				obj.defaultValue = this.columns[i].defaultValue || '';
			}
			ret[name] = obj;
			this.columnKeys.push(name);
		}
		this.columns = ret;
	},

	_getDefaultValue:function (column) {
		if (this.columns[column]) {
			return this.columns[column].defaultValue;
		}
		return undefined;
	},

	createSettersAndGetters:function () {
		for (var i = 0; i < this.columnKeys.length; i++) {
			this.createSetterFor(this.columnKeys[i]);
			this.createGetterFor(this.columnKeys[i]);
		}
	},

	createSetterFor:function (columnName) {
		var fn = 'set' + columnName.substr(0, 1).toUpperCase() + columnName.substr(1);
		this[fn] = function (value) {
			this._setRecordValue(columnName, value);
			this.updateViews();
			return value;
		}.bind(this)
	},

	createGetterFor:function (columnName) {
		var fn = 'get' + columnName.substr(0, 1).toUpperCase() + columnName.substr(1);
		this[fn] = function () {
			return this.getRecordProperty(columnName)
		}.bind(this)
	},

	getColumnName:function (column) {
		if (column.name) {
			return column.name;
		}
		return column;
	},

	_setRecordValue:function (property, value) {
		if (this.currentRecord) {
			this.currentRecord[property] = value;
			if (this.formComponents[property]) {
				for (var i = 0; i < this.formComponents[property].length; i++) {
					this.formComponents[property][i].setValue(value);
				}
			}
			this.fireEvent('change', [value, this]);
			this.fireEvent('update', this.currentRecord);
		}
	},

	getRecordProperty:function (property) {
		if (this.currentRecord) {
			return this.currentRecord[property];
		}
		return '';
	},
	/**
	 Load remote record from server
	 Query sent looks like this: { getModelRecord:1, recordId:recordId, modelName:this.name }
	 Response should be in this format:
	 { success: true|false, message : "on error message", data : { id:100,firstname:'John',...} }
	 @method load
	 @param {String} recordId

	 Example of query:
	 @example
	 request:{
	 		"request": "model/100/read"
	 	}
	 Example of expected response
	 @example
         {
            "success":true,
            "message":"",
            "code": 200,
            "data":{
                "id":100,
                "lastname":"Doe",
                "firstname":"John",
                "address":"My street 27",
                "zipcode":"4330",
                "city":"Springfield",
                "phone":"+00 12 23 23 43",
                "email":"john.doe@example-domain.com",
                "picture":"john.psd"
            }
         }

	 */
	load:function (recordId) {
		if (!this.url && !ludo.remote.hasGlobalUrl()) {
			return;
		}
		new ludo.remote.JSON({
			url:this.url,
			data:{
				"request":[this.name, recordId].join('/')
			},
			listeners:{
				"success":function (request) {
					this.populate(recordId, request.getResponseData());
				}.bind(this),
				"failure":function (request) {
					/**
					 * success parameter in response from server returned false
					 * @event loadfail
					 * @param {Object} JSON from server
					 * @param {Object} ludo.model
					 */
					this.fireEvent('loadfail', [request.getResponse(), this]);
				}.bind(this)
			}
		});
	},

	populate:function (recordId, record) {
		this.recordId = recordId;
		for (var prop in record) {
			if (record.hasOwnProperty(prop)) {
				this._setRecordValue(prop, record[prop]);
			}
		}
		/**
		 * Event fired when record has been successfully loaded from server
		 * @event load
		 * @param {Object} Returned record
		 * @param {Object} ludo.model
		 */
		this.fireEvent('load', [this.currentRecord, this]);
		this.commitFormFields();
		this.updateViews();
	},

	registerProgressBar:function (cmp) {
		this.progressBar = cmp;
	},
	/**
	 * Register ludo.View object. if name of component is the same
	 * as column name in model, it will add change event to the component and
	 * update it's value to current model value. This method is called
	 * by ludo.form.Manager
	 * @method registerFormComponent
	 * @param {Object} formComponent
	 * @private
	 */
	registerFormComponent:function (formComponent) {

		var name = formComponent.getName();
		if (this.columnKeys.indexOf(name) >= 0) {
			if (!this.formComponents[name]) {
				this.formComponents[name] = [];
			}
			this.formComponents[name].push(formComponent);
			formComponent.addEvent('valueChange', this.updateByForm.bind(this));
			formComponent.setValue(this.getRecordProperty(name));
			formComponent.commit();
		}
	},
	registerView:function (view) {
		this.views.push(view);
		view.insertJSON({ data:this.currentRecord });
	},

	updateViews:function () {
		for (var i = 0, len = this.views.length; i < len; i++) {
			this.views[i].insertJSON({ data:this.currentRecord });
		}
	},

	updateByForm:function (value, formComponent) {
		this._setRecordValue(formComponent.getName(), value);
		this.updateViews();

	},

	hasColumn:function (key) {
		return this.columnKeys.indexOf(key) >= 0;
	},

	/**
	 example: { freeText : 'Notes' }
	 @method save
	 @param {Object} formData
	 Save model data to server. The server request will look like this:
	 @example
		 {
		 	   "request": "Person/100/save",
			   "data": { "id:100","firstname":"Jane","lastname":"Doe" }
		 }

	 Response from server should be in this format
	 @example
	 	{
		   "success" : true,
		   "message" : "A message in case of error",
		   "response" : {
		   		"id": "100"
		   }
	   }

	 "message" is used for eventual error messages.
	 "code" is optional and may be used for internal error handling.
	 "response" is an array of updated model values. In most cases, this would be empty or undefined. Here's a use case of when
	 it might be useful:

	 - You have a View with one file upload element named "image_file"
	 - In that component, you also have a component used for preview of existing image file
	 i.e. children :
	 @example
	 	[{ type: 'form.File', label : 'New image', name : 'image_file' }, { tpl : '<img src="{preview}">' }]
	 - On click on Submit, you send the newly uploaded image file to the server and use some Image Conversion software(example: ImageMagick)
	 to generate a new "preview".
	 - Now, you want to update the view with this new preview, so you send it back in the "updates" array:
	 @example
	 	"response" : [{ preview : 'images/new-file.jpg' }]

	 */
	save:function (formData) {
		formData = formData || {};
		var data = {};
		for (var key in formData) {
			if (formData.hasOwnProperty(key) && !this.hasColumn(key)) {
				data[key] = formData[key];
			}
		}

		this.fireEvent('beforesubmit', this);

		var req =
        req.send("save", this.recordId, data);

	},
    _remoteHandler:undefined,
    remoteHandler:function(){
        if(this._remoteHandler === undefined){
            this._remoteHandler = new ludo.remote.JSON({
                url:this.url,
                resource:this.name,
                listeners:{
                    "success":function (request) {
                        var updates = request.getResponseData();
                        if (updates) {
                            this.handleModelUpdates(updates);
                        }
                        /**
                         * event fired when model is saved
                         * @event success
                         * @param {Object} JSON response from server
                         * @param {Object} ludo.model.Model
                         */
                        this.fireEvent('success', [request.getResponse(), this]);
                        this.commitFormFields();
                    }.bind(this),
                    "failure":function (request) {
                        /**
                         * Event fired when success parameter in response from server is false
                         * @event failure
                         * @param {Object} JSON response from server. Error message should be in the "message" property
                         * @param {Object} ludo.model.Model
                         *
                         */
                        this.fireEvent('failure', [request.getResponse(), this]);
                    }.bind(this),
                    "error":function (request) {
                        /**
                         * Server error event. Fired when the server didn't handle the request
                         * @event servererror
                         * @param {String} error text
                         * @param {String} error message
                         */
                        this.fireEvent('servererror', [request.getErrorText(), request.getErrorCode()]);
                    }.bind(this)
                }
            });
        }
        return this._remoteHa
    },

	getSubmitData:function (data) {
		return {
			id:'saveModelRecord',
			progressBarId:this.getProgressBarId(),
			data:{
				recordId:this.recordId,
				modelName:this.name,
				record:this.currentRecord,
				formData:data
			}
		};
	},

	getProgressBarId:function () {
		if (this.progressBar) {
			return this.progressBar.getProgressBarId();
		}
		return undefined;
	},

	handleModelUpdates:function (updates) {
		for (var column in updates) {
			if (updates.hasOwnProperty(column)) {
				this._setRecordValue(column, updates[column]);
			}
		}
	},

	/**
	 * Commit all form fields, i.e. update initial value to current value
	 * @method commitFormFields
	 * @return void
	 */
	commitFormFields:function () {
		for (var name in this.formComponents) {
			if (this.formComponents.hasOwnProperty(name)) {
				var cmps = this.formComponents[name];
				for (var i = 0; i < cmps.length; i++) {
					cmps[i].commit();
				}
			}
		}
	},
	/**
	 * New record with default values. Form elements will be updated
	 * automatically.
	 * @method newRecord
	 * @return void
	 */
	newRecord:function () {
		for (var column in this.columns) {
			if (this.columns.hasOwnProperty(column)) {
				this._setRecordValue(column, this.columns[column].defaultValue);
			}
		}
		this.commitFormFields();
		this.updateViews();
	},

	fill:function (data) {
		for (var key in data) {
			if (data.hasOwnProperty(key)) {
				this.currentRecord[key] = data[key];
			}
		}
		this.fireEvent('update', this.currentRecord);
	}
});
<commit_msg>Fixed bug in model.js<commit_after>/**
 ludo Model. A Model should be passed to a ludo.View object using the config.model property
 A Model creates dynamic getters and setters. Updates made to the model will be done to form components
 which have the same name as a column in the model.

 @namespace model
 @class Model
 @example
 model:{
	 type:'model.Model',
	 name:'user',
	 columns:['firstname', 'lastname', 'email','timezone','locale'],
	 autoLoad:true
	}
 This is an example of a model config sent to a component.
 */
ludo.model.Model = new Class({
	Extends:Events,
	type:'model.Model',

	/**
	 * @attribute {String} model name
	 * @description Name of model
	 * @default undefined
	 */
	name:undefined,
	/**
	 * Column specifications
	 @attribute columns
	 @type Array
	 @default undefined
	 @example
	 ['firstname','lastname', { name:'age', defaultValue:20 }]
	 */
	columns:undefined,
	columnKeys:[],
	currentRecord:{},

	progressBar:undefined,
	formComponents:{},
	views:[],

	/**
	 * URL for save and load. If not set, it will use the URL of nearest component.
	 * @attribute url
	 * @param url
	 * @type String
	 * @default undefined
	 */
	url:undefined,

	/**
	 * Event listeners, example : listeners :{ submit : doSomething(); }, submitfail : doSomethingElse()}
	 * @attribute Object listeners
	 * @default undefined
	 */
	listeners:undefined,
	/**
	 * Initial record id
	 * @attribute {String} recordId
	 * @default undefined
	 */
	recordId:undefined,

	/**
	 * Send initial server request even if no id is set. The model will then be populated from server with default data
	 * @attribute {Boolean} autoLoad
	 */
	autoLoad:false,

	initialize:function (config) {
		if (config.name !== undefined)this.name = config.name;
		if (config.columns !== undefined)this.columns = config.columns;
		if (config.recordId !== undefined)this.recordId = config.recordId;
		ludo.CmpMgr.registerComponent(this);

		this._validateColumns();

		if (config.listeners) {
			this.listeners = config.listeners;
		}
		if (config.url)this.url = config.url;
		this.createSettersAndGetters();
		if (this.listeners) {
			this.addEvents(this.listeners);
		}
		if (config.id || config.autoLoad) {
			this.load(config.id);
		}
	},

	_setUrl:function (url) {
		this.url = url;
	},

	_validateColumns:function () {
		var ret = {};
		for (var i = 0; i < this.columns.length; i++) {
			var obj = {};
			var name;
			if (!this.columns[i].name) {
				obj.name = name = this.columns[i];
				obj.defaultValue = '';
			} else {
				obj.name = name = this.columns[i].name;
				obj.defaultValue = this.columns[i].defaultValue || '';
			}
			ret[name] = obj;
			this.columnKeys.push(name);
		}
		this.columns = ret;
	},

	_getDefaultValue:function (column) {
		if (this.columns[column]) {
			return this.columns[column].defaultValue;
		}
		return undefined;
	},

	createSettersAndGetters:function () {
		for (var i = 0; i < this.columnKeys.length; i++) {
			this.createSetterFor(this.columnKeys[i]);
			this.createGetterFor(this.columnKeys[i]);
		}
	},

	createSetterFor:function (columnName) {
		var fn = 'set' + columnName.substr(0, 1).toUpperCase() + columnName.substr(1);
		this[fn] = function (value) {
			this._setRecordValue(columnName, value);
			this.updateViews();
			return value;
		}.bind(this)
	},

	createGetterFor:function (columnName) {
		var fn = 'get' + columnName.substr(0, 1).toUpperCase() + columnName.substr(1);
		this[fn] = function () {
			return this.getRecordProperty(columnName)
		}.bind(this)
	},

	getColumnName:function (column) {
		if (column.name) {
			return column.name;
		}
		return column;
	},

	_setRecordValue:function (property, value) {
		if (this.currentRecord) {
			this.currentRecord[property] = value;
			if (this.formComponents[property]) {
				for (var i = 0; i < this.formComponents[property].length; i++) {
					this.formComponents[property][i].setValue(value);
				}
			}
			this.fireEvent('change', [value, this]);
			this.fireEvent('update', this.currentRecord);
		}
	},

	getRecordProperty:function (property) {
		if (this.currentRecord) {
			return this.currentRecord[property];
		}
		return '';
	},
	/**
	 Load remote record from server
	 Query sent looks like this: { getModelRecord:1, recordId:recordId, modelName:this.name }
	 Response should be in this format:
	 { success: true|false, message : "on error message", data : { id:100,firstname:'John',...} }
	 @method load
	 @param {String} recordId

	 Example of query:
	 @example
	 request:{
	 		"request": "model/100/read"
	 	}
	 Example of expected response
	 @example
         {
            "success":true,
            "message":"",
            "code": 200,
            "data":{
                "id":100,
                "lastname":"Doe",
                "firstname":"John",
                "address":"My street 27",
                "zipcode":"4330",
                "city":"Springfield",
                "phone":"+00 12 23 23 43",
                "email":"john.doe@example-domain.com",
                "picture":"john.psd"
            }
         }

	 */
	load:function (recordId) {
		if (!this.url && !ludo.remote.hasGlobalUrl()) {
			return;
		}
		new ludo.remote.JSON({
			url:this.url,
			data:{
				"request":[this.name, recordId].join('/')
			},
			listeners:{
				"success":function (request) {
					this.populate(recordId, request.getResponseData());
				}.bind(this),
				"failure":function (request) {
					/**
					 * success parameter in response from server returned false
					 * @event loadfail
					 * @param {Object} JSON from server
					 * @param {Object} ludo.model
					 */
					this.fireEvent('loadfail', [request.getResponse(), this]);
				}.bind(this)
			}
		});
	},

	populate:function (recordId, record) {
		this.recordId = recordId;
		for (var prop in record) {
			if (record.hasOwnProperty(prop)) {
				this._setRecordValue(prop, record[prop]);
			}
		}
		/**
		 * Event fired when record has been successfully loaded from server
		 * @event load
		 * @param {Object} Returned record
		 * @param {Object} ludo.model
		 */
		this.fireEvent('load', [this.currentRecord, this]);
		this.commitFormFields();
		this.updateViews();
	},

	registerProgressBar:function (cmp) {
		this.progressBar = cmp;
	},
	/**
	 * Register ludo.View object. if name of component is the same
	 * as column name in model, it will add change event to the component and
	 * update it's value to current model value. This method is called
	 * by ludo.form.Manager
	 * @method registerFormComponent
	 * @param {Object} formComponent
	 * @private
	 */
	registerFormComponent:function (formComponent) {

		var name = formComponent.getName();
		if (this.columnKeys.indexOf(name) >= 0) {
			if (!this.formComponents[name]) {
				this.formComponents[name] = [];
			}
			this.formComponents[name].push(formComponent);
			formComponent.addEvent('valueChange', this.updateByForm.bind(this));
			formComponent.setValue(this.getRecordProperty(name));
			formComponent.commit();
		}
	},
	registerView:function (view) {
		this.views.push(view);
		view.insertJSON({ data:this.currentRecord });
	},

	updateViews:function () {
		for (var i = 0, len = this.views.length; i < len; i++) {
			this.views[i].insertJSON({ data:this.currentRecord });
		}
	},

	updateByForm:function (value, formComponent) {
		this._setRecordValue(formComponent.getName(), value);
		this.updateViews();

	},

	hasColumn:function (key) {
		return this.columnKeys.indexOf(key) >= 0;
	},

	/**
	 example: { freeText : 'Notes' }
	 @method save
	 @param {Object} formData
	 Save model data to server. The server request will look like this:
	 @example
		 {
		 	   "request": "Person/100/save",
			   "data": { "id:100","firstname":"Jane","lastname":"Doe" }
		 }

	 Response from server should be in this format
	 @example
	 	{
		   "success" : true,
		   "message" : "A message in case of error",
		   "response" : {
		   		"id": "100"
		   }
	   }

	 "message" is used for eventual error messages.
	 "code" is optional and may be used for internal error handling.
	 "response" is an array of updated model values. In most cases, this would be empty or undefined. Here's a use case of when
	 it might be useful:

	 - You have a View with one file upload element named "image_file"
	 - In that component, you also have a component used for preview of existing image file
	 i.e. children :
	 @example
	 	[{ type: 'form.File', label : 'New image', name : 'image_file' }, { tpl : '<img src="{preview}">' }]
	 - On click on Submit, you send the newly uploaded image file to the server and use some Image Conversion software(example: ImageMagick)
	 to generate a new "preview".
	 - Now, you want to update the view with this new preview, so you send it back in the "updates" array:
	 @example
	 	"response" : [{ preview : 'images/new-file.jpg' }]

	 */
	save:function (formData) {
		formData = formData || {};
		var data = {};
		for (var key in formData) {
			if (formData.hasOwnProperty(key) && !this.hasColumn(key)) {
				data[key] = formData[key];
			}
		}

		this.fireEvent('beforesubmit', this);
        this.request().send("save", this.recordId, data);

	},
    _request:undefined,
    request:function(){
        if(this._request === undefined){
            this._request = new ludo.remote.JSON({
                url:this.url,
                resource:this.name,
                listeners:{
                    "success":function (request) {
                        var updates = request.getResponseData();
                        if (updates) {
                            this.handleModelUpdates(updates);
                        }
                        /**
                         * event fired when model is saved
                         * @event success
                         * @param {Object} JSON response from server
                         * @param {Object} ludo.model.Model
                         */
                        this.fireEvent('success', [request.getResponse(), this]);
                        this.commitFormFields();
                    }.bind(this),
                    "failure":function (request) {
                        /**
                         * Event fired when success parameter in response from server is false
                         * @event failure
                         * @param {Object} JSON response from server. Error message should be in the "message" property
                         * @param {Object} ludo.model.Model
                         *
                         */
                        this.fireEvent('failure', [request.getResponse(), this]);
                    }.bind(this),
                    "error":function (request) {
                        /**
                         * Server error event. Fired when the server didn't handle the request
                         * @event servererror
                         * @param {String} error text
                         * @param {String} error message
                         */
                        this.fireEvent('servererror', [request.getErrorText(), request.getErrorCode()]);
                    }.bind(this)
                }
            });
        }
        return this._remoteHa
    },

	getSubmitData:function (data) {
		return {
			id:'saveModelRecord',
			progressBarId:this.getProgressBarId(),
			data:{
				recordId:this.recordId,
				modelName:this.name,
				record:this.currentRecord,
				formData:data
			}
		};
	},

	getProgressBarId:function () {
		if (this.progressBar) {
			return this.progressBar.getProgressBarId();
		}
		return undefined;
	},

	handleModelUpdates:function (updates) {
		for (var column in updates) {
			if (updates.hasOwnProperty(column)) {
				this._setRecordValue(column, updates[column]);
			}
		}
	},

	/**
	 * Commit all form fields, i.e. update initial value to current value
	 * @method commitFormFields
	 * @return void
	 */
	commitFormFields:function () {
		for (var name in this.formComponents) {
			if (this.formComponents.hasOwnProperty(name)) {
				var cmps = this.formComponents[name];
				for (var i = 0; i < cmps.length; i++) {
					cmps[i].commit();
				}
			}
		}
	},
	/**
	 * New record with default values. Form elements will be updated
	 * automatically.
	 * @method newRecord
	 * @return void
	 */
	newRecord:function () {
		for (var column in this.columns) {
			if (this.columns.hasOwnProperty(column)) {
				this._setRecordValue(column, this.columns[column].defaultValue);
			}
		}
		this.commitFormFields();
		this.updateViews();
	},

	fill:function (data) {
		for (var key in data) {
			if (data.hasOwnProperty(key)) {
				this.currentRecord[key] = data[key];
			}
		}
		this.fireEvent('update', this.currentRecord);
	}
});
<|endoftext|>


<commit_before>/**
 * Round x with n decimal.
 * @param {Number} x Number to be rounded.
 * @param {Number} n Number of digits after semi column.
 * @returns {Number} Number rounded.
 */
const round = (x, n) => Math.round(x * (n * 10)) / (n * 10);

const scaleN = (n) => round(Math.pow(1.2, n), 3);

const scale = n => `${scaleN(n)}rem`;

const colors = {
  white: '#D3D0CB',
  black: '#1E2019', // #393E41
  yellow: '#E2C044',
  red: '#FF0000',
  darkBlue: '#587B7F',
  blue: '#85B9BF',
  green: '#9FE80C',
  gray: '#393E41',

  blackShades: [
    '#32342D'
  ]
};

const fontFamily = '"Cambo", serif';

const theme = {
  scale,
  scaleN,
  colors,
  fontFamily,
  color: colors.white,
  bg: colors.black,
  padding: `0 ${scaleN(1)}rem ${scaleN(10)}rem ${scaleN(0)}rem`,
  margin: '0 auto',
  maxWidth: '60rem',
  borderRadius: '0.2rem',
  lineHeight: 1.5,
  focus: {
    border: '0.4rem red solid'
  },
  p: {
    fontSize: scale(1),
    lineHeight: scaleN(2),
    firstLetter: {
      fontSize: `${(scaleN(1) * 2) + scaleN(2)}rem`,
      color: colors.yellow,
      lineHeight: 0.2,
      padding: '1.3rem 0.1rem',
      margin: 0
    }
  },
  a: {
    fontFamily,
    color: colors.white,
    textDecoration: 'none',
    hover: {
      color: colors.yellow
    }
  },
  header: {
    title: {
      fontSize: scale(3),
      textAlign: 'left',
      padding: `${scale(0)} 0 0 0`,
      margin: `0 0 ${scale(6)} 0`,
      lineHeight: scaleN(0)
    },
    subTitle: {
      fontSize: `${scale(0)}`,
      padding: `${scale(-4)} 0 0 0`,
      margin: `${scale(-20)} 0 0 0`,
      lineHeight: scaleN(-1)
    },
    isHome: {
      title: {
        fontSize: scale(7),
        textAlign: 'center',
        padding: `${scale(2)} 0 0 0`,
        margin: `${scale(0)} 0 ${scale(4)} 0`,
        lineHeight: scaleN(0)
      },
      subTitle: {
        fontSize: `${scale(0)}`,
        padding: `${scale(-4)} 0 0 0`,
        margin: `0`,
        lineHeight: scaleN(-1)
      }
    }
  },
  menu: {
    padding: scale(2),
    opened: {
      bg: colors.blackShades[0]
    },
    closed: {
      bg: 'transparent'
    },
    label: {
      width: scale(3),
      height: scale(3),
      fontSize: scale(3.2)
    },
    a: {
      fontSize: scale(1),
      paddingBottom: scale(1),
      textAlign: 'left',
      color: colors.white,
      active: {
        color: colors.yellow
      }
    },
    ul: {
      marginTop: scale(6),
      marginLeft: scale(7.8)
    }
  },
  socialLinks: {
    margin: `0 ${scale(0)}`,
    padding: 0,
    a: {
      color: colors.white,
      fontSize: scale(-1),
      margin: 0,
      padding: `${scale(0)} ${scale(0)} 0 ${scale(0)}`,
      svg: {
        fontSize: scale(5),
        margin: `0 auto ${scale(-4)} auto`,
        padding: 0,
      },
      hover: {
        color: colors.yellow
      }
    }
  },
  welcome: {
    marginTop: scale(6),
    marginBottom: scale(2),
  },
  h1: {
    fontSize: scale(6),
    padding: `${scale(1)} 0 0 0`,
    margin: `${scale(4)} 0 0 0`,
    textAlign: 'center'
  },
  h2: {
    fontSize: scale(4),
    padding: `${scale(1)} 0 0 0`,
    margin: `${scale(4)} 0 0 0`,
    textAlign: 'center'
  },
  blog: {
    list: {
      header: {
        fontSize: scale(6),
        padding: `${scale(1)} 0 0 0`,
        margin: `${scale(4)} 0 0 0`,
      },
      ul: {
        padding: `0`,
        margin: `0`,
      },
      item: {
        padding: scale(2),
        margin: `0 -${scaleN(2)}rem`,
        hover: {
          backgroundColor: colors.blackShades[0]
        },
        header: {
          fontSize: scale(3),
          lineHeight: scaleN(1),
          time: {
            fontSize: scale(0),
            color: colors.blue
          }
        },
        p: {
          fontSize: scale(1),
          lineHeight: scaleN(2),
          padding: `0`,
          margin: `0`,
        }
      }
    },
    post: {
      padding: 0,
      margin: `0 0 ${scale(4)} 0`,
      header: {
        fontSize: scale(5),
        time: {
          fontSize: scale(0),
          color: colors.blue
        }
      },
      content: {
        code: {
          color: colors.yellow,
          fontSize: scale(1)
        },
        highlight: {
          backgroundColor: colors.gray,
          borderRadius: '0.2rem',
          margin: `${scale(0)} 0`,
          padding: `${scale(0)}`,
          code: {
            color: colors.white
          }
        },
        a: {
          color: colors.yellow
        },
        h1: {
          fontSize: scale(4),
          margin: 0,
          padding: `${scale(4)} 0 0 0`
        },
        h2: {
          fontSize: scale(3),
          margin: 0,
          padding: `${scale(3)} 0 0 0`
        },
        h3: {
          fontSize: scale(2),
          margin: 0,
          padding: `${scale(2)} 0 0 0`
        },
        h4: {
          fontSize: scale(1),
          margin: 0,
          padding: `${scale(1)} 0 0 0`
        },
        h5: {
          fontSize: scale(0),
          margin: 0,
          padding: `${scale(0)} 0 0 0`
        },
        h6: {
          fontSize: scale(-1),
          margin: 0,
          padding: `${scale(0)} 0 0 0`
        },
        p: {
          fontSize: scale(1),
          margin: 0,
          padding: `${scale(0)} 0 0 0`
        },
        ul: {
          fontSize: scale(1),
          margin: 0,
          padding: `${scale(0)}`
        }
      }
    }
  },
  i18n: {
    selectLanguage: {
      li: {
        margin: `-${scale(0)} 0 0 0`,
        padding: `${scale(5)} ${scale(-2)} ${scale(-5)} ${scale(-2)}`,
        fontSize: scale(3),
        borderRadius: '0.2rem',
        selected: {
          backgroundColor: colors.gray
        }
      }
    }
  }
};

export default theme;
<commit_msg>Style header and remove old styles<commit_after>/**
 * Round x with n decimal.
 * @param {Number} x Number to be rounded.
 * @param {Number} n Number of digits after semi column.
 * @returns {Number} Number rounded.
 */
const round = (x, n) => Math.round(x * (n * 10)) / (n * 10);

const scaleN = (n) => round(Math.pow(1.2, n), 3);

const scale = n => `${scaleN(n)}rem`;

const colors = {
  white: '#D3D0CB',
  black: '#1E2019', // #393E41
  yellow: '#E2C044',
  red: '#FF0000',
  darkBlue: '#587B7F',
  blue: '#85B9BF',
  green: '#9FE80C',
  gray: '#393E41',

  blackShades: [
    '#32342D'
  ]
};

const fontFamily = '"Cambo", serif';

const theme = {
  scale,
  scaleN,
  colors,
  fontFamily,
  color: colors.white,
  bg: colors.black,
  padding: `0 ${scaleN(1)}rem ${scaleN(10)}rem ${scaleN(0)}rem`,
  margin: '0 auto',
  maxWidth: '60rem',
  borderRadius: '0.2rem',
  lineHeight: 1.5,
  focus: {
    border: '0.4rem red solid'
  },
  p: {
    fontSize: scale(1),
    lineHeight: scaleN(2),
    firstLetter: {
      fontSize: `${(scaleN(1) * 2) + scaleN(2)}rem`,
      color: colors.yellow,
      lineHeight: 0.2,
      padding: '1.3rem 0.1rem',
      margin: 0
    }
  },
  a: {
    fontFamily,
    color: colors.white,
    textDecoration: 'none',
    hover: {
      color: colors.yellow
    }
  },
  header: {
    title: {
      fontSize: scale(3),
      textAlign: 'left',
      padding: `${scale(0)} 0 0 0`,
      margin: `0 0 ${scale(6)} 0`,
      lineHeight: scaleN(0)
    },
    subTitle: {
      fontSize: `${scale(0)}`,
      padding: `${scale(-4)} 0 0 0`,
      margin: `${scale(-20)} 0 0 0`,
      lineHeight: scaleN(-1)
    },
    isHome: {
      title: {
        fontSize: scale(4),
        textAlign: 'center',
        padding: `${scale(2)} 0 0 0`,
        margin: `${scale(0)} 0 ${scale(4)} 0`,
        lineHeight: scaleN(0)
      },
      subTitle: {
        fontSize: `${scale(0)}`,
        padding: `${scale(-4)} 0 0 0`,
        margin: `0`,
        lineHeight: scaleN(-1)
      }
    }
  },
  menu: {
    padding: scale(2),
    opened: {
      bg: colors.blackShades[0]
    },
    closed: {
      bg: 'transparent'
    },
    label: {
      width: scale(3),
      height: scale(3),
      fontSize: scale(3.2)
    },
    a: {
      fontSize: scale(1),
      paddingBottom: scale(1),
      textAlign: 'left',
      color: colors.white,
      active: {
        color: colors.yellow
      }
    },
    ul: {
      marginTop: scale(6),
      marginLeft: scale(7.8)
    }
  },
  h1: {
    fontSize: scale(6),
    padding: `${scale(1)} 0 0 0`,
    margin: `${scale(4)} 0 0 0`,
    textAlign: 'center'
  },
  h2: {
    fontSize: scale(4),
    padding: `${scale(1)} 0 0 0`,
    margin: `${scale(4)} 0 0 0`,
    textAlign: 'center'
  },
  i18n: {
    selectLanguage: {
      li: {
        margin: `-${scale(0)} 0 0 0`,
        padding: `${scale(5)} ${scale(-2)} ${scale(-5)} ${scale(-2)}`,
        fontSize: scale(3),
        borderRadius: '0.2rem',
        selected: {
          backgroundColor: colors.gray
        }
      }
    }
  }
};

export default theme;
<|endoftext|>


<commit_before>import cuid from 'cuid';
import Image from 'src/models/Image';
import { slugger, liveRootUrl } from 'src/config/constants';

/**
 * A blog post
 * @export
 * @param {Object} [post={}] Pass existing post data or leave empty to create a new post
 * @returns Post class object
 */
export default function Post(post = {}) {
  // Properties
  this.id = post.id || cuid();
  this.created = post.created || Date.now();
  this.lastEdited = post.lastEdited || null;
  this.lastSaved = post.lastSaved || null;
  this.lastPublished = post.lastPublished || null;
  this.author = post.author || null;
  this.markdown = post.markdown || '';
  this.images = post.images || [];
  this.html = post.html || null;
  this.notificationSent = post.notificationSent || false;
  this.excerpt = post.excerpt || null;
  this.description = post.description || null;
  this.titleImage = post.titleImage || null;
  this.type = 'post';
  this.drawings = post.drawings || [];

  /**
   * Normalize the post object, no functions, ready for setting to firebase
   * @returns {Object} Clean post object without functions
   */
  this.normalize = () => JSON.parse(JSON.stringify(this));

  /**
   * Get the post title, first line starting with a single #
   * @returns {String} Post title extracted from post markdown or empty string
   */
  Object.defineProperty(this, 'title', {
    get() {
      const title = this.markdown.match(/^# .+/gm);
      return title ? title[0].replace('# ', '') : '';
    },
  });

  Object.defineProperty(this, 'liveUrl', {
    get() {
      const url = `${liveRootUrl}${slugger(this.title)}`;
      return url || '';
    },
  });

  Object.defineProperty(this, 'url', {
    get() {
      const url = `/posts/${slugger(this.title)}`;
      return url || '';
    },
  });

  Object.defineProperty(this, 'heroImageUrl', {
    get() {
      const image = this.titleImage
        ? this.titleImage.url
        : new Image(this.images[0]).getSmallestThumbUrl();
      return image;
    },
  });

  Object.defineProperty(this, 'editUrl', {
    get() {
      return `/editpost/${this.id}`;
    },
  });
}
<commit_msg>Replace formatted parts of a title<commit_after>import cuid from 'cuid';
import Image from 'src/models/Image';
import { slugger, liveRootUrl } from 'src/config/constants';

/**
 * A blog post
 * @export
 * @param {Object} [post={}] Pass existing post data or leave empty to create a new post
 * @returns Post class object
 */
export default function Post(post = {}) {
  // Properties
  this.id = post.id || cuid();
  this.created = post.created || Date.now();
  this.lastEdited = post.lastEdited || null;
  this.lastSaved = post.lastSaved || null;
  this.lastPublished = post.lastPublished || null;
  this.author = post.author || null;
  this.markdown = post.markdown || '';
  this.images = post.images || [];
  this.html = post.html || null;
  this.notificationSent = post.notificationSent || false;
  this.excerpt = post.excerpt || null;
  this.description = post.description || null;
  this.titleImage = post.titleImage || null;
  this.type = 'post';
  this.drawings = post.drawings || [];

  /**
   * Normalize the post object, no functions, ready for setting to firebase
   * @returns {Object} Clean post object without functions
   */
  this.normalize = () => JSON.parse(JSON.stringify(this));

  /**
   * Get the post title, first line starting with a single #
   * @returns {String} Post title extracted from post markdown or empty string
   */
  Object.defineProperty(this, 'title', {
    get() {
      const title = this.markdown.match(/^# .+/gm);
      return title ? title[0].replace('# ', '').replace(/\*.+\*/g, '') : '';
    },
  });

  Object.defineProperty(this, 'liveUrl', {
    get() {
      const url = `${liveRootUrl}${slugger(this.title)}`;
      return url || '';
    },
  });

  Object.defineProperty(this, 'url', {
    get() {
      const url = `/posts/${slugger(this.title)}`;
      return url || '';
    },
  });

  Object.defineProperty(this, 'heroImageUrl', {
    get() {
      const image = this.titleImage
        ? this.titleImage.url
        : new Image(this.images[0]).getSmallestThumbUrl();
      return image;
    },
  });

  Object.defineProperty(this, 'editUrl', {
    get() {
      return `/editpost/${this.id}`;
    },
  });
}
<|endoftext|>


<commit_before>var socketIo = require('socket.io');
var command = require('./command');
var defaultAPI = require('./api/socket.io');

var DEFAULT_TIMEOUT = 10 * 1000;

function deepClone(base) {
  if (!base || typeof base != 'object')
    return base;

  var cloned = Object.create(null);
  Object.keys(base).forEach(function(key) {
    cloned[key] = deepClone(base[key]);
  });
  return cloned;
}

function sanitizeBackendMessage(envelope) {
  return {
    statusCode: envelope.statusCode,
    body: deepClone(envelope.body)
  };
}
exports.sanitizeBackendMessage = sanitizeBackendMessage;

exports.register = function(application, server, params) {
  params = params || {};
  var connection = params.connection;
  if (!connection)
    throw new Error('Connection to the backend is required!');

  if (!server)
    throw new Error('A server instance is required!');

  function createClientMessageHandler(commandName, socket) {
    return (function(data, clientOptions) {
      var commandDefinition = unifiedCommandSet[commandName];
      if (!commandDefinition)
        return;

      clientOptions = clientOptions || {};

      var event = commandName;
      if (commandDefinition.requestConverter) {
        try {
          var result = commandDefinition.requestConverter(event, data);
          event = result[0];
          data = result[1];
        } catch(error) {
          socket.emit('error', error.message || error);
          return;
        }
      }

      var callback = null;
      var options = {};
      if (command.RequestResponse.isInstance(commandDefinition)) {
        callback = function(error, envelope) {
          if (error) {
            socket.emit('error', error);
            return;
          }

          var responseEvent = envelope.type;
          var responseData = envelope.body;

          if (commandDefinition.responseConverter) {
            var result = commandDefinition.responseConverter(responseEvent, responseData);
            responseEvent = result[0];
            responseData = result[1];
          }

          if (clientOptions.responseEvent)
            responseEvent = clientOptions.responseEvent;

          socket.emit(responseEvent, responseData);
        };
        options.timeout = DEFAULT_TIMEOUT;
      } else if (command.PublishSubscribe.isInstance(commandDefinition)) {
        event += '.subscribe';
      }

      options.sessionId = socket.id;
      connection.emitMessage(event, data, callback, options);
    });
  }

  function createPublishedMessageHandler(commandName, socket) {
    return (function(body) {
      var commandDefinition = unifiedCommandSet[commandName];

      var event = commandName;
      var data = body;

      if (commandDefinition.responseConverter) {
        var result = commandDefinition.responseConverter(event, data);
        event = result[0];
        data = result[1];
      }

      socket.emit(event, data);
    });
  }

  function createErrorHandler(socket) {
    return (function(error) {
      socket.emit('error', error);
    });
  }

  var commandSets = [defaultAPI].concat(params.plugins || []);
  var unifiedCommandSet = {};
  commandSets.forEach(function(commandSet) {
    Object.keys(commandSet).forEach(function(commandName) {
      var definition = commandSet[commandName];
      if (!command.SocketCommand.isInstance(definition))
        return;
      unifiedCommandSet[commandName] = definition;
    });
  });

  var registeredCommands = [];
  Object.keys(unifiedCommandSet).forEach(function(commandName) {
    var definition = unifiedCommandSet[commandName];
    if (!command.SocketCommand.isInstance(definition))
      return;
    registeredCommands.push({ name:       commandName,
                              definition: definition });
  });

  var io = socketIo.listen(server);
  io.sockets.on('connection', function(socket) {
    application.emit('connection', socket);

    var events = [];
    var handlers = {};
    registeredCommands.forEach(function(command) {
      if (command.PublishSubscribe.isInstance(command.definition)) {
        socket.on(command.name + '.subscribe',
                  createClientMessageHandler(command.name, socket));

        var publishedMessageHandler = createPublishedMessageHandler(command.name, socket);
        connection.on(command.name, publishedMessageHandler);
        events.push(command.name);
        handlers[command.name] = publishedMessageHandler;
      } else {
        socket.on(command.name,
                  createClientMessageHandler(command.name, socket));
      }
    });

    var errorHandler = createErrorHandler(socket);
    connection.on('error', errorHandler);

    socket.on('disconnect', function() {
      events.forEach(function(event) {
        connection.removeListener(event, handlers[event]);
      });
      events = undefined;
      handlers = undefined;
      connection.removeListener('error', errorHandler);
      socket.removeAllListeners();
    });
  });

  return registeredCommands;
}
<commit_msg>Resolve confliction of variable names<commit_after>var socketIo = require('socket.io');
var command = require('./command');
var defaultAPI = require('./api/socket.io');

var DEFAULT_TIMEOUT = 10 * 1000;

function deepClone(base) {
  if (!base || typeof base != 'object')
    return base;

  var cloned = Object.create(null);
  Object.keys(base).forEach(function(key) {
    cloned[key] = deepClone(base[key]);
  });
  return cloned;
}

function sanitizeBackendMessage(envelope) {
  return {
    statusCode: envelope.statusCode,
    body: deepClone(envelope.body)
  };
}
exports.sanitizeBackendMessage = sanitizeBackendMessage;

exports.register = function(application, server, params) {
  params = params || {};
  var connection = params.connection;
  if (!connection)
    throw new Error('Connection to the backend is required!');

  if (!server)
    throw new Error('A server instance is required!');

  function createClientMessageHandler(commandName, socket) {
    return (function(data, clientOptions) {
      var commandDefinition = unifiedCommandSet[commandName];
      if (!commandDefinition)
        return;

      clientOptions = clientOptions || {};

      var event = commandName;
      if (commandDefinition.requestConverter) {
        try {
          var result = commandDefinition.requestConverter(event, data);
          event = result[0];
          data = result[1];
        } catch(error) {
          socket.emit('error', error.message || error);
          return;
        }
      }

      var callback = null;
      var options = {};
      if (command.RequestResponse.isInstance(commandDefinition)) {
        callback = function(error, envelope) {
          if (error) {
            socket.emit('error', error);
            return;
          }

          var responseEvent = envelope.type;
          var responseData = envelope.body;

          if (commandDefinition.responseConverter) {
            var result = commandDefinition.responseConverter(responseEvent, responseData);
            responseEvent = result[0];
            responseData = result[1];
          }

          if (clientOptions.responseEvent)
            responseEvent = clientOptions.responseEvent;

          socket.emit(responseEvent, responseData);
        };
        options.timeout = DEFAULT_TIMEOUT;
      } else if (command.PublishSubscribe.isInstance(commandDefinition)) {
        event += '.subscribe';
      }

      options.sessionId = socket.id;
      connection.emitMessage(event, data, callback, options);
    });
  }

  function createPublishedMessageHandler(commandName, socket) {
    return (function(body) {
      var commandDefinition = unifiedCommandSet[commandName];

      var event = commandName;
      var data = body;

      if (commandDefinition.responseConverter) {
        var result = commandDefinition.responseConverter(event, data);
        event = result[0];
        data = result[1];
      }

      socket.emit(event, data);
    });
  }

  function createErrorHandler(socket) {
    return (function(error) {
      socket.emit('error', error);
    });
  }

  var commandSets = [defaultAPI].concat(params.plugins || []);
  var unifiedCommandSet = {};
  commandSets.forEach(function(commandSet) {
    Object.keys(commandSet).forEach(function(commandName) {
      var definition = commandSet[commandName];
      if (!command.SocketCommand.isInstance(definition))
        return;
      unifiedCommandSet[commandName] = definition;
    });
  });

  var registeredCommands = [];
  Object.keys(unifiedCommandSet).forEach(function(commandName) {
    var definition = unifiedCommandSet[commandName];
    if (!command.SocketCommand.isInstance(definition))
      return;
    registeredCommands.push({ name:       commandName,
                              definition: definition });
  });

  var io = socketIo.listen(server);
  io.sockets.on('connection', function(socket) {
    application.emit('connection', socket);

    var events = [];
    var handlers = {};
    registeredCommands.forEach(function(registeredCommand) {
      if (command.PublishSubscribe.isInstance(registeredCommand.definition)) {
        socket.on(registeredCommand.name + '.subscribe',
                  createClientMessageHandler(registeredCommand.name, socket));

        var publishedMessageHandler = createPublishedMessageHandler(registeredCommand.name, socket);
        connection.on(registeredCommand.name, publishedMessageHandler);
        events.push(registeredCommand.name);
        handlers[registeredCommand.name] = publishedMessageHandler;
      } else {
        socket.on(registeredCommand.name,
                  createClientMessageHandler(registeredCommand.name, socket));
      }
    });

    var errorHandler = createErrorHandler(socket);
    connection.on('error', errorHandler);

    socket.on('disconnect', function() {
      events.forEach(function(event) {
        connection.removeListener(event, handlers[event]);
      });
      events = undefined;
      handlers = undefined;
      connection.removeListener('error', errorHandler);
      socket.removeAllListeners();
    });
  });

  return registeredCommands;
}
<|endoftext|>


<commit_before>import Adapter from '../adapter';
import { pluralize } from 'inflection';

/**
 * Integrate blackburn with your Mongoose ORM.
 *
 * @title Mongoose Adapter
 */

export default Adapter.extend({

  /**
   * Given a Mongoose Document, return the type.
   *
   * @method typeForRecord
   *
   * @param  {Object}      record  a Mongoose Document
   * @param  {Object}      options
   *
   * @return {String}
   */
  typeForRecord(record) {
    return pluralize(record.constructor.modelName);
  },

  /**
   * Given a Mongoose Document, return it's id.
   *
   * @method idForRecord
   *
   * @param  {Object}    record
   *
   * @return {Number|String}  id of the supplied record
   */
  idForRecord(record) {
    return record.id;
  },

  /**
   * Fetch a simple attribute from the record.
   *
   * @method attributeFromRecord
   *
   * @param  {Object}            record
   * @param  {String}            attributeName
   *
   * @return {*}
   */
  attributeFromRecord(record, attributeName) {
    return record[attributeName];
  },

  /**
   * Given a record, the relationship name, and the relationship config, return
   * the data for a given record, which could be the ids, an id, the records, or
   * a specific record.
   *
   * This method takes advantage of the fact that the Document.populated()
   * method either returns the id(s) of the populated record(s), or undefined.
   *
   * @method relationshipFromRecord
   *
   * @param  {Object}  record
   * @param  {String}  relationshipName
   * @param  {Object}  relationshipConfig
   * @param  {String}  relationshipConfig.strategy  the serialization strategy,
   * either 'ids', 'id', 'records', or 'records'
   * @param  {String}  relationshipConfig.type  the type of the related records
   *
   * @return {Object[]|Object|String|Number|String[]|Number[]}  related data
   */
  relationshipFromRecord(record, relationshipName, relationshipConfig/*, options*/) {
    let strategy = relationshipConfig.strategy;
    if (strategy === 'ids' || strategy === 'id') {
      return record.populated(relationshipName) || record[relationshipName];
    } else {
      return record[relationshipName];
    }
  },

});
<commit_msg>fix dangling comma<commit_after>import Adapter from '../adapter';
import { pluralize } from 'inflection';

/**
 * Integrate blackburn with your Mongoose ORM.
 *
 * @title Mongoose Adapter
 */

export default Adapter.extend({

  /**
   * Given a Mongoose Document, return the type.
   *
   * @method typeForRecord
   *
   * @param  {Object}      record  a Mongoose Document
   * @param  {Object}      options
   *
   * @return {String}
   */
  typeForRecord(record) {
    return pluralize(record.constructor.modelName);
  },

  /**
   * Given a Mongoose Document, return it's id.
   *
   * @method idForRecord
   *
   * @param  {Object}    record
   *
   * @return {Number|String}  id of the supplied record
   */
  idForRecord(record) {
    return record.id;
  },

  /**
   * Fetch a simple attribute from the record.
   *
   * @method attributeFromRecord
   *
   * @param  {Object}            record
   * @param  {String}            attributeName
   *
   * @return {*}
   */
  attributeFromRecord(record, attributeName) {
    return record[attributeName];
  },

  /**
   * Given a record, the relationship name, and the relationship config, return
   * the data for a given record, which could be the ids, an id, the records, or
   * a specific record.
   *
   * This method takes advantage of the fact that the Document.populated()
   * method either returns the id(s) of the populated record(s), or undefined.
   *
   * @method relationshipFromRecord
   *
   * @param  {Object}  record
   * @param  {String}  relationshipName
   * @param  {Object}  relationshipConfig
   * @param  {String}  relationshipConfig.strategy  the serialization strategy,
   * either 'ids', 'id', 'records', or 'records'
   * @param  {String}  relationshipConfig.type  the type of the related records
   *
   * @return {Object[]|Object|String|Number|String[]|Number[]}  related data
   */
  relationshipFromRecord(record, relationshipName, relationshipConfig/*, options*/) {
    let strategy = relationshipConfig.strategy;
    if (strategy === 'ids' || strategy === 'id') {
      return record.populated(relationshipName) || record[relationshipName];
    } else {
      return record[relationshipName];
    }
  }

});
<|endoftext|>


<commit_before>
(function(window, angular, undefined) {'use strict';
// define ngTouch module
/* global -ngTouch */
var ngTouch = angular.module('ngTouch', []);

/**
 * @ngdoc directive
 * @name ngClick
 *
 * @description
 * A more powerful replacement for the default ngClick designed to be used on touchscreen
 * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
 * the click event. This version handles them immediately, and then prevents the
 * following click event from propagating.
 *
 * Requires the {@link ngTouch `ngTouch`} module to be installed.
 *
 * This directive can fall back to using an ordinary click event, and so works on desktop
 * browsers as well as mobile.
 *
 * This directive also sets the CSS class `ng-click-active` while the element is being held
 * down (by a mouse click or touch) so you can restyle the depressed element if you wish.
 *
 * @element ANY
 * @param {expression} ngClick {@link guide/expression Expression} to evaluate
 * upon tap. (Event object is available as `$event`)
 *
 * @example
    <example module="ngClickExample" deps="angular-touch.js">
      <file name="index.html">
        <button ng-click="count = count + 1" ng-init="count=0">
          Increment
        </button>
        count: {{ count }}
      </file>
      <file name="script.js">
        angular.module('ngClickExample', ['ngTouch']);
      </file>
    </example>
 */
ngTouch.config(['$provide', function($provide) {
  $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
    // drop the default ngClick directive
    $delegate.shift();
    return $delegate;
  }]);
}]);

// redefine the ngClick directive
ngTouch.directive('ngClick', ['$parse',
  function($parse) {
    // Actual linking function.
    return function(scope, element, attr) {
      // parse the function to be executed on click
      var fn = $parse(attr.ngClick);
      // attach the modified fastclick handler to this element (needs the real domNode)
      var fastclickableElement = FastClick.attach(element[0]);
      // attach a specific class to the clickable element indicating that it is handled via fastclick
      // this needs to be pushed to the end of the call stack, else some browsers will fail adding the class
      window.setTimeout(function() {
        element.addClass('fastclickable');
      });
      // listen to the click events (fired by Fastclick or per browser default)
      // and execute the action accordingly
      element.on('click', function(event) {
        scope.$apply(function() {
          fn(scope, {$event:event});
        });

        // support restore-after attribute, which resets the state of the active element
        // after the given timespan (in ms)
        if (angular.isDefined(attr.restoreAfter)) {
          var delay = parseInt(attr.restoreAfter);
          // execute the reset function after the given delay
          // make sure to bind the scope properly to the fastclickable elememnt (else IE9 executes the function in
          // the window' scope)
          window.setTimeout(fastclickableElement.reset.bind(fastclickableElement), delay);
        }
      });

      // watch the attribute cancelTap's value for changes --> as soon as the attribute is set to true,
      // reset the elements state immediately
      if (angular.isDefined(attr.cancelTap)) {
        var unwatchCancelTap = scope.$watch(attr.cancelTap, function(newVal) {
          if (newVal) {
            fastclickableElement.reset();
          }
        });
        // also make sure to remove the watcher when the scope is destroyed
        scope.$on('$destroy', function() {
          unwatchCancelTap();
        });
      }
    };
  }]
);

})(window, window.angular);
<commit_msg>removed timeout when adding the 'fastclickable' class and safe checking for fastclickableElement<commit_after>
(function(window, angular, undefined) {'use strict';
// define ngTouch module
/* global -ngTouch */
var ngTouch = angular.module('ngTouch', []);

/**
 * @ngdoc directive
 * @name ngClick
 *
 * @description
 * A more powerful replacement for the default ngClick designed to be used on touchscreen
 * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
 * the click event. This version handles them immediately, and then prevents the
 * following click event from propagating.
 *
 * Requires the {@link ngTouch `ngTouch`} module to be installed.
 *
 * This directive can fall back to using an ordinary click event, and so works on desktop
 * browsers as well as mobile.
 *
 * This directive also sets the CSS class `ng-click-active` while the element is being held
 * down (by a mouse click or touch) so you can restyle the depressed element if you wish.
 *
 * @element ANY
 * @param {expression} ngClick {@link guide/expression Expression} to evaluate
 * upon tap. (Event object is available as `$event`)
 *
 * @example
    <example module="ngClickExample" deps="angular-touch.js">
      <file name="index.html">
        <button ng-click="count = count + 1" ng-init="count=0">
          Increment
        </button>
        count: {{ count }}
      </file>
      <file name="script.js">
        angular.module('ngClickExample', ['ngTouch']);
      </file>
    </example>
 */
ngTouch.config(['$provide', function($provide) {
  $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
    // drop the default ngClick directive
    $delegate.shift();
    return $delegate;
  }]);
}]);

// redefine the ngClick directive
ngTouch.directive('ngClick', ['$parse',
  function($parse) {
    // Actual linking function.
    return function(scope, element, attr) {
      // parse the function to be executed on click
      var fn = $parse(attr.ngClick);
      // attach the modified fastclick handler to this element (needs the real domNode)
      var fastclickableElement = FastClick.attach(element[0]);
      // attach a specific class to the clickable element indicating that it is handled via fastclick
      // this needs to be pushed to the end of the call stack, else some browsers will fail adding the class
      element.addClass('fastclickable');
      // listen to the click events (fired by Fastclick or per browser default)
      // and execute the action accordingly
      element.on('click', function(event) {
        scope.$apply(function() {
          fn(scope, {$event:event});
        });

        // support restore-after attribute, which resets the state of the active element
        // after the given timespan (in ms)
        if (angular.isDefined(attr.restoreAfter) && typeof fastclickableElement !== 'undefined') {
          var delay = parseInt(attr.restoreAfter);
          // execute the reset function after the given delay
          // make sure to bind the scope properly to the fastclickable elememnt (else IE9 executes the function in
          // the window' scope)
          window.setTimeout(fastclickableElement.reset.bind(fastclickableElement), delay);
        }
      });

      // watch the attribute cancelTap's value for changes --> as soon as the attribute is set to true,
      // reset the elements state immediately
      if (angular.isDefined(attr.cancelTap)) {
        var unwatchCancelTap = scope.$watch(attr.cancelTap, function(newVal) {
          if (newVal) {
            fastclickableElement.reset();
          }
        });
        // also make sure to remove the watcher when the scope is destroyed
        scope.$on('$destroy', function() {
          unwatchCancelTap();
        });
      }
    };
  }]
);

})(window, window.angular);
<|endoftext|>


<commit_before>import express from 'express'
import path from 'path'
import favicon from 'serve-favicon'
import logger from 'morgan'
import cookieParser from 'cookie-parser'
import bodyParser from 'body-parser'
import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'

import router from './router'
import admin from './admin'
import config from '../../webpack.config'

const app = express()

// view engine setup
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')

// uncomment after placing your favicon in /public
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
app.use(logger('dev'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser('dexiao'))
app.use(express.static(path.join(__dirname, 'public')))

// const compiler = webpack(config)

// app.use(webpackDevMiddleware(compiler, {
//   publicPath: config.output.publicPath,
//   stats: { colors: true }
// }))

// app.use(webpackHotMiddleware(compiler))

app.use('/', router)
app.use('/admin', admin)

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found')
  err.status = 404
  next(err)
})

// error handler
// will print stacktrace
app.use(function(err, req, res, next) {
  res.status(err.status || 500)
  res.render('error', {
    message: err.message,
    error: err
  })
})

app.listen(4000)

export default app
<commit_msg>打开webpack<commit_after>import express from 'express'
import path from 'path'
import favicon from 'serve-favicon'
import logger from 'morgan'
import cookieParser from 'cookie-parser'
import bodyParser from 'body-parser'
import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'

import router from './router'
import admin from './admin'
import config from '../../webpack.config'

const app = express()

// view engine setup
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')

// uncomment after placing your favicon in /public
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
app.use(logger('dev'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser('dexiao'))
app.use(express.static(path.join(__dirname, 'public')))

const compiler = webpack(config)

app.use(webpackDevMiddleware(compiler, {
  publicPath: config.output.publicPath,
  stats: { colors: true }
}))

app.use(webpackHotMiddleware(compiler))

app.use('/', router)
app.use('/admin', admin)

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found')
  err.status = 404
  next(err)
})

// error handler
// will print stacktrace
app.use(function(err, req, res, next) {
  res.status(err.status || 500)
  res.render('error', {
    message: err.message,
    error: err
  })
})

app.listen(4000)

export default app
<|endoftext|>


<commit_before>#ifndef __browser_dom_DomEvent__
#define __browser_dom_DomEvent__

#ifdef HIDDEN
/**
 * Some code in this file is based on Mootools.net and adapted to the
 * architecture of Bootstrap, with added changes in design and architecture
 * where deemeded necessary.
 * See http://www.bootstrap-js.net/wiki/MootoolsDifferences
 */
#endif

#include "DomElement.js"

////////////////////////////////////////////////////////////////////////////////
// DomEvent

// Name it DomEvent instead of Event, as Event is a native prototype.
DomEvent = Base.extend(new function() {
	// MACIE does not accept numbers for keys, so use strings:
	var keys = {
		 '8': 'backspace',
		'13': 'enter',
		'27': 'esc',
		'32': 'space',
		'37': 'left',
		'38': 'up',
		'39': 'right',
		'40': 'down',
		'46': 'delete'
	};

	function hover(name, type) {
		return {
			type: type,
			listener: function(event) {
				if (event.relatedTarget != this && !this.hasChild(event.relatedTarget))
					this.fireEvent(name, event);
			}
		}
	}

	return {
		initialize: function(event) {
			this.event = event = event || window.event;
			this.type = event.type;
			this.target = DomElement.get(event.target || event.srcElement);
			if (this.target.nodeType == 3)
				this.target = this.target.getParent(); // Safari
			this.shift = event.shiftKey;
			this.control = event.ctrlKey;
			this.alt = event.altKey;
			this.meta = event.metaKey;
			if (/^(mousewheel|DOMMouseScroll)$/.test(this.type)) {
				this.wheel = event.wheelDelta ?
					event.wheelDelta / (window.opera ? -120 : 120) :
					- (event.detail || 0) / 3;
			} else if (/^key/.test(this.type)) {
				this.code = event.which || event.keyCode;
				this.key = keys[this.code] || String.fromCharCode(this.code).toLowerCase();
			} else if (/^mouse|^click$/.test(this.type)) {
				this.page = {
					x: event.pageX || event.clientX + document.documentElement.scrollLeft,
					y: event.pageY || event.clientY + document.documentElement.scrollTop
				};
				this.client = {
					x: event.pageX ? event.pageX - window.pageXOffset : event.clientX,
					y: event.pageY ? event.pageY - window.pageYOffset : event.clientY
				};
				// TODO: Calculate only if Dimension.js is defined! Add conditional macro?
				var offset = this.target.getOffset();
				this.offset = {
					x: this.page.x - offset.x,
					y: this.page.y - offset.y
				}
				this.rightClick = event.which == 3 || event.button == 2;
				if (/^mouse(over|out)$/.test(this.type))
					this.relatedTarget = DomElement.get(event.relatedTarget ||
						this.type == 'mouseout' ? event.toElement : event.fromElement);
			}
		},

		stop: function() {
			this.stopPropagation();
			this.preventDefault();
			return this;
		},

		stopPropagation: function() {
			if (this.event.stopPropagation) this.event.stopPropagation();
			else this.event.cancelBubble = true;
			// Needed for dragging
			this.stopped = true;
			return this;
		},

		preventDefault: function() {
			if (this.event.preventDefault) this.event.preventDefault();
			else this.event.returnValue = false;
			return this;
		},

		statics: {
			events: new Hash({
				mouseenter: hover('mouseenter', 'mouseover'),
				mouseleave: hover('mouseleave', 'mouseout'),
				mousewheel: { type: Browser.GECKO ? 'DOMMouseScroll' : 'mousewheel' },
				domready: function(func) { // Only used by Window.
					if (window.loaded) func.call(this);
					else if (!this.domReady) {
						// Only install it once, since fireEvent calls all the
						// handlers.
						this.domReady = true;
						var domReady = function() {
							if (this.loaded) return;
							this.loaded = true;
							if (this.timer) this.timer = this.timer.clear();
							this.fireEvent('domready');
						}.bind(this);
						if (document.readyState && (Browser.WEBKIT || Browser.MACIE)) { // Safari and Konqueror
							this.timer = (function() {
								if (/^(loaded|complete)$/.test(document.readyState)) domReady();
							}).periodic(50);
						} else if (document.readyState && Browser.IE) { // IE
							document.write('<script id=ie_ready defer src="'
								+ (window.location.protocol == 'https:' ? '://0' : 'javascript:void(0)')
								+ '"><\/script>');
							document.getElementById('ie_ready').onreadystatechange = function() {
								if (window.readyState == 'complete') domReady();
							};
						} else { // Others
							Window.addEvent('load', domReady);
							Document.addEvent('DOMContentLoaded', domReady);
						}
					}
				}
			}),

			add: function(events) {
				this.events.merge(events);
			}
		}
	};
});

DomElement.inject({
	addEvent: function(type, func) {
		this.events = this.events || {};
		var entries = this.events[type] = this.events[type] || [];
		if (func && !entries.find(function(entry) { return entry.func == func })) {
			// See if we have a pseudo event here.
			var listener = func, name = type, pseudo = DomEvent.events[type];
			if (pseudo) {
				if (typeof pseudo == 'function') pseudo = pseudo.call(this, func);
				listener = pseudo && pseudo.listener || listener;
				name = pseudo && pseudo.type;
			}
			// Check if the function takes a parameter. If so, it must
			// want an event. Wrap it so it recieves a wrapped event, and
			// bind it to that at the same time, as on PC IE, event listeners
			// are not called bound to their objects.
			var that = this, bound = (listener.parameters().length == 0)
				? listener.bind(this)
				: function(event) {
					event = new DomEvent(event);
					if (listener.call(that, event) === false)
						event.stop();
				};
			if (name) {
				if (this.$.addEventListener) {
					this.$.addEventListener(name, bound, false);
				} else if (this.$.attachEvent) {
					this.$.attachEvent('on' + name, bound);
#ifdef BROWSER_LEGACY
				} else {
					// Simulate multiple callbacks, with support for
					// stopPropagation and preventDefault
					this.$['on' + name] = function(event) {
						entries.each(function(entry) {
							entry.bound(event);
							if (event.event.cancelBubble) throw Base.stop;
						});
						// Passing "this" for bind above breaks throw Base.stop
						// on MACIE.
						// The reason is maybe that this is a native element?
						return event.event.returnValue;
					};
#endif // !BROWSER_LEGACY
				}
			}
			// Func is the one to be called through fireEvent. see dragstart
			// Also store a refrence to name here, as this might have changed too.
			entries.push({ func: func, name: name, bound: bound });
		}
		return this;
	},

	removeEvent: function(type, func) {
		var entries = (this.events || {})[type], entry;
		if (func && entries) {
#ifdef BROWSER_LEGACY
			// When shutting down, added functions seem to disappear here on
			// Mac IE. Fix it the easy way.
			// TODO: Check if still needed?
			entries = this.events[type] = Array.create(entries);
#endif // !BROWSER_LEGACY
			if (entry = entries.remove(function(entry) { return entry.func == func })) {
				var name = entry.name, pseudo = DomEvent.events[type];
				if (pseudo && pseudo.remove) pseudo.remove.call(this, func);
				if (name) {
					if (this.$.removeEventListener) {
						this.$.removeEventListener(name, entry.bound, false);
					} else if (this.$.detachEvent) {
						this.$.detachEvent('on' + name, entry.bound);
#ifdef BROWSER_LEGACY
					} else if (!entries.length) {
						this.$['on' + name] = null;
#endif // !BROWSER_LEGACY
					}
				}
			}
		}
		return this;
	},

	addEvents: function(events) {
		return Base.each(events || [], function(fn, type) {
			this.addEvent(type, fn);
		}, this);
	},

	removeEvents: function(type) {
		if (this.events) {
			if (type) {
				(this.events[type] || []).each(function(fn) {
					this.removeEvent(type, fn);
				}, this);
				delete this.events[type];
			} else {
				Base.each(this.events, function(ev, type) {
					this.removeEvents(type);
				}, this);
				this.events = null;
			}
		}
		return this;
	},

	fireEvent: function(type, event) {
		var entries = (this.events || {})[type];
		if (entries) {
			// Make sure we pass already wrapped events through
			if (event) event = event.event ? event : new DomEvent(event);
			entries.each(function(entry) {
				entry.func.call(this, event);
			}, this);
		}
		// Return true if event was fired, false otherwise
		return !!entries;
	},

	dispose: function() {
		this.removeEvents();
	}
});

#endif // __browser_dom_DomEvent__
<commit_msg>Fixed domready bug on IE<commit_after>#ifndef __browser_dom_DomEvent__
#define __browser_dom_DomEvent__

#ifdef HIDDEN
/**
 * Some code in this file is based on Mootools.net and adapted to the
 * architecture of Bootstrap, with added changes in design and architecture
 * where deemeded necessary.
 * See http://www.bootstrap-js.net/wiki/MootoolsDifferences
 */
#endif

#include "DomElement.js"

////////////////////////////////////////////////////////////////////////////////
// DomEvent

// Name it DomEvent instead of Event, as Event is a native prototype.
DomEvent = Base.extend(new function() {
	// MACIE does not accept numbers for keys, so use strings:
	var keys = {
		 '8': 'backspace',
		'13': 'enter',
		'27': 'esc',
		'32': 'space',
		'37': 'left',
		'38': 'up',
		'39': 'right',
		'40': 'down',
		'46': 'delete'
	};

	function hover(name, type) {
		return {
			type: type,
			listener: function(event) {
				if (event.relatedTarget != this && !this.hasChild(event.relatedTarget))
					this.fireEvent(name, event);
			}
		}
	}

	return {
		initialize: function(event) {
			this.event = event = event || window.event;
			this.type = event.type;
			this.target = DomElement.get(event.target || event.srcElement);
			if (this.target.nodeType == 3)
				this.target = this.target.getParent(); // Safari
			this.shift = event.shiftKey;
			this.control = event.ctrlKey;
			this.alt = event.altKey;
			this.meta = event.metaKey;
			if (/^(mousewheel|DOMMouseScroll)$/.test(this.type)) {
				this.wheel = event.wheelDelta ?
					event.wheelDelta / (window.opera ? -120 : 120) :
					- (event.detail || 0) / 3;
			} else if (/^key/.test(this.type)) {
				this.code = event.which || event.keyCode;
				this.key = keys[this.code] || String.fromCharCode(this.code).toLowerCase();
			} else if (/^mouse|^click$/.test(this.type)) {
				this.page = {
					x: event.pageX || event.clientX + document.documentElement.scrollLeft,
					y: event.pageY || event.clientY + document.documentElement.scrollTop
				};
				this.client = {
					x: event.pageX ? event.pageX - window.pageXOffset : event.clientX,
					y: event.pageY ? event.pageY - window.pageYOffset : event.clientY
				};
				// TODO: Calculate only if Dimension.js is defined! Add conditional macro?
				var offset = this.target.getOffset();
				this.offset = {
					x: this.page.x - offset.x,
					y: this.page.y - offset.y
				}
				this.rightClick = event.which == 3 || event.button == 2;
				if (/^mouse(over|out)$/.test(this.type))
					this.relatedTarget = DomElement.get(event.relatedTarget ||
						this.type == 'mouseout' ? event.toElement : event.fromElement);
			}
		},

		stop: function() {
			this.stopPropagation();
			this.preventDefault();
			return this;
		},

		stopPropagation: function() {
			if (this.event.stopPropagation) this.event.stopPropagation();
			else this.event.cancelBubble = true;
			// Needed for dragging
			this.stopped = true;
			return this;
		},

		preventDefault: function() {
			if (this.event.preventDefault) this.event.preventDefault();
			else this.event.returnValue = false;
			return this;
		},

		statics: {
			events: new Hash({
				mouseenter: hover('mouseenter', 'mouseover'),

				mouseleave: hover('mouseleave', 'mouseout'),

				mousewheel: { type: Browser.GECKO ? 'DOMMouseScroll' : 'mousewheel' },

				domready: function(func) { // Only used by Window.
					if (this.loaded) func.call(this);
					else if (!this.domReady) {
						// Only install it once, since fireEvent calls all the
						// handlers.
						this.domReady = true;
						var domReady = function() {
							if (this.loaded) return;
							this.loaded = true;
							if (this.timer) this.timer = this.timer.clear();
							this.fireEvent('domready');
						}.bind(this);
						if (document.readyState && (Browser.WEBKIT || Browser.MACIE)) { // Safari and Konqueror
							this.timer = (function() {
								if (/^(loaded|complete)$/.test(document.readyState)) domReady();
							}).periodic(50);
						} else if (document.readyState && Browser.IE) { // IE
							document.write('<script id=ie_domready defer src="'
								+ (window.location.protocol == 'https:' ? '://0' : 'javascript:void(0)')
								+ '"><\/script>');
							$('ie_domready').addEvent('readystatechange', function() {
								if (this.$.readyState == 'complete') domReady();
							});
						} else { // Others
							Window.addEvent('load', domReady);
							Document.addEvent('DOMContentLoaded', domReady);
						}
					}
				}
			}),

			add: function(events) {
				this.events.merge(events);
			}
		}
	};
});

DomElement.inject({
	addEvent: function(type, func) {
		this.events = this.events || {};
		var entries = this.events[type] = this.events[type] || [];
		if (func && !entries.find(function(entry) { return entry.func == func })) {
			// See if we have a pseudo event here.
			var listener = func, name = type, pseudo = DomEvent.events[type];
			if (pseudo) {
				if (typeof pseudo == 'function') pseudo = pseudo.call(this, func);
				listener = pseudo && pseudo.listener || listener;
				name = pseudo && pseudo.type;
			}
			// Check if the function takes a parameter. If so, it must
			// want an event. Wrap it so it recieves a wrapped event, and
			// bind it to that at the same time, as on PC IE, event listeners
			// are not called bound to their objects.
			var that = this, bound = listener.parameters().length == 0
				? listener.bind(this)
				: function(event) {
					event = new DomEvent(event);
					if (listener.call(that, event) === false)
						event.stop();
				};
			if (name) {
				if (this.$.addEventListener) {
					this.$.addEventListener(name, bound, false);
				} else if (this.$.attachEvent) {
					this.$.attachEvent('on' + name, bound);
#ifdef BROWSER_LEGACY
				} else {
					// Simulate multiple callbacks, with support for
					// stopPropagation and preventDefault
					this.$['on' + name] = function(event) {
						entries.each(function(entry) {
							entry.bound(event);
							if (event.event.cancelBubble) throw Base.stop;
						});
						// Passing "this" for bind above breaks throw Base.stop
						// on MACIE.
						// The reason is maybe that this is a native element?
						return event.event.returnValue;
					};
#endif // !BROWSER_LEGACY
				}
			}
			// Func is the one to be called through fireEvent. see dragstart
			// Also store a refrence to name here, as this might have changed too.
			entries.push({ func: func, name: name, bound: bound });
		}
		return this;
	},

	removeEvent: function(type, func) {
		var entries = (this.events || {})[type], entry;
		if (func && entries) {
#ifdef BROWSER_LEGACY
			// When shutting down, added functions seem to disappear here on
			// Mac IE. Fix it the easy way.
			// TODO: Check if still needed?
			entries = this.events[type] = Array.create(entries);
#endif // !BROWSER_LEGACY
			if (entry = entries.remove(function(entry) { return entry.func == func })) {
				var name = entry.name, pseudo = DomEvent.events[type];
				if (pseudo && pseudo.remove) pseudo.remove.call(this, func);
				if (name) {
					if (this.$.removeEventListener) {
						this.$.removeEventListener(name, entry.bound, false);
					} else if (this.$.detachEvent) {
						this.$.detachEvent('on' + name, entry.bound);
#ifdef BROWSER_LEGACY
					} else if (!entries.length) {
						this.$['on' + name] = null;
#endif // !BROWSER_LEGACY
					}
				}
			}
		}
		return this;
	},

	addEvents: function(events) {
		return Base.each(events || [], function(fn, type) {
			this.addEvent(type, fn);
		}, this);
	},

	removeEvents: function(type) {
		if (this.events) {
			if (type) {
				(this.events[type] || []).each(function(fn) {
					this.removeEvent(type, fn);
				}, this);
				delete this.events[type];
			} else {
				Base.each(this.events, function(ev, type) {
					this.removeEvents(type);
				}, this);
				this.events = null;
			}
		}
		return this;
	},

	fireEvent: function(type, event) {
		var entries = (this.events || {})[type];
		if (entries) {
			// Make sure we pass already wrapped events through
			if (event) event = event.event ? event : new DomEvent(event);
			entries.each(function(entry) {
				entry.func.call(this, event);
			}, this);
		}
		// Return true if event was fired, false otherwise
		return !!entries;
	},

	dispose: function() {
		this.removeEvents();
	}
});

#endif // __browser_dom_DomEvent__
<|endoftext|>


<commit_before>/*
*	Solution to Euler Totient Extended
*
*	@author Lakha Singh
*/

// Exit
var exit = function( code ){
	code = code || '';
	console.error( code );
	process.exit('ERROR: ' + code);
};

// Validate Test case numbers, 1 >= T <= 128
var validateT = function( T ){
	if ( !(T >=1 && T <= 128) ){
		exit('Test-cases should be between 1 and 128');
	}
}

// Read User input
var readInput = function( callback ){
	// Set encoding
	process.stdin.setEncoding('utf8');

	// Denotes number of test cases
	var T;

	// user input
	var input = [];

	// to store intermediate values
	var helper;

	callback = callback || function(){};

	// Input handler
	var h = function( data ){
		if ( !T ){
			T = parseInt( data, 10 );
			validateT( T );
		}else{
			if ( T !== 0 ){
				helper = data.split(' ');
				input.push({
					N: parseInt( helper[0], 10 ),
					K: parseInt( helper[1], 10 )
				});
				T--;

				if ( T === 0){
					process.stdin.removeListener('data', h);
					callback( input );
				}
			}
		}
	};

	// Start Listening to user input
	process.stdin.addListener('data', h);
};

readInput(function( input ){
	console.log( input );

	exit();
});

<commit_msg>added factorize and isPrime functions base on trial dividion algo<commit_after>/*
*	Solution to Euler Totient Extended
*
*	@author Lakha Singh
*/

// Terminate program
var exit = function( code ){
	code = code || '';
	console.error( code );
	process.exit('ERROR: ' + code);
};

// Validate Test cases, 1 >= T <= 128
var validateT = function( T ){
	if ( !(T >=1 && T <= 128) ){
		exit('Test-cases should be between 1 and 128');
	}
}

// Read User input
var readInput = function( callback ){
	// Set encoding
	process.stdin.setEncoding('utf8');

	// Denotes number of test cases
	var T;

	// user input
	var input = [];

	// to store intermediate values
	var helper;

	callback = callback || function(){};

	// Input handler
	var h = function( data ){
		if ( !T ){
			T = parseInt( data, 10 );
			validateT( T );
		}else{
			if ( T !== 0 ){
				helper = data.split(' ');
				input.push({
					N: parseInt( helper[0], 10 ),
					K: parseInt( helper[1], 10 )
				});
				T--;

				if ( T === 0){
					process.stdin.removeListener('data', h);
					callback( input );
				}
			}
		}
	};

	// Start Listening to user input
	process.stdin.addListener('data', h);
};

// Factorize integer
var factorize = (function(){
	// To find if given number is prime
	var isPrime = function( num ){
		var limit = parseInt( Math.sqrt( num ), 10);

		var i = 2, k = 1;

		// As 2 and 3 cant be included in 6k+1, hence checking them seperately
		for ( ; i <= 3; i++ ){
			if ( num % i === 0 ){
				return false;
			}
		}

		// For optimzation, checking against only prime divisors
		while ( i <= limit ){
			for ( j = -1; j <= 1; j +=2 ){
				i = 6*k + j;

				if ( num % i === 0 ){
					return false;
				}
			}
			k++;
		}
		return true;
	};

	return function( num ){
		var limit = num/2;

		var i = 2, k = 1;

		// Calulated factors
		var factors = [];

		// For primr numbers, simply return the number
		if ( isPrime(num) ){
			return [ num ];
		}

		// For optimzation, checking against only prime divisors
		for ( ; i <= 3; i++ ){
			while( num % i === 0){
				factors.push( i );
				num /= i;
			}
		}

		while( num !== 1 ){
			for ( j = -1; j <= 1; j +=2 ){
				i = 6*k + j;

				while( num % i === 0){
					factors.push( i );
					num /= i;
				}
			}
			k++;
		}

		return factors;
	};
}());

// Start
readInput(function( input ){
	var i;
	// Execute all Test Cases
	for ( i = 0; i < input.length; i++ ){
		console.log( factorize( input[ i ].N ) );
	}

	// Terminate
	exit();
});

// Test 4566547864562
/*

		while( num !== 1 ){
						while( num % i === 0){
				factors.push( i );
				num /= i;
			}
			i++;
			*/

<|endoftext|>