<commit_before>if (typeof window !== "undefined") {
    // window is undefined in non-browser context
    window.CKEDITOR_BASEPATH = dojoConfig.app.pathPrefix+'vendor/ckeditor/ckeditor/';
}

define( [
    "dojo/_base/declare",
    "dojo/_base/lang",
    "dojo/_base/config",
    "dojo/_base/window",
    "dojo/topic",
    "dojo/query",
    "dijit/form/TextBox",
    "ckeditor/ckeditor",
    "../Factory",
    "../../../../locale/Dictionary",
    "../../../_include/_HelpMixin",
    "../../../_include/_TranslateMixin",
    "../../../_include/widget/MediaBrowserDlgWidget",
    "./_AttributeWidgetMixin",
    "dojo/text!./template/CKEditor.html"
],
function(
    declare,
    lang,
    config,
    win,
    topic,
    query,
    TextBox,
    CKEditor,
    ControlFactory,
    Dict,
    _HelpMixin,
    _TranslateMixin,
    MediaBrowserDlg,
    _AttributeWidgetMixin,
    template
) {
    return declare([TextBox, _HelpMixin, _TranslateMixin, _AttributeWidgetMixin], {

        templateString: template,
        intermediateChanges: true,
        inputType: null, // control description as string as used in Factory.getControlClass()
        entity: null,
        editorInstance: null,
        mediaBrowser: null,

        constructor: function(args) {
            declare.safeMixin(this, args);

            this.label = Dict.translate(this.name);
        },

        postCreate: function() {
            this.inherited(arguments);

            var pathPrefix = config.app.pathPrefix;
            var mediaBrowserRoute = pathPrefix+'media';
            var linkBrowserRoute = pathPrefix+'link';
            var mediaBaseHref = config.app.wcmfBaseHref;

            this.editorInstance = CKEDITOR.replace(this.textbox, {
                customConfig: pathPrefix+'js/config/ckeditor_config.js',
                filebrowserBrowseUrl: mediaBrowserRoute,
                filebrowserLinkBrowseUrl: linkBrowserRoute,
                baseHref: mediaBaseHref,
                toolbar: this.getToolbarName(),
                filebrowserWindowWidth: '800',
                filebrowserWindowHeight: '700',
                readOnly: this.disabled
            });

            // custom filebrowser dialog instantiation
            // @see https://github.com/simogeo/Filemanager/wiki/How-to-open-the-Filemanager-from-CKEditor-in-a-modal-window
            var browseDlg = null;
            CKEDITOR.on('dialogDefinition', function(event) {
                var editor = event.editor;
                var dialogDefinition = event.data.definition;
                var tabCount = dialogDefinition.contents.length;
                var cleanUpFunc = CKEDITOR.tools.addFunction(function () {
                    browseDlg.hide();
                });

                for (var i=0; i<tabCount; i++) {
                    var browseButton = dialogDefinition.contents[i].get('browse');
                    if (browseButton !== null) {
                        browseButton.hidden = false;
                        browseButton.onClick = function(dialog, i) {
                            editor._.filebrowserSe = this;
                            browseDlg = new MediaBrowserDlg({
                                url: this.filebrowser.url+'?CKEditorFuncNum='+
                                        CKEDITOR.instances[event.editor.name]._.filebrowserFn+
                                        '&CKEditorCleanUpFuncNum='+cleanUpFunc
                            });
                            browseDlg.show();
                        };
                    }
                }
            });

            this.own(
                topic.subscribe("entity-datachange", lang.hitch(this, function(data) {
                    if ((this.entity && this.entity.get('oid') === data.entity.get('oid')) &&
                            data.name === this.name) {
                        var newValue = this.sanitiseValue(data.newValue);
                        this.set("value", newValue);
                    }
                }))
            );
            this.editorInstance.on("instanceReady", lang.hitch(this, function() {
                this.editorInstance.on("change", lang.hitch(this, function() {
                    this.set("value", this.sanitiseValue(this.editorInstance.getData()));
                    // send change event
                    this.emit("change", this);
                }));
                // set padding on editor content
                var content = query("iframe", this.domNode)[0].contentWindow.document;
                win.withDoc(content, function() {
                    query(".cke_editable").style("padding", "5px");
                }, this);
                // fix edit state (editor instance is initially read only)
                this.set("disabled", this.disabled);
            }));
        },

        _setDisabledAttr: function(value) {
            if (this.editorInstance) {
                this.editorInstance.setReadOnly(value);
            }
        },

        _setValueAttr: function(value) {
            if (this.editorInstance && value != this.sanitiseValue(this.editorInstance.getData())) {
                this.editorInstance.setData(value);
            }
            this.inherited(arguments);
        },

        _getValueAttr: function() {
            if (this.editorInstance) {
                return this.sanitiseValue(this.editorInstance.getData());
            }
            else {
                return this.inherited(arguments);
            }
        },

        getToolbarName: function() {
            var options = ControlFactory.getOptions(this.inputType);
            return (options.toolbarSet) ? options.toolbarSet : "wcmf";
        },

        destroy: function() {
            this.editorInstance.removeAllListeners();
            this.inherited(arguments);
        },

        sanitiseValue: function(value) {
            return (typeof value === "string" || value instanceof String) ?
                value.trim() : value;
        }
    });
});<commit_msg>Fix exception when setting ckeditor read only<commit_after>if (typeof window !== "undefined") {
    // window is undefined in non-browser context
    window.CKEDITOR_BASEPATH = dojoConfig.app.pathPrefix+'vendor/ckeditor/ckeditor/';
}

define( [
    "dojo/_base/declare",
    "dojo/_base/lang",
    "dojo/_base/config",
    "dojo/_base/window",
    "dojo/topic",
    "dojo/query",
    "dijit/form/TextBox",
    "ckeditor/ckeditor",
    "../Factory",
    "../../../../locale/Dictionary",
    "../../../_include/_HelpMixin",
    "../../../_include/_TranslateMixin",
    "../../../_include/widget/MediaBrowserDlgWidget",
    "./_AttributeWidgetMixin",
    "dojo/text!./template/CKEditor.html"
],
function(
    declare,
    lang,
    config,
    win,
    topic,
    query,
    TextBox,
    CKEditor,
    ControlFactory,
    Dict,
    _HelpMixin,
    _TranslateMixin,
    MediaBrowserDlg,
    _AttributeWidgetMixin,
    template
) {
    return declare([TextBox, _HelpMixin, _TranslateMixin, _AttributeWidgetMixin], {

        templateString: template,
        intermediateChanges: true,
        inputType: null, // control description as string as used in Factory.getControlClass()
        entity: null,
        editorInstance: null,
        mediaBrowser: null,

        constructor: function(args) {
            declare.safeMixin(this, args);

            this.label = Dict.translate(this.name);
        },

        postCreate: function() {
            this.inherited(arguments);

            var pathPrefix = config.app.pathPrefix;
            var mediaBrowserRoute = pathPrefix+'media';
            var linkBrowserRoute = pathPrefix+'link';
            var mediaBaseHref = config.app.wcmfBaseHref;

            this.editorInstance = CKEDITOR.replace(this.textbox, {
                customConfig: pathPrefix+'js/config/ckeditor_config.js',
                filebrowserBrowseUrl: mediaBrowserRoute,
                filebrowserLinkBrowseUrl: linkBrowserRoute,
                baseHref: mediaBaseHref,
                toolbar: this.getToolbarName(),
                filebrowserWindowWidth: '800',
                filebrowserWindowHeight: '700',
                readOnly: this.disabled
            });

            // custom filebrowser dialog instantiation
            // @see https://github.com/simogeo/Filemanager/wiki/How-to-open-the-Filemanager-from-CKEditor-in-a-modal-window
            var browseDlg = null;
            CKEDITOR.on('dialogDefinition', function(event) {
                var editor = event.editor;
                var dialogDefinition = event.data.definition;
                var tabCount = dialogDefinition.contents.length;
                var cleanUpFunc = CKEDITOR.tools.addFunction(function () {
                    browseDlg.hide();
                });

                for (var i=0; i<tabCount; i++) {
                    var browseButton = dialogDefinition.contents[i].get('browse');
                    if (browseButton !== null) {
                        browseButton.hidden = false;
                        browseButton.onClick = function(dialog, i) {
                            editor._.filebrowserSe = this;
                            browseDlg = new MediaBrowserDlg({
                                url: this.filebrowser.url+'?CKEditorFuncNum='+
                                        CKEDITOR.instances[event.editor.name]._.filebrowserFn+
                                        '&CKEditorCleanUpFuncNum='+cleanUpFunc
                            });
                            browseDlg.show();
                        };
                    }
                }
            });

            this.own(
                topic.subscribe("entity-datachange", lang.hitch(this, function(data) {
                    if ((this.entity && this.entity.get('oid') === data.entity.get('oid')) &&
                            data.name === this.name) {
                        var newValue = this.sanitiseValue(data.newValue);
                        this.set("value", newValue);
                    }
                }))
            );
            this.editorInstance.on("instanceReady", lang.hitch(this, function() {
                this.editorInstance.on("change", lang.hitch(this, function() {
                    this.set("value", this.sanitiseValue(this.editorInstance.getData()));
                    // send change event
                    this.emit("change", this);
                }));
                // set padding on editor content
                var content = query("iframe", this.domNode)[0].contentWindow.document;
                win.withDoc(content, function() {
                    query(".cke_editable").style("padding", "5px");
                }, this);
                // fix edit state (editor instance is initially read only)
                this.set("disabled", this.disabled);
            }));
        },

        _setDisabledAttr: function(value) {
            if (this.editorInstance) {
                setTimeout(lang.hitch(this, function() {
                    try {
                        this.editorInstance.setReadOnly(value);
                    }
                    catch (ex) {}
                }), 100);
            }
        },

        _setValueAttr: function(value) {
            if (this.editorInstance && value != this.sanitiseValue(this.editorInstance.getData())) {
                this.editorInstance.setData(value);
            }
            this.inherited(arguments);
        },

        _getValueAttr: function() {
            if (this.editorInstance) {
                return this.sanitiseValue(this.editorInstance.getData());
            }
            else {
                return this.inherited(arguments);
            }
        },

        getToolbarName: function() {
            var options = ControlFactory.getOptions(this.inputType);
            return (options.toolbarSet) ? options.toolbarSet : "wcmf";
        },

        destroy: function() {
            this.editorInstance.removeAllListeners();
            this.inherited(arguments);
        },

        sanitiseValue: function(value) {
            return (typeof value === "string" || value instanceof String) ?
                value.trim() : value;
        }
    });
});<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'
import classnames from 'classnames'

import './scss/block-miniature.scss'

const isActive = (selected, layout) => JSON.stringify(selected) === JSON.stringify(layout)
const BlockMiniature = ({ selectedLayout, layout, onClick }) => (

        {layout.map((size, index) => (
          <div
            key={index}
            className={classnames(`lines clearfix px1 col col-${size.lg_size}`)}
          >

        ))}

)

BlockMiniature.propTypes = {
  selectedLayout: PropTypes.array.isRequired,
  layout: PropTypes.array.isRequired,
  onClick: PropTypes.func.isRequired
}

export default BlockMiniature
<commit_msg>Fix make BlockMiniature in 4 columns. #212<commit_after>import React, { PropTypes } from 'react'
import classnames from 'classnames'

import './scss/block-miniature.scss'

const isActive = (selected, layout) => JSON.stringify(selected) === JSON.stringify(layout)
const BlockMiniature = ({ selectedLayout, layout, onClick }) => (

        {layout.map((size, index) => (
          <div
            key={index}
            className={classnames(`lines clearfix px1 col col-${size.lg_size}`)}
          >

        ))}

)

BlockMiniature.propTypes = {
  selectedLayout: PropTypes.array.isRequired,
  layout: PropTypes.array.isRequired,
  onClick: PropTypes.func.isRequired
}

export default BlockMiniature
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var $ = require('jquery');
var heartbeats = require('heartbeats');
var Backbone = require('backbone');
var dispatcher = require('../../../dispatchers/admin-dispatcher');
var payment = require('../config.json');
var session = require('../../../modules/session/models/session');

var pollingHeart = new heartbeats.Heart(5000);

Backbone.$ = $;

var Payment = Backbone.Model.extend({
  defaults: {
    // id: 0,
    // createdAt: 0,
    // updatedAt: 0,
    // uid: 0,
    // client_resource_id: 0,
    to_address_id: 0,
    from_address_id: 0,
    to_amount: 0.0,
    to_currency: '',
    to_issuer: '',
    from_amount: 0.0,
    from_currency: '',
    from_issuer: '',
    transaction_state: '',
    transaction_hash: '',
    data: '',
    state: '',
    external_transaction_id: 0
  },

  requiredAttrs: {
    to_address_id: {
      type: 'number' // int
    },
    from_address_id: {
      type: 'number' // int
    },
    to_amount: {
      type: 'number' // decimal
    },
    to_currency: {
      type: 'string',
      minLength: 1
    },
    to_issuer: {
      type: 'string',
      minLength: 1 // figure out what this is!!!!!
    },
    from_amount: {
      type: 'number' // decimal
    },
    from_currency: {
      type: 'string',
      minLength: 1
    },
    from_issuer: {
      type: 'string',
      minLength: 1 // figure out what this is!!!!!
    }
  },

  url: 'http://localhost:5000/v1/ripple_transactions',

  initialize: function() {
    _.bindAll(this);

    dispatcher.register(this.dispatchCallback);
  },

  dispatchCallback: function(payload) {
    var handleAction = {};

    if (!_.isUndefined(handleAction[payload.actionType])) {
      handleAction[payload.actionType](payload.data);
    }
  },

  validationErrors: [],

  handleObject: function(attr, minLength) {
    if (attr === null) {
      return false;
    }

    if (Array.isArray(attr)) {
      return attr.length >= minLength;
    }

    return Object.keys(attr).length >= minLength;
  },

  handleString: function(attr, minLength) {
    return !!attr && attr.length >= minLength;
  },

  testValid: function(attr, requirements) {
    var attribute = this.get(attr);
    var testValid = {
      object: this.handleObject,
      string: this.handleString,
    };
    var isDefined = !_.isUndefined(attribute);
    var type = requirements.type === 'array' ? 'object' : requirements.type;
    var isValid = typeof attribute === type;

    if (isValid && !_.isUndefined(testValid[typeof attribute])) {
      isValid = testValid[typeof attribute](attribute, requirements.minLength);
    }

    // custom error messaging
    if (!isDefined) {
      this.validationErrors.push('"' + attr + '" of payment is undefined');
    } else if (!isValid) {
      this.validationErrors.push('"' + attr + '" of payment is invalid');
    }

    //return isDefined && isValid;
  },

  validate: function() {
    var isValid = true,
        _this = this;

    _.each(this.requiredAttrs, function(requirements, requiredAttr) {
      if (!_this.testValid(requiredAttr, requirements)) {
        isValid = false;
      }
    });

    if (!isValid) {
      //return 'There is an error';
    }
  },

  parse: function(data, options) {
    if (options.collection) {
      return data;
    }
    return data.ripple_transaction;
  },

  retry: function() {
    //
  },

  handleSuccess: function(model) {
    if (model.get('state') === 'succeeded' || model.get('state') === 'failed') {
      console.log('polling', model.get('state'));
      pollingHeart.clearEvents();
    }
  },

  pollStatusHelper: function() {
    console.log('polling payment');
    this.fetch({
      url: this.url + '/' + this.get('id'),
      dataType: 'json',
      contentType: 'application/json',
      headers: {
        Authorization: session.get('credentials')
      },
      success: this.handleSuccess
    });
  },

  pollStatus: function() {
    pollingHeart.onBeat(1, this.pollStatusHelper);
  }
});

module.exports = Payment;
<commit_msg>[TASK] Set polling limit for payment status poll<commit_after>'use strict';

var _ = require('lodash');
var $ = require('jquery');
var heartbeats = require('heartbeats');
var Backbone = require('backbone');
var dispatcher = require('../../../dispatchers/admin-dispatcher');
var payment = require('../config.json');
var session = require('../../../modules/session/models/session');

var pollingHeart = new heartbeats.Heart(5000);

Backbone.$ = $;

var Payment = Backbone.Model.extend({
  defaults: {
    // id: 0,
    // createdAt: 0,
    // updatedAt: 0,
    // uid: 0,
    // client_resource_id: 0,
    to_address_id: 0,
    from_address_id: 0,
    to_amount: 0.0,
    to_currency: '',
    to_issuer: '',
    from_amount: 0.0,
    from_currency: '',
    from_issuer: '',
    transaction_state: '',
    transaction_hash: '',
    data: '',
    state: '',
    external_transaction_id: 0
  },

  requiredAttrs: {
    to_address_id: {
      type: 'number' // int
    },
    from_address_id: {
      type: 'number' // int
    },
    to_amount: {
      type: 'number' // decimal
    },
    to_currency: {
      type: 'string',
      minLength: 1
    },
    to_issuer: {
      type: 'string',
      minLength: 1 // figure out what this is!!!!!
    },
    from_amount: {
      type: 'number' // decimal
    },
    from_currency: {
      type: 'string',
      minLength: 1
    },
    from_issuer: {
      type: 'string',
      minLength: 1 // figure out what this is!!!!!
    }
  },

  url: 'http://localhost:5000/v1/ripple_transactions',

  initialize: function() {
    _.bindAll(this);

    dispatcher.register(this.dispatchCallback);
  },

  dispatchCallback: function(payload) {
    var handleAction = {};

    if (!_.isUndefined(handleAction[payload.actionType])) {
      handleAction[payload.actionType](payload.data);
    }
  },

  validationErrors: [],

  handleObject: function(attr, minLength) {
    if (attr === null) {
      return false;
    }

    if (Array.isArray(attr)) {
      return attr.length >= minLength;
    }

    return Object.keys(attr).length >= minLength;
  },

  handleString: function(attr, minLength) {
    return !!attr && attr.length >= minLength;
  },

  testValid: function(attr, requirements) {
    var attribute = this.get(attr);
    var testValid = {
      object: this.handleObject,
      string: this.handleString,
    };
    var isDefined = !_.isUndefined(attribute);
    var type = requirements.type === 'array' ? 'object' : requirements.type;
    var isValid = typeof attribute === type;

    if (isValid && !_.isUndefined(testValid[typeof attribute])) {
      isValid = testValid[typeof attribute](attribute, requirements.minLength);
    }

    // custom error messaging
    if (!isDefined) {
      this.validationErrors.push('"' + attr + '" of payment is undefined');
    } else if (!isValid) {
      this.validationErrors.push('"' + attr + '" of payment is invalid');
    }

    //return isDefined && isValid;
  },

  validate: function() {
    var isValid = true,
        _this = this;

    _.each(this.requiredAttrs, function(requirements, requiredAttr) {
      if (!_this.testValid(requiredAttr, requirements)) {
        isValid = false;
      }
    });

    if (!isValid) {
      //return 'There is an error';
    }
  },

  parse: function(data, options) {
    if (options.collection) {
      return data;
    }
    return data.ripple_transaction;
  },

  retry: function() {
    //
  },

  handleSuccess: function(model) {
    if (model.get('state') === 'succeeded' || model.get('state') === 'failed') {
      console.log('polling', model.get('state'));
      pollingHeart.clearEvents();
    }
  },

  pollStatusHelper: function() {
    console.log('polling payment');
    this.fetch({
      url: this.url + '/' + this.get('id'),
      dataType: 'json',
      contentType: 'application/json',
      headers: {
        Authorization: session.get('credentials')
      },
      success: this.handleSuccess
    });
  },

  pollStatus: function() {
    pollingHeart.onBeat(1, this.pollStatusHelper);
    pollingHeart.onBeat(10, pollingHeart.clearEvents);
  }
});

module.exports = Payment;
<|endoftext|>


<commit_before>/** OnboardingProgress view model.
    It tracks the onboarding information and methods
    to update views to that state
**/
var Model = require('../models/Model');
var ko = require('knockout');

/**
 * Definition of steps, in strict order, with settings from available:
 * - serviceProfessionalOnly:boolean Whether is display to professional users only
 * - jobTitleSpecific:boolean Whether the step needs a jobTitleID in order to work,
 * and accepts that as the first URL segment
 */
var STEPS = {
    welcome: {},
    publicContactInfo: {},
    addJobTitle: {
        serviceProfessionalOnly: true
    },
    serviceAddresses: {
        serviceProfessionalOnly: true,
        jobTitleSpecific: true
    }
};

var getProfessionalSteps = function() {
    return Object.keys(STEPS)
    .map(function(stepName) {
        return stepName;
    });
};
var getClientSteps = function() {
    return Object.keys(STEPS)
    .filter(function(stepName) {
        return !STEPS[stepName].serviceProfessionalOnly;
    })
    .map(function(stepName) {
        return stepName;
    });
};

function OnboardingProgress(values) {
    var stepNumberFinished = -1;

    Model(this);

    this.model.defProperties({
        isServiceProfessional: false,
        stepNumber: stepNumberFinished,
        // Let's set a job title to pass in to jobTitleSpecific steps as URL segment
        selectedJobTitleID: null
    }, values);

    this.stepNames = ko.pureComputed(function() {
        return this.isServiceProfessional() ? getProfessionalSteps() : getClientSteps();
    }, this);

    this.totalSteps = ko.pureComputed(function() {
        // 'Zero' step is a welcome, not accounted:
        return this.stepNames().length - 1;
    }, this);

    this.incrementStep = function() {
        var currentStep = this.stepNumber();

        if(this.isFinished()) {
            return;
        }
        else if(currentStep == (this.stepNames().length - 1)){
            this.finish();
        }
        else {
            this.stepNumber(currentStep + 1);
        }
    };

    this.stepAfter = function(stepName) {
        var nextStep = new OnboardingProgress({
            selectedJobTitleID: this.selectedJobTitleID(),
            isServiceProfessional: this.isServiceProfessional()
        });
        nextStep.setStepByName(stepName);
        nextStep.incrementStep();

        return nextStep;
    };

    this.stepName = ko.pureComputed(function() {
        return this.stepNames()[this.stepNumber()] || null;
    }, this);

    this.stepUrl = ko.pureComputed(function() {
        var name = this.stepName();
        if (!name) return null;
        var url = '/' + name;

        // Check if there is need for a jobTitleID in the URL
        var def = STEPS[name];
        var jobID = this.selectedJobTitleID();
        if (jobID && def && def.jobTitleSpecific) {
            url += '/' + jobID;
        }

        return url;
    }, this);

    this.progressText = ko.pureComputed(function() {
        // TODO L18N
        return this.stepNumber() + ' of ' + this.totalSteps();
    }, this);

    this.inProgress = ko.pureComputed(function() {
        return !this.isFinished();
    }, this);

    this.finish = function() {
        this.stepNumber(stepNumberFinished);
    };

    this.isFinished = ko.pureComputed(function() {
        return this.stepNumber() === stepNumberFinished;
    }, this);
}

module.exports = OnboardingProgress;

OnboardingProgress.prototype.setStepByName = function setStepByName(name) {
    var stepIndex = this.stepNames().indexOf(name);
    if (stepIndex > -1) {
        this.stepNumber(stepIndex);
        return true;
    }
    return false;
};
<commit_msg>Added new last onboarding steps, depending on user, and caching the list for each user type #584<commit_after>/** OnboardingProgress view model.
    It tracks the onboarding information and methods
    to update views to that state
**/
var Model = require('../models/Model');
var ko = require('knockout');

/**
 * Definition of steps, in strict order, with settings from available:
 * - serviceProfessionalOnly:boolean Whether is display to professional users only
 * - jobTitleSpecific:boolean Whether the step needs a jobTitleID in order to work,
 * and accepts that as the first URL segment
 */
var STEPS = {
    welcome: {},
    publicContactInfo: {},
    addJobTitle: {
        serviceProfessionalOnly: true
    },
    serviceAddresses: {
        serviceProfessionalOnly: true,
        jobTitleSpecific: true
    },
    listingEditor: {
        serviceProfessionalOnly: true,
        jobTitleSpecific: true
    },
    home: {
        clientOnly: true
    }
};

var PROFESSIONAL_STEPS = Object.keys(STEPS)
.filter(function(stepName) {
    return !STEPS[stepName].clientOnly;
})
.map(function(stepName) {
    return stepName;
});
var CLIENT_STEPS = Object.keys(STEPS)
.filter(function(stepName) {
    return !STEPS[stepName].serviceProfessionalOnly;
})
.map(function(stepName) {
    return stepName;
});

function OnboardingProgress(values) {
    var stepNumberFinished = -1;

    Model(this);

    this.model.defProperties({
        isServiceProfessional: false,
        stepNumber: stepNumberFinished,
        // Let's set a job title to pass in to jobTitleSpecific steps as URL segment
        selectedJobTitleID: null
    }, values);

    this.stepNames = ko.pureComputed(function() {
        return this.isServiceProfessional() ? PROFESSIONAL_STEPS : CLIENT_STEPS;
    }, this);

    this.totalSteps = ko.pureComputed(function() {
        // 'Zero' step is a welcome, not accounted:
        return this.stepNames().length - 1;
    }, this);

    this.incrementStep = function() {
        var currentStep = this.stepNumber();

        if(this.isFinished()) {
            return;
        }
        else if(currentStep == (this.stepNames().length - 1)){
            this.finish();
        }
        else {
            this.stepNumber(currentStep + 1);
        }
    };

    this.stepAfter = function(stepName) {
        var nextStep = new OnboardingProgress({
            selectedJobTitleID: this.selectedJobTitleID(),
            isServiceProfessional: this.isServiceProfessional()
        });
        nextStep.setStepByName(stepName);
        nextStep.incrementStep();

        return nextStep;
    };

    this.stepName = ko.pureComputed(function() {
        return this.stepNames()[this.stepNumber()] || null;
    }, this);

    this.stepUrl = ko.pureComputed(function() {
        var name = this.stepName();
        if (!name) return null;
        var url = '/' + name;

        // Check if there is need for a jobTitleID in the URL
        var def = STEPS[name];
        var jobID = this.selectedJobTitleID();
        if (jobID && def && def.jobTitleSpecific) {
            url += '/' + jobID;
        }

        return url;
    }, this);

    this.progressText = ko.pureComputed(function() {
        // TODO L18N
        return this.stepNumber() + ' of ' + this.totalSteps();
    }, this);

    this.inProgress = ko.pureComputed(function() {
        return !this.isFinished();
    }, this);

    this.finish = function() {
        this.stepNumber(stepNumberFinished);
    };

    this.isFinished = ko.pureComputed(function() {
        return this.stepNumber() === stepNumberFinished;
    }, this);
}

module.exports = OnboardingProgress;

OnboardingProgress.prototype.setStepByName = function setStepByName(name) {
    var stepIndex = this.stepNames().indexOf(name);
    if (stepIndex > -1) {
        this.stepNumber(stepIndex);
        return true;
    }
    return false;
};
<|endoftext|>


<commit_before>import { Camera } from './Camera.js';
import { Object3D } from '../core/Object3D.js';

/**
 * @author alteredq / http://alteredqualia.com/
 * @author arose / http://github.com/arose
 */

function OrthographicCamera( left, right, top, bottom, near, far ) {

	Camera.call( this );

	this.type = 'OrthographicCamera';

	this.zoom = 1;
	this.view = null;

	this.left = ( left !== undefined ) ? left : -1;
	this.right = ( right !== undefined ) ? right : 1;
	this.top = ( top !== undefined ) ? ltopeft : 1;
	this.bottom = ( bottom !== undefined ) ? bottom : -1;

	this.near = ( near !== undefined ) ? near : 0.1;
	this.far = ( far !== undefined ) ? far : 2000;

	this.updateProjectionMatrix();

}

OrthographicCamera.prototype = Object.assign( Object.create( Camera.prototype ), {

	constructor: OrthographicCamera,

	isOrthographicCamera: true,

	copy: function ( source, recursive ) {

		Camera.prototype.copy.call( this, source, recursive );

		this.left = source.left;
		this.right = source.right;
		this.top = source.top;
		this.bottom = source.bottom;
		this.near = source.near;
		this.far = source.far;

		this.zoom = source.zoom;
		this.view = source.view === null ? null : Object.assign( {}, source.view );

		return this;

	},

	setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {

		if ( this.view === null ) {

			this.view = {
				enabled: true,
				fullWidth: 1,
				fullHeight: 1,
				offsetX: 0,
				offsetY: 0,
				width: 1,
				height: 1
			};

		}

		this.view.enabled = true;
		this.view.fullWidth = fullWidth;
		this.view.fullHeight = fullHeight;
		this.view.offsetX = x;
		this.view.offsetY = y;
		this.view.width = width;
		this.view.height = height;

		this.updateProjectionMatrix();

	},

	clearViewOffset: function () {

		if ( this.view !== null ) {

			this.view.enabled = false;

		}

		this.updateProjectionMatrix();

	},

	updateProjectionMatrix: function () {

		var dx = ( this.right - this.left ) / ( 2 * this.zoom );
		var dy = ( this.top - this.bottom ) / ( 2 * this.zoom );
		var cx = ( this.right + this.left ) / 2;
		var cy = ( this.top + this.bottom ) / 2;

		var left = cx - dx;
		var right = cx + dx;
		var top = cy + dy;
		var bottom = cy - dy;

		if ( this.view !== null && this.view.enabled ) {

			var zoomW = this.zoom / ( this.view.width / this.view.fullWidth );
			var zoomH = this.zoom / ( this.view.height / this.view.fullHeight );
			var scaleW = ( this.right - this.left ) / this.view.width;
			var scaleH = ( this.top - this.bottom ) / this.view.height;

			left += scaleW * ( this.view.offsetX / zoomW );
			right = left + scaleW * ( this.view.width / zoomW );
			top -= scaleH * ( this.view.offsetY / zoomH );
			bottom = top - scaleH * ( this.view.height / zoomH );

		}

		this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );

		this.projectionMatrixInverse.getInverse( this.projectionMatrix );

	},

	toJSON: function ( meta ) {

		var data = Object3D.prototype.toJSON.call( this, meta );

		data.object.zoom = this.zoom;
		data.object.left = this.left;
		data.object.right = this.right;
		data.object.top = this.top;
		data.object.bottom = this.bottom;
		data.object.near = this.near;
		data.object.far = this.far;

		if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );

		return data;

	}

} );

export { OrthographicCamera };
<commit_msg>Fixed typo<commit_after>import { Camera } from './Camera.js';
import { Object3D } from '../core/Object3D.js';

/**
 * @author alteredq / http://alteredqualia.com/
 * @author arose / http://github.com/arose
 */

function OrthographicCamera( left, right, top, bottom, near, far ) {

	Camera.call( this );

	this.type = 'OrthographicCamera';

	this.zoom = 1;
	this.view = null;

	this.left = ( left !== undefined ) ? left : -1;
	this.right = ( right !== undefined ) ? right : 1;
	this.top = ( top !== undefined ) ? top : 1;
	this.bottom = ( bottom !== undefined ) ? bottom : -1;

	this.near = ( near !== undefined ) ? near : 0.1;
	this.far = ( far !== undefined ) ? far : 2000;

	this.updateProjectionMatrix();

}

OrthographicCamera.prototype = Object.assign( Object.create( Camera.prototype ), {

	constructor: OrthographicCamera,

	isOrthographicCamera: true,

	copy: function ( source, recursive ) {

		Camera.prototype.copy.call( this, source, recursive );

		this.left = source.left;
		this.right = source.right;
		this.top = source.top;
		this.bottom = source.bottom;
		this.near = source.near;
		this.far = source.far;

		this.zoom = source.zoom;
		this.view = source.view === null ? null : Object.assign( {}, source.view );

		return this;

	},

	setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {

		if ( this.view === null ) {

			this.view = {
				enabled: true,
				fullWidth: 1,
				fullHeight: 1,
				offsetX: 0,
				offsetY: 0,
				width: 1,
				height: 1
			};

		}

		this.view.enabled = true;
		this.view.fullWidth = fullWidth;
		this.view.fullHeight = fullHeight;
		this.view.offsetX = x;
		this.view.offsetY = y;
		this.view.width = width;
		this.view.height = height;

		this.updateProjectionMatrix();

	},

	clearViewOffset: function () {

		if ( this.view !== null ) {

			this.view.enabled = false;

		}

		this.updateProjectionMatrix();

	},

	updateProjectionMatrix: function () {

		var dx = ( this.right - this.left ) / ( 2 * this.zoom );
		var dy = ( this.top - this.bottom ) / ( 2 * this.zoom );
		var cx = ( this.right + this.left ) / 2;
		var cy = ( this.top + this.bottom ) / 2;

		var left = cx - dx;
		var right = cx + dx;
		var top = cy + dy;
		var bottom = cy - dy;

		if ( this.view !== null && this.view.enabled ) {

			var zoomW = this.zoom / ( this.view.width / this.view.fullWidth );
			var zoomH = this.zoom / ( this.view.height / this.view.fullHeight );
			var scaleW = ( this.right - this.left ) / this.view.width;
			var scaleH = ( this.top - this.bottom ) / this.view.height;

			left += scaleW * ( this.view.offsetX / zoomW );
			right = left + scaleW * ( this.view.width / zoomW );
			top -= scaleH * ( this.view.offsetY / zoomH );
			bottom = top - scaleH * ( this.view.height / zoomH );

		}

		this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );

		this.projectionMatrixInverse.getInverse( this.projectionMatrix );

	},

	toJSON: function ( meta ) {

		var data = Object3D.prototype.toJSON.call( this, meta );

		data.object.zoom = this.zoom;
		data.object.left = this.left;
		data.object.right = this.right;
		data.object.top = this.top;
		data.object.bottom = this.bottom;
		data.object.near = this.near;
		data.object.far = this.far;

		if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );

		return data;

	}

} );

export { OrthographicCamera };
<|endoftext|>


<commit_before>const u = require('./testUtil')
const base64png = require('./base64png')
window.locale = 'FI'
window.IS_TEST = true
mocha.setup({
    ui: 'bdd',
//    reporter: WebConsole
})
window.expect = chai.expect

const $el = {}

describe('rich text editor', () => {
    before('wait for tools to hide', u.waitUntil(() => u.isOutsideViewPort($('[data-js="tools"]'))))
    before('wait answer field to initialize', u.waitUntil(() => $('.answer1').attr('contenteditable')))
    before(() => {
        $el.answer1 = $('.answer1')
        $el.latexField = $('[data-js="latexField"]')
        $el.equationField = $('[data-js="equationField"]')
        $el.equationFieldTextArea = $el.equationField.find('textarea')
        $el.mathToolbar = $('[data-js="mathToolbar"]')
        $el.tools = $('[data-js="tools"]')
        $el.mathEditor = $('[data-js="mathEditor"]')
    })
    before('focus', () => $el.answer1.focus())
    before('wait for tools visible', u.waitUntil(() => $el.tools.is(':visible')))

    it('shows character list', () => expect($('[data-js="charactersList"]')).to.be.visible)
    it('hide math tools', () => expect($el.mathToolbar).to.be.hidden)
    it('answer has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))

    describe('when pasting images', () => {
        describe('png', () => {
            before('paste image', done => {
                $el.answer1.append(base64png)
                $el.answer1.find('img:last').get(0).onload = e => {
                    if (~e.target.src.indexOf('http')) done()
                }
                $el.answer1.trigger(u.pasteEventMock)
            })
            it('saves pasted image', () => {
                expect($el.answer1.find('img:last')).to.have.attr('src').match(/\/screenshot/)
            })
        })
        describe('not png', () => {
            let currentImgAmout
            before('#3', done => {
                currentImgAmout = $el.answer1.find('img').length
                $el.answer1
                    .append('')
                $('.answer1 img:last').get(0).onload = e => {
                    done()
                }
                $el.answer1.trigger(u.pasteEventMock)
            })
            it('ignores other than png images', () => {
                expect($el.answer1.find('img').length).to.equal(currentImgAmout)
            })
        })
    })

    describe('start math', () => {
        before('wait for tools visible', u.waitUntil(() => $el.tools.is(':visible')))
        before(() => $('[data-js="newEquation"]').mousedown())

        it('shows math tools', () => expect($el.mathToolbar).to.be.visible)
        it('shows math editor', () => expect($el.mathEditor).to.be.visible)
        it('answer still has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))

        describe('when focus in latex field', () => {
            before('type', () => $el.latexField.focus().val('xy').trigger('input'))
            before(u.delay)
            before(() => $('.rich-text-editor-toolbar-characters-group button:eq(2)').mousedown())
            before(u.delay)
            before('focus', () => $el.answer1.focus())

            it('answer still has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))
            it('shows math in equation field', () => expect($el.equationField).to.have.text('xy±'))
            it('shows math in latex field', () => expect($el.latexField).to.have.value('xy\\pm'))
            it('shows math in img', () => expect($('img:first')).to.have.attr('src', '/math.svg?latex=xy%5Cpm'))
        })

        describe('when focus in equation field', () => {
            before(() => $('img:first').trigger({type: 'click', which: 1}))
            before(() => $el.latexField.trigger('blur').val('').trigger('input'))
            before(() => $el.equationFieldTextArea.trigger('focusin').val('a+b').trigger('paste'))
            before(u.delay)
            before(() => $('.rich-text-editor-toolbar-characters-group button:eq(3)').mousedown())
            before(u.delay)
            before(() => $el.answer1.focus())

            it('answer still has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))
            it('shows math in equation field', () => expect($el.equationField).to.have.text('a+b∞'))
            it('shows math in latex field', () => expect($el.latexField).to.have.value('a+b\\infty'))
            it('shows math in img', () => expect($('img:first')).to.have.attr('src', '/math.svg?latex=a%2Bb%5Cinfty'))
        })

        describe('equation click opens and esc closes math editor', () => {
            before(() => $('img:first').trigger({type: 'click', which: 1}))
            it('editor is visible and then hidden', () => {
                expect($el.mathEditor).to.be.visible
                $el.answer1.trigger({type: 'keydown', keyCode: 27})
                expect($el.mathEditor).to.be.hidden
            })
        })

        describe('when clicking special character from toolbar', () => {
            before(() => $('.rich-text-editor-toolbar-characters-group button:first').mousedown())

            it('inserts special character to answer body', () => {
                expect($el.answer1).to.have.text('°')
            })
        })
    })

    describe('when leaving answer box', () => {
        before(() => $el.answer1.blur())
        before(u.delay)

        it('removes focus style from answer', () => expect($el.answer1).to.not.have.class('rich-text-focused'))
    })
})
<commit_msg>Add another test for focus behavior when clicking math from outside<commit_after>const u = require('./testUtil')
const base64png = require('./base64png')
window.locale = 'FI'
window.IS_TEST = true
mocha.setup({
    ui: 'bdd',
//    reporter: WebConsole
})
window.expect = chai.expect

const $el = {}

describe('rich text editor', () => {
    before('wait for tools to hide', u.waitUntil(() => u.isOutsideViewPort($('[data-js="tools"]'))))
    before('wait answer field to initialize', u.waitUntil(() => $('.answer1').attr('contenteditable')))
    before(() => {
        $el.answer1 = $('.answer1')
        $el.latexField = $('[data-js="latexField"]')
        $el.equationField = $('[data-js="equationField"]')
        $el.equationFieldTextArea = $el.equationField.find('textarea')
        $el.mathToolbar = $('[data-js="mathToolbar"]')
        $el.tools = $('[data-js="tools"]')
        $el.mathEditor = $('[data-js="mathEditor"]')
    })
    before('focus', () => $el.answer1.focus())
    before('wait for tools visible', u.waitUntil(() => $el.tools.is(':visible')))

    it('shows character list', () => expect($('[data-js="charactersList"]')).to.be.visible)
    it('hide math tools', () => expect($el.mathToolbar).to.be.hidden)
    it('answer has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))

    describe('when pasting images', () => {
        describe('png', () => {
            before('paste image', done => {
                $el.answer1.append(base64png)
                $el.answer1.find('img:last').get(0).onload = e => {
                    if (~e.target.src.indexOf('http')) done()
                }
                $el.answer1.trigger(u.pasteEventMock)
            })
            it('saves pasted image', () => {
                expect($el.answer1.find('img:last')).to.have.attr('src').match(/\/screenshot/)
            })
        })
        describe('not png', () => {
            let currentImgAmout
            before('#3', done => {
                currentImgAmout = $el.answer1.find('img').length
                $el.answer1
                    .append('')
                $('.answer1 img:last').get(0).onload = e => {
                    done()
                }
                $el.answer1.trigger(u.pasteEventMock)
            })
            it('ignores other than png images', () => {
                expect($el.answer1.find('img').length).to.equal(currentImgAmout)
            })
        })
    })

    describe('start math', () => {
        before('wait for tools visible', u.waitUntil(() => $el.tools.is(':visible')))
        before(() => $('[data-js="newEquation"]').mousedown())

        it('shows math tools', () => expect($el.mathToolbar).to.be.visible)
        it('shows math editor', () => expect($el.mathEditor).to.be.visible)
        it('answer still has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))

        describe('when focus in latex field', () => {
            before('type', () => $el.latexField.focus().val('xy').trigger('input'))
            before(u.delay)
            before(() => $('.rich-text-editor-toolbar-characters-group button:eq(2)').mousedown())
            before(u.delay)
            before('focus', () => $el.answer1.focus())

            it('answer still has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))
            it('shows math in equation field', () => expect($el.equationField).to.have.text('xy±'))
            it('shows math in latex field', () => expect($el.latexField).to.have.value('xy\\pm'))
            it('shows math in img', () => expect($('img:first')).to.have.attr('src', '/math.svg?latex=xy%5Cpm'))
        })

        describe('when focus in equation field', () => {
            before(() => $el.answer1.blur())
            before(u.delay)
            before(() => $('img:first').trigger({type: 'click', which: 1}))
            before(() => $el.latexField.val('').trigger('input'))
            before(() => $el.equationFieldTextArea.val('a+b').trigger('paste'))
            before(u.delay)
            before(() => $('.rich-text-editor-toolbar-characters-group button:eq(3)').mousedown())
            before(u.delay)
            before(() => $el.answer1.focus())

            it('answer still has focus style', () => expect($el.answer1).to.have.class('rich-text-focused'))
            it('shows math in equation field', () => expect($el.equationField).to.have.text('a+b∞'))
            it('shows math in latex field', () => expect($el.latexField).to.have.value('a+b\\infty'))
            it('shows math in img', () => expect($('img:first')).to.have.attr('src', '/math.svg?latex=a%2Bb%5Cinfty'))
        })

        describe('equation click opens and esc closes math editor', () => {
            before(() => $el.answer1.blur())
            it('editor is visible and then hidden', () => {
                expect($el.answer1).to.not.have.class('rich-text-focused')
                $('img:first').trigger({type: 'click', which: 1})
                expect($el.answer1).to.have.class('rich-text-focused')
                expect($el.mathEditor).to.be.visible
                $el.answer1.trigger({type: 'keydown', keyCode: 27})
                expect($el.mathEditor).to.be.hidden
            })
        })

        describe('when clicking special character from toolbar', () => {
            before(() => $('.rich-text-editor-toolbar-characters-group button:first').mousedown())

            it('inserts special character to answer body', () => {
                expect($el.answer1).to.have.text('°')
            })
        })
    })

    describe('when leaving answer box', () => {
        before(() => $el.answer1.blur())
        before(u.delay)

        it('removes focus style from answer', () => expect($el.answer1).to.not.have.class('rich-text-focused'))
    })
})
<|endoftext|>


<commit_before>QUnit.module('views');

QUnit.test('ok.View()', function (assert) {
	var view = new ok.View();
	assert.ok(view instanceof ok.View, 'Can create views');
	assert.deepEqual(view.classNames, ['ok-view'], 'Views have "ok-view" class by default');
	assert.deepEqual(ok.toArray(view.el.classList), ['ok-view'], 'Classes are applied to view element');
	assert.equal(view.tagName, 'div', 'Views are divs by default');
	assert.equal(view.el.tagName.toLowerCase(), 'div', 'Views elements are divs by default');
	var custom = new ok.View({
		tagName: 'p',
		classNames: 'custom-paragraph'
	});
	assert.equal(custom.tagName, 'p', 'Tag name property is set on init');
	assert.equal(custom.el.tagName.toLowerCase(), 'p', 'Can make views a tag other than div');
	assert.deepEqual(custom.classNames, ['ok-view', 'custom-paragraph'], 'Classes can be set on views');
	assert.deepEqual(ok.toArray(custom.el.classList), ['ok-view', 'custom-paragraph'], 'Classes can be set on view elements');
});

QUnit.test('ok.View#isStarted', function (assert) {
	var view = new ok.View();
	assert.equal(view.isStarted, false, 'Views are not started by default');
	view.start();
	assert.equal(view.isStarted, true, 'View can be started');
	view.stop();
	assert.equal(view.isStarted, false, 'View can be stopped');
});
<commit_msg>Test view `setElement()` method<commit_after>QUnit.module('views');

QUnit.test('ok.View()', function (assert) {
	var view = new ok.View();
	assert.ok(view instanceof ok.View, 'Can create views');
	assert.deepEqual(view.classNames, ['ok-view'], 'Views have "ok-view" class by default');
	assert.deepEqual(ok.toArray(view.el.classList), ['ok-view'], 'Classes are applied to view element');
	assert.equal(view.tagName, 'div', 'Views are divs by default');
	assert.equal(view.el.tagName.toLowerCase(), 'div', 'Views elements are divs by default');
	var custom = new ok.View({
		tagName: 'p',
		classNames: 'custom-paragraph'
	});
	assert.equal(custom.tagName, 'p', 'Tag name property is set on init');
	assert.equal(custom.el.tagName.toLowerCase(), 'p', 'Can make views a tag other than div');
	assert.deepEqual(custom.classNames, ['ok-view', 'custom-paragraph'], 'Classes can be set on views');
	assert.deepEqual(ok.toArray(custom.el.classList), ['ok-view', 'custom-paragraph'], 'Classes can be set on view elements');
});

QUnit.test('ok.View#isStarted', function (assert) {
	var view = new ok.View();
	assert.equal(view.isStarted, false, 'Views are not started by default');
	view.start();
	assert.equal(view.isStarted, true, 'View can be started');
	view.stop();
	assert.equal(view.isStarted, false, 'View can be stopped');
});

QUnit.test('ok.View#setElement()', function (assert) {
	var a = document.createElement('h1');
	var b = document.createElement('h2');
	var view = new ok.View({
		el: a
	});
	assert.equal(view.el, a, 'Views can be initialized with an element');
	view.setElement(b);
	assert.equal(view.el, b, 'View element can be set');
});
<|endoftext|>


<commit_before>module('Layer');

test('previousSibling / nextSibling', function() {
	var project = paper.project;
	var firstLayer = project.activeLayer;
	var secondLayer = new Layer();
	equals(function() {
		return secondLayer.previousSibling == firstLayer;
	}, true);
	equals(function() {
		return secondLayer.nextSibling == null;
	}, true);

	// Move another layer into secondLayer and check nextSibling /
	// previousSibling:
	var path = new Path();
	var thirdLayer = new Layer();
	secondLayer.appendBottom(thirdLayer);
	equals(function() {
		return secondLayer.children.length;
	}, 2);
	equals(function() {
		return thirdLayer.nextSibling == path;
	}, true);
	secondLayer.appendTop(thirdLayer);
	equals(function() {
		return thirdLayer.nextSibling == null;
	}, true);
	equals(function() {
		return thirdLayer.previousSibling == path;
	}, true);
	equals(function() {
		return project.layers.length == 2;
	}, true);

	firstLayer.appendTop(secondLayer);
	equals(function() {
		return project.layers.length == 1;
	}, true);
});

test('moveAbove / moveBelow', function() {
	var project = paper.project;
	var firstLayer = project.activeLayer;
	var secondLayer = new Layer();
	secondLayer.moveBelow(firstLayer);
	equals(function() {
		return secondLayer.previousSibling == null;
	}, true);
	equals(function() {
		return secondLayer.nextSibling == firstLayer;
	}, true);

	var path = new Path();
	firstLayer.appendTop(path);

	// move the layer above the path, inside the firstLayer:
	secondLayer.moveAbove(path);
	equals(function() {
		return secondLayer.previousSibling == path;
	}, true);
	equals(function() {
		return secondLayer.parent == firstLayer;
	}, true);
	// There should now only be one layer left:
	equals(function() {
		return project.layers.length;
	}, 1);
});

test('appendTop / appendBottom / nesting', function() {
	var project = paper.project;
	var firstLayer = project.activeLayer;
	var secondLayer = new Layer();
	// There should be two layers now in project.layers
	equals(function() {
		return project.layers.length;
	}, 2);
	firstLayer.appendTop(secondLayer);
	equals(function() {
		return secondLayer.parent == firstLayer;
	}, true);
	// There should only be the firsLayer now in project.layers
	equals(function() {
		return project.layers.length;
	}, 1);
	equals(function() {
		return project.layers[0] == firstLayer;
	}, true);
	// Now move secondLayer bellow the first again, in which case it should
	// reappear in project.layers
	secondLayer.moveBelow(firstLayer);
	// There should be two layers now in project.layers again now
	equals(function() {
		return project.layers.length;
	}, 2);
	equals(function() {
		return project.layers[0] == firstLayer
			&& project.layers[1] == secondLayer;
	}, true);
);<commit_msg>Fix Layer nesting tests.<commit_after>module('Layer');

test('previousSibling / nextSibling', function() {
	var project = paper.project;
	var firstLayer = project.activeLayer;
	var secondLayer = new Layer();
	equals(function() {
		return secondLayer.previousSibling == firstLayer;
	}, true);
	equals(function() {
		return secondLayer.nextSibling == null;
	}, true);

	// Move another layer into secondLayer and check nextSibling /
	// previousSibling:
	var path = new Path();
	var thirdLayer = new Layer();
	secondLayer.appendBottom(thirdLayer);
	equals(function() {
		return secondLayer.children.length;
	}, 2);
	equals(function() {
		return thirdLayer.nextSibling == path;
	}, true);
	secondLayer.appendTop(thirdLayer);
	equals(function() {
		return thirdLayer.nextSibling == null;
	}, true);
	equals(function() {
		return thirdLayer.previousSibling == path;
	}, true);
	equals(function() {
		return project.layers.length == 2;
	}, true);

	firstLayer.appendTop(secondLayer);
	equals(function() {
		return project.layers.length == 1;
	}, true);
});

test('moveAbove / moveBelow', function() {
	var project = paper.project;
	var firstLayer = project.activeLayer;
	var secondLayer = new Layer();
	secondLayer.moveBelow(firstLayer);
	equals(function() {
		return secondLayer.previousSibling == null;
	}, true);
	equals(function() {
		return secondLayer.nextSibling == firstLayer;
	}, true);

	var path = new Path();
	firstLayer.appendTop(path);

	// move the layer above the path, inside the firstLayer:
	secondLayer.moveAbove(path);
	equals(function() {
		return secondLayer.previousSibling == path;
	}, true);
	equals(function() {
		return secondLayer.parent == firstLayer;
	}, true);
	// There should now only be one layer left:
	equals(function() {
		return project.layers.length;
	}, 1);
});

test('appendTop / appendBottom / nesting', function() {
	var project = paper.project;
	var firstLayer = project.activeLayer;
	var secondLayer = new Layer();
	// There should be two layers now in project.layers
	equals(function() {
		return project.layers.length;
	}, 2);
	firstLayer.appendTop(secondLayer);
	equals(function() {
		return secondLayer.parent == firstLayer;
	}, true);
	// There should only be the firsLayer now in project.layers
	equals(function() {
		return project.layers.length;
	}, 1);
	equals(function() {
		return project.layers[0] == firstLayer;
	}, true);
	// Now move secondLayer bellow the first again, in which case it should
	// reappear in project.layers
	secondLayer.moveBelow(firstLayer);
	// There should be two layers now in project.layers again now
	equals(function() {
		return project.layers.length;
	}, 2);
	equals(function() {
		return project.layers[0] == secondLayer
			&& project.layers[1] == firstLayer;
	}, true);
});<|endoftext|>


<commit_before>import logger from '../util/logger'

const layerHandler = (tuples) => {
  return tuples.reduce((layer, tuple) => {
    const type = tuple[0]
    const value = tuple[1]
    switch (type) {
      case 2:
        layer.name = value
        break
      case 6:
        layer.lineTypeName = value
        break
      case 62:
        layer.colorNumber = value
        break
      case 70:
        layer.flags = value
        break
      case 390:
        layer.lineWeightEnum = value
        break
      default:
    }
    return layer
  }, {type: 'LAYER'})
}

const styleHandler = (tuples) => {
  return tuples.reduce((style, tuple) => {
    const type = tuple[0]
    const value = tuple[1]
    switch (type) {
      case 2:
        style.name = value
        break
      case 6:
        style.lineTypeName = value
        break
      case 40:
        style.fixedTextHeight = value
        break
      case 41:
        style.widthFactor = value
        break
      case 50:
        style.obliqueAngle = value
        break
      case 71:
        style.flags = value
        break
      case 42:
        style.lastHeightUsed = value
        break
      case 3:
        style.primaryFontFileName = value
        break
      case 4:
        style.bigFontFileName = value
        break
      default:
    }
    return style
  }, {type: 'STYLE'})
}

const tableHandler = (tuples, tableType, handler) => {
  const tableRowsTuples = []

  let tableRowTuples
  tuples.forEach(tuple => {
    const type = tuple[0]
    const value = tuple[1]
    if (((type === 0) || (type === 2)) && (value === tableType)) {
      tableRowTuples = []
      tableRowsTuples.push(tableRowTuples)
    } else {
      tableRowTuples.push(tuple)
    }
  })

  return tableRowsTuples.reduce((acc, rowTuples) => {
    const tableRow = handler(rowTuples)
    if (tableRow.name) {
      acc[tableRow.name] = tableRow
    } else {
      logger.warn('table row without name:', tableRow)
    }
    return acc
  }, {})
}

export default (tuples) => {
  let tableGroups = []
  let tableTuples
  tuples.forEach((tuple) => {
    // const type = tuple[0];
    const value = tuple[1]
    if (value === 'TABLE') {
      tableTuples = []
      tableGroups.push(tableTuples)
    } else if (value === 'ENDTAB') {
      tableGroups.push(tableTuples)
    } else {
      tableTuples.push(tuple)
    }
  })

  let stylesTuples = []
  let layersTuples = []
  tableGroups.forEach(group => {
    if (group[0][1] === 'STYLE') {
      stylesTuples = group
    } else if (group[0][1] === 'LTYPE') {
      logger.warn('LTYPE in tables not supported')
    } else if (group[0][1] === 'LAYER') {
      layersTuples = group
    }
  })

  return {
    layers: tableHandler(layersTuples, 'LAYER', layerHandler),
    styles: tableHandler(stylesTuples, 'STYLE', styleHandler)
  }
}
<commit_msg>fix: 390 prop should be 370; 290 is a plot prop<commit_after>import logger from '../util/logger'

const layerHandler = (tuples) => {
  return tuples.reduce((layer, tuple) => {
    const type = tuple[0]
    const value = tuple[1]
    // https://www.autodesk.com/techpubs/autocad/acad2000/dxf/layer_dxf_04.htm
    switch (type) {
      case 2:
        layer.name = value
        break
      case 6:
        layer.lineTypeName = value
        break
      case 62:
        layer.colorNumber = value
        break
      case 70:
        layer.flags = value
        break
      case 290:
        layer.plot = parseInt(value) === 0 ? false : true
        break
      case 370:
        layer.lineWeightEnum = value
        break
      default:
    }
    return layer
  }, {type: 'LAYER'})
}

const styleHandler = (tuples) => {
  return tuples.reduce((style, tuple) => {
    const type = tuple[0]
    const value = tuple[1]
    switch (type) {
      case 2:
        style.name = value
        break
      case 6:
        style.lineTypeName = value
        break
      case 40:
        style.fixedTextHeight = value
        break
      case 41:
        style.widthFactor = value
        break
      case 50:
        style.obliqueAngle = value
        break
      case 71:
        style.flags = value
        break
      case 42:
        style.lastHeightUsed = value
        break
      case 3:
        style.primaryFontFileName = value
        break
      case 4:
        style.bigFontFileName = value
        break
      default:
    }
    return style
  }, {type: 'STYLE'})
}

const tableHandler = (tuples, tableType, handler) => {
  const tableRowsTuples = []

  let tableRowTuples
  tuples.forEach(tuple => {
    const type = tuple[0]
    const value = tuple[1]
    if (((type === 0) || (type === 2)) && (value === tableType)) {
      tableRowTuples = []
      tableRowsTuples.push(tableRowTuples)
    } else {
      tableRowTuples.push(tuple)
    }
  })

  return tableRowsTuples.reduce((acc, rowTuples) => {
    const tableRow = handler(rowTuples)
    if (tableRow.name) {
      acc[tableRow.name] = tableRow
    } else {
      logger.warn('table row without name:', tableRow)
    }
    return acc
  }, {})
}

export default (tuples) => {
  let tableGroups = []
  let tableTuples
  tuples.forEach((tuple) => {
    // const type = tuple[0];
    const value = tuple[1]
    if (value === 'TABLE') {
      tableTuples = []
      tableGroups.push(tableTuples)
    } else if (value === 'ENDTAB') {
      tableGroups.push(tableTuples)
    } else {
      tableTuples.push(tuple)
    }
  })

  let stylesTuples = []
  let layersTuples = []
  tableGroups.forEach(group => {
    if (group[0][1] === 'STYLE') {
      stylesTuples = group
    } else if (group[0][1] === 'LTYPE') {
      logger.warn('LTYPE in tables not supported')
    } else if (group[0][1] === 'LAYER') {
      layersTuples = group
    }
  })

  return {
    layers: tableHandler(layersTuples, 'LAYER', layerHandler),
    styles: tableHandler(stylesTuples, 'STYLE', styleHandler)
  }
}
<|endoftext|>


<commit_before>// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file relies on the fact that the following declarations have been made
// in runtime.js:
// var $Function = global.Function;
// var $Array = global.Array;

"use strict";

(function() {
  function FunctionToMethod(homeObject) {
    if (!IS_SPEC_FUNCTION(this)) {
      throw MakeTypeError('toMethod_non_function',
                          [%ToString(this), typeof this]);

    }

    if (!IS_SPEC_OBJECT(homeObject)) {
      throw MakeTypeError('toMethod_non_object',
                          [%ToString(homeObject)]);
    }

    return %ToMethod(this, homeObject);
  }

  %CheckIsBootstrapping();

  InstallFunctions($Function.prototype, DONT_ENUM, $Array(
      "toMethod", FunctionToMethod
  ));
}());
<commit_msg>Make sure FunctionToMethod is lazily compiled.<commit_after>// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file relies on the fact that the following declarations have been made
// in runtime.js:
// var $Function = global.Function;
// var $Array = global.Array;

"use strict";

function FunctionToMethod(homeObject) {
  if (!IS_SPEC_FUNCTION(this)) {
    throw MakeTypeError('toMethod_non_function',
                        [%ToString(this), typeof this]);

  }

  if (!IS_SPEC_OBJECT(homeObject)) {
    throw MakeTypeError('toMethod_non_object',
                        [%ToString(homeObject)]);
  }

  return %ToMethod(this, homeObject);
}

function SetupHarmonyClasses() {
  %CheckIsBootstrapping();

  InstallFunctions($Function.prototype, DONT_ENUM, $Array(
      "toMethod", FunctionToMethod
  ));
}

SetupHarmonyClasses();
<|endoftext|>


<commit_before>window.HeadingAnchors = {

    init: function() {

        var $ = function( selector ) {
            var domElements   = document.querySelectorAll( selector ),
                elementsArray = [];
            for ( var i = 0; i < domElements.length; i++ ) {
                elementsArray.push( domElements[i] );
            }
            return elementsArray;
        };

        var nice = function( notNiceString ) {
            return notNiceString.replace('’','').replace(/[^a-z0-9]+/ig, '-');
        };

        var niceHeadings = {};

        $('h2, h3, h4, h5, h6').forEach(function( titleElement ) {

            var anchor    = document.createElement('a'),
                niceHeading = nice( titleElement.innerHTML );

            if ( niceHeading in niceHeadings ) {
                anchor.href = '#-'+ niceHeading +'-'+ niceHeadings[niceHeading];
                titleElement.id = '-'+ niceHeading +'-'+ niceHeadings[niceHeading];
                niceHeadings[niceHeading] = niceHeadings[niceHeading] += 1;
            } else {
                anchor.href = '#-'+ niceHeading;
                titleElement.id = '-'+ niceHeading;
                niceHeadings[niceHeading] = 2;
            }

            titleElement.className += ' heading-anchor';
            titleElement.appendChild( anchor );
        });

        var headingInHash = document.getElementById( window.location.hash.substr(1) )
        if ( headingInHash ) {
            window.scrollTo( 0, headingInHash.offsetTop );
        }

    }

};
<commit_msg>Renamed "titleElement" to "headingElement" all over the place.<commit_after>window.HeadingAnchors = {

    init: function() {

        var $ = function( selector ) {
            var domElements   = document.querySelectorAll( selector ),
                elementsArray = [];
            for ( var i = 0; i < domElements.length; i++ ) {
                elementsArray.push( domElements[i] );
            }
            return elementsArray;
        };

        var nice = function( notNiceString ) {
            return notNiceString.replace('’','').replace(/[^a-z0-9]+/ig, '-');
        };

        var niceHeadings = {};

        $('h2, h3, h4, h5, h6').forEach(function( headingElement ) {

            var anchor    = document.createElement('a'),
                niceHeading = nice( headingElement.innerHTML );

            if ( niceHeading in niceHeadings ) {
                anchor.href = '#-'+ niceHeading +'-'+ niceHeadings[niceHeading];
                headingElement.id = '-'+ niceHeading +'-'+ niceHeadings[niceHeading];
                niceHeadings[niceHeading] = niceHeadings[niceHeading] += 1;
            } else {
                anchor.href = '#-'+ niceHeading;
                headingElement.id = '-'+ niceHeading;
                niceHeadings[niceHeading] = 2;
            }

            headingElement.className += ' heading-anchor';
            headingElement.appendChild( anchor );
        });

        var headingInHash = document.getElementById( window.location.hash.substr(1) )
        if ( headingInHash ) {
            window.scrollTo( 0, headingInHash.offsetTop );
        }

    }

};
<|endoftext|>


<commit_before>(function (spiderOakApp, fileHelper, window, undefined) {
  "use strict";
  var console = window.console || {};
  console.log = console.log || function(){};
  var Backbone    = window.Backbone,
      _           = window._,
      $           = window.$;

  var FILETYPE_WORDDOC = {
      type: "application/word",
      description: "MS Word",
      openInternally: false
    },
    FILETYPE_EXCEL = {
      type: "application/excel",
      description: "MS Excel",
      openInternally: false
    },
    FILETYPE_POWERPOINT = {
      type: "application/powerpoint",
      description: "MS PowerPoint",
      openInternally: false
    },
    FILETYPE_PDF = {
      type: "application/pdf",
      description: "Adobe PDF",
      openInternally: false
    },
    FILETYPE_JPEG = {
      type: "image/jpeg",
      description: "JPEG Image",
      openInternally: true
    },
    FILETYPE_GIF = {
      type: "image/gif",
      description: "GIF Image",
      openInternally: false
    },
    FILETYPE_PNG = {
      type: "image/png",
      description: "PNG Image",
      openInternally: true
    },
    FILETYPE_TEXT = {
      type: "text/plain",
      description: "Plain Text",
      openInternally: true
    },
    FILETYPE_HTML = {
      type: "text/html",
      description: "HTML",
      openInternally: true
    },
    FILETYPE_CSS = {
      type: "text/css",
      description: "CSS",
      openInternally: true
    },
    FILETYPE_JS = {
      type: "text/javascript",
      description: "JavaScript",
      openInternally: true
    },
    FILETYPE_PAGES = {
      type: "application/pages",
      description: "Apple Pages",
      openInternally: false
    },
    FILETYPE_NUMBERS = {
      type: "application/numbers",
      description: "Apple Numbers",
      openInternally: false
    },
    FILETYPE_KEYNOTE = {
      type: "application/keynote",
      description: "Apple Keynote",
      openInternally: false
    },
    FILETYPE_RTF = {
      type: "application/rtf",
      description: "Rich Text",
      openInternally: false
    },
    FILETYPE_CSV = {
      type: "text/csv",
      description: "CSV",
      openInternally: true
    },
    FILETYPE_VCARD = {
      type: "application_vcard",
      description: "Vcard",
      openInternally: false
    },
    FILETYPE_UNKNOWN = {
      type: "unknown",
      description: "Unknown",
      openInternally: false
    };

  fileHelper.readableFileSize = function(size) {
    var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    var i = 0;
    while(size >= 1024) {
        size /= 1024;
        ++i;
    }
    return size.toFixed(1) + ' ' + units[i];
  };

  fileHelper.fileTypeFromExtension = function(fileName) {
    var fileExtension = fileName.split('.').pop();

    if (fileExtension === "doc" || fileExtension === "docx") {
      return FILETYPE_WORDDOC;
    }
    if (fileExtension === "xls" || fileExtension === "xlsx"||
       fileExtension === "xlsb" || fileExtension === "xlsm") {
        return FILETYPE_EXCEL;
    }
    if (fileExtension === "ppt" || fileExtension === "pptx") {
        return FILETYPE_POWERPOINT;
    }
    else if (fileExtension === "gif") {
        return FILETYPE_GIF;
    }
    else if (fileExtension === "jpg"|| fileExtension === "jpeg") {
        return FILETYPE_JPEG;
    }
    else if (fileExtension === "png") {
        return FILETYPE_PNG;
    }
    else if (fileExtension === "txt") {
        return FILETYPE_TEXT;
    }
    else if (fileExtension === "pdf") {
        return FILETYPE_PDF;
    }
    else if (fileExtension === "pages") {
        return FILETYPE_PAGES;
    }
    else if (fileExtension === "numbers") {
        return FILETYPE_NUMBERS;
    }
    else if (fileExtension === "key") {
        return FILETYPE_KEYNOTE;
    }
    else if (fileExtension === "rtf") {
        return FILETYPE_RTF;
    }
    else if (fileExtension === "csv") {
        return FILETYPE_CSV;
    }
    else if (fileExtension === "vcf") {
        return FILETYPE_VCARD;
    }
    else if (fileExtension === "html" || fileExtension === "htm") {
       return FILETYPE_HTML;
    }
    else if (fileExtension === "css") {
       return FILETYPE_CSS;
    }
    else if (fileExtension === "js") {
       return FILETYPE_JS;
    }
    return FILETYPE_UNKNOWN;
  };

})(window.spiderOakApp = window.spiderOakApp || {},
      window.fileHelper = window.fileHelper || {}, window);
<commit_msg>Accidental dupe<commit_after><|endoftext|>


<commit_before>const { Extra, Markup } = require('telegraf');
const axios = require('axios');
const { sendToAdmin, GLOBAL_KEYBOARD } = require('./utils');
const { findUserByIdAndUpdate, findOrCreateUserById } = require('./dbmanager');
const { LASTFM_URL, LASTFM_KEY } = require('../../config');

async function start(ctx, next) {
  const res = await findOrCreateUserById(ctx.from.id);

  if (res.created) {
    await ctx.reply(`Hello, ${ctx.from.first_name}!

This bot allows you to scrobble songs, albums and tracklists in text mode. \
To take advantage of these opportunities you have to grant access to your Last.fm account...`, GLOBAL_KEYBOARD);
    ctx.enterScene('auth');
    return sendToAdmin(ctx, `We've got a new user! @${ctx.from.username}`);
  }

  return next();
}

function help(ctx) {
  ctx.reply(`To scrobble a track you can simply type its info in this format:

Artist\nTrack Name\nAlbum Title

Use the keyboard below for album and tracklist scrobbling.

/auth — grant access or change account
/recent — see recent scrobbled tracks from your account

If you have any ideas or improvements for the bot please tell us about them via /wish command`, GLOBAL_KEYBOARD);
}

async function whoami(ctx) {
  return ctx.reply(`You are logged in as <a href="http://www.last.fm/user/${ctx.user.account}">${ctx.user.account}</a>`,
    Extra.HTML().webPreview(false));
}

async function nextAlbum(ctx, which) {
  const pages = ctx.callbackQuery.message.text.slice(ctx.callbackQuery.message.text.search(/\d?\d of \d\d/)).split(' of ');
  const i = which === 'NEXT'
    ? +pages[0] + 1 > +pages[1] ? 1 : +pages[0] + 1
    : +pages[0] - 1 < 1 ? +pages[1] : +pages[0] - 1;
  const id = ctx.user.discogs_results[i].id;
  const res = await axios(`https://api.discogs.com/releases/${id}`);

  if (res.data.tracklist.length) {
    const tracks = res.data.tracklist
      .map((track) => {
        const dur = track.duration.split(':');
        return { name: track.title, duration: (dur[0] * 60) + +dur[1] };
      });

    ctx.user = await findUserByIdAndUpdate(ctx.from.id, { 'album.tracks': tracks }, { new: true });
  } else {
    ctx.user = await findUserByIdAndUpdate(ctx.from.id, {
      'album.tracks': ['There are no any tracks in this result'],
    }, { new: true });
  }

  const title = ctx.user.album.title;
  const artist = ctx.user.album.artist;

  return ctx.editMessageText(
    `You are about to scrobble [${title}](${encodeURI(
    `http://www.last.fm/music/${artist}/${title}`)}) by [${artist}](${encodeURI(
    `http://www.last.fm/music/${artist}`)}). The following tracks have been found on Discogs.com and will be scrobbled:

${ctx.user.album.tracks.map(track => track.name).join('\n')}

Results: ${i} of ${ctx.user.discogs_results.length - 1}`,
    Extra.markdown().webPreview(false).markup(Markup.inlineKeyboard([[
      Markup.callbackButton('Edit', 'EDIT'),
      Markup.callbackButton('⬅', 'PREV'),
      Markup.callbackButton('➡', 'NEXT'),
      Markup.callbackButton('Cancel', 'CANCEL'),
    ], [Markup.callbackButton('OK', 'OK')],
    ])));
}

async function searchFromLastfmAndAnswerInlineQuery(ctx, type = 'track') {
  if (!ctx.inlineQuery.query) {
    return ctx.answerInlineQuery([{
      type: 'article',
      title: 'Type your query below...',
      id: ctx.inlineQuery.id,
      input_message_content: {
        message_text: 'Type your query below...',
      },
    }]);
  }

  const query = ctx.inlineQuery.query;
  const res = await axios(encodeURI(
    `${LASTFM_URL}?method=${type}.search&${type}=${query}&api_key=${LASTFM_KEY}&format=json`));
  const results = res.data.results[`${type}matches`][`${type}`];
  const inlineResults = results
    .filter(item => item.name !== '(null)')
    .map((item, i) => {
      const photoUrl = item.image[2]['#text'] || 'http://img2-ak.lst.fm/i/u/174s/c6f59c1e5e7240a4c0d427abd71f3dbb.png';

      return {
        type: 'article',
        id: String(i),
        thumb_url: photoUrl,
        photo_width: 174,
        photo_height: 174,
        title: item.name,
        description: `${item.artist}`,
        input_message_content: {
          message_text: `${item.artist}\n${item.name}`,
        },
      };
    });

  return ctx.answerInlineQuery(inlineResults);
}

async function recentTracks(ctx) {
  const res = await axios(`${LASTFM_URL}?method=user.getrecenttracks&user=${ctx.user.account}&limit=15&api_key=${LASTFM_KEY}&format=json`);
  const tracks = res.data.recenttracks.track
    .filter((track) => {
      if (track['@attr']) {
        return !track['@attr'].nowplaying;
      }

      return true;
    })
    .map(track => ({
      artist: track.artist['#text'],
      name: track.name,
      album: track.album['#text'],
      url: track.url,
    }));

  return ctx.reply(`Here are the very last 15 scrobbled tracks from your account:

${(tracks.map(track => `<a href="${encodeURI(`http://www.last.fm/music/${track.artist}`)}">${track.artist} — \
${track.name}`)
    .join('\n'))}`,
      Extra.HTML().webPreview(false));
}

module.exports = {
  start,
  help,
  whoami,
  nextAlbum,
  searchFromLastfmAndAnswerInlineQuery,
  recentTracks,
};
<commit_msg>Fix nextAlbum action with 1 result<commit_after>const { Extra, Markup } = require('telegraf');
const axios = require('axios');
const { sendToAdmin, GLOBAL_KEYBOARD } = require('./utils');
const { findUserByIdAndUpdate, findOrCreateUserById } = require('./dbmanager');
const { LASTFM_URL, LASTFM_KEY } = require('../../config');

async function start(ctx, next) {
  const res = await findOrCreateUserById(ctx.from.id);

  if (res.created) {
    await ctx.reply(`Hello, ${ctx.from.first_name}!

This bot allows you to scrobble songs, albums and tracklists in text mode. \
To take advantage of these opportunities you have to grant access to your Last.fm account...`, GLOBAL_KEYBOARD);
    ctx.enterScene('auth');
    return sendToAdmin(ctx, `We've got a new user! @${ctx.from.username}`);
  }

  return next();
}

function help(ctx) {
  ctx.reply(`To scrobble a track you can simply type its info in this format:

Artist\nTrack Name\nAlbum Title

Use the keyboard below for album and tracklist scrobbling.

/auth — grant access or change account
/recent — see recent scrobbled tracks from your account

If you have any ideas or improvements for the bot please tell us about them via /wish command`, GLOBAL_KEYBOARD);
}

async function whoami(ctx) {
  return ctx.reply(`You are logged in as <a href="http://www.last.fm/user/${ctx.user.account}">${ctx.user.account}</a>`,
    Extra.HTML().webPreview(false));
}

async function nextAlbum(ctx, which = 'NEXT') {
  let i = null;
  let id = null;
  const pages = ctx.callbackQuery.message.text.slice(ctx.callbackQuery.message.text.search(/\d?\d of \d\d/)).split(' of ');

  switch (which) {
    case 'NEXT':
      i = +pages[0] + 1 > +pages[1] ? 1 : +pages[0] + 1;
      break;
    case 'PREV':
      i = +pages[0] - 1 < 1 ? +pages[1] : +pages[0] - 1;
      break;
    default:
      return;
  }

  if (ctx.user.discogs_results[i]) {
    id = ctx.user.discogs_results[i].id;
  } else {
    return ctx.answerCallbackQuery('No more results');
  }

  const res = await axios(`https://api.discogs.com/releases/${id}`);

  if (res.data.tracklist.length) {
    const tracks = res.data.tracklist
      .map((track) => {
        const dur = track.duration.split(':');
        return { name: track.title, duration: (dur[0] * 60) + +dur[1] };
      });

    ctx.user = await findUserByIdAndUpdate(ctx.from.id, { 'album.tracks': tracks }, { new: true });
  } else {
    ctx.user = await findUserByIdAndUpdate(ctx.from.id, {
      'album.tracks': ['There are no any tracks in this result'],
    }, { new: true });
  }

  const title = ctx.user.album.title;
  const artist = ctx.user.album.artist;

  return ctx.editMessageText(
    `You are about to scrobble [${title}](${encodeURI(
    `http://www.last.fm/music/${artist}/${title}`)}) by [${artist}](${encodeURI(
    `http://www.last.fm/music/${artist}`)}). The following tracks have been found on Discogs.com and will be scrobbled:

${ctx.user.album.tracks.map(track => track.name).join('\n')}

Results: ${i} of ${ctx.user.discogs_results.length - 1}`,
    Extra.markdown().webPreview(false).markup(Markup.inlineKeyboard([[
      Markup.callbackButton('Edit', 'EDIT'),
      Markup.callbackButton('⬅', 'PREV'),
      Markup.callbackButton('➡', 'NEXT'),
      Markup.callbackButton('Cancel', 'CANCEL'),
    ], [Markup.callbackButton('OK', 'OK')],
    ])));
}

async function searchFromLastfmAndAnswerInlineQuery(ctx, type = 'track') {
  if (!ctx.inlineQuery.query) {
    return ctx.answerInlineQuery([{
      type: 'article',
      title: 'Type your query below...',
      id: ctx.inlineQuery.id,
      input_message_content: {
        message_text: 'Type your query below...',
      },
    }]);
  }

  const query = ctx.inlineQuery.query;
  const res = await axios(encodeURI(
    `${LASTFM_URL}?method=${type}.search&${type}=${query}&api_key=${LASTFM_KEY}&format=json`));
  const results = res.data.results[`${type}matches`][`${type}`];
  const inlineResults = results
    .filter(item => item.name !== '(null)')
    .map((item, i) => {
      const photoUrl = item.image[2]['#text'] || 'http://img2-ak.lst.fm/i/u/174s/c6f59c1e5e7240a4c0d427abd71f3dbb.png';

      return {
        type: 'article',
        id: String(i),
        thumb_url: photoUrl,
        photo_width: 174,
        photo_height: 174,
        title: item.name,
        description: `${item.artist}`,
        input_message_content: {
          message_text: `${item.artist}\n${item.name}`,
        },
      };
    });

  return ctx.answerInlineQuery(inlineResults);
}

async function recentTracks(ctx) {
  const res = await axios(`${LASTFM_URL}?method=user.getrecenttracks&user=${ctx.user.account}&limit=15&api_key=${LASTFM_KEY}&format=json`);
  const tracks = res.data.recenttracks.track
    .filter((track) => {
      if (track['@attr']) {
        return !track['@attr'].nowplaying;
      }

      return true;
    })
    .map(track => ({
      artist: track.artist['#text'],
      name: track.name,
      album: track.album['#text'],
      url: track.url,
    }));

  return ctx.reply(`Here are the very last 15 scrobbled tracks from your account:

${(tracks.map(track => `<a href="${encodeURI(`http://www.last.fm/music/${track.artist}`)}">${track.artist} — \
${track.name}`)
    .join('\n'))}`,
      Extra.HTML().webPreview(false));
}

module.exports = {
  start,
  help,
  whoami,
  nextAlbum,
  searchFromLastfmAndAnswerInlineQuery,
  recentTracks,
};
<|endoftext|>


<commit_before>/**
 * Grunt Mustacher
 * https://github.com/malas34/grunt-mustacher
 *
 * Copyright (c) 2014 Matthieu Lassalvy
 * Licensed under the MIT license.
 *
 * HANDLEBARS
 * @see http://handlebarsjs.com/
 *
 */
/*jslint indent: 4, nomen: true */
/*global module, require */
(function () {

    'use strict';

    var ImportsHelper,
        Handlebars = require('handlebars'),
        mustacher = require('./../mustacher');

    ImportsHelper = function () {};

    ImportsHelper.prototype.register = function () {
        Handlebars.registerHelper('$js', this.render.bind(this));
        Handlebars.registerHelper('$css', this.render.bind(this));
    };

    ImportsHelper.prototype.render = function (file, metas, options) {
        var data, result, parsed, k,
            args = mustacher.hasOptions(arguments);
        if (!args || args.length < 2) {
            throw new Error('missing arguments');
        }
        if (args.length < 3) {
            options = metas;
            metas = '';
        }
        if (metas !== '') {
            try {
                parsed = JSON.parse(metas);
                metas = '';
                for (k in parsed) {
                    if (parsed.hasOwnProperty(k)) {
                        metas = k + '="' + parsed[k] + '"';
                    }
                }
            } catch (e) {
                throw new Error('unable to parse metas');
            }
        }
        switch (options.name) {
            case '$js':
                result = '<script type="text/javascript" src="' + file + '.js ' + metas + ' "></script>';
                break;
            case '$css':
                result = '<link rel="stylesheet" type="text/css" href="' + file + '.css" ' + metas + ' />';
                break;
        }
        return new Handlebars.SafeString(result);
    };

    module.exports = ImportsHelper;

}());
<commit_msg>add inline css import<commit_after>/**
 * Grunt Mustacher
 * https://github.com/malas34/grunt-mustacher
 *
 * Copyright (c) 2014 Matthieu Lassalvy
 * Licensed under the MIT license.
 *
 * HANDLEBARS
 * @see http://handlebarsjs.com/
 *
 */
/*jslint indent: 4, nomen: true */
/*global module, require, console */
(function () {

    'use strict';

    var ImportsHelper,
        js_ext = '.js',
        css_ext = '.css',
        fs = require('fs'),
        Handlebars = require('handlebars'),
        mustacher = require('./../mustacher');

    ImportsHelper = function () {};

    /**
     *
     *
     *
     */
    ImportsHelper.prototype.getInlineContent = function (file) {
        try {
            return fs.readFileSync(file, 'utf8');
        } catch (e) {
            console.log('unable to load file ' + file);
            return '/* unable to load file ' + file + ' */';
        }
    };

    /**
     *
     *
     *
     */
    ImportsHelper.prototype.injectJS = function (file, inline) {
        var result;
        if (!inline) {
            result = '<script type="text/javascript" src="' + file + js_ext + '"></script>';
        } else {
            result = '<script type="text/javascript">';
            result += '<![CDATA[';
            result += this.getInlineContent(file + js_ext);
            result += ']]>';
            result += '</script>';
        }
        return result;
    };

    ImportsHelper.prototype.injectCSS = function (file, inline) {
        var result;
        if (!inline) {
            result = '<link rel="stylesheet" type="text/css" href="' + file + css_ext + '" />';
        } else {
            result = '<style type="text/css">';
            result += '/* inline file ' + file + css_ext + '*/';
            result += this.getInlineContent(file + css_ext);
            result += '</style>';
        }
        return result;
    };

    /**
     *
     *
     *
     */
    ImportsHelper.prototype.render = function (file, inline, options) {
        var data, result,
            args = mustacher.hasOptions(arguments);
        if (!args || args.length < 2) {
            throw new Error('missing arguments');
        }
        if (args.length < 3) {
            options = inline;
            inline = false;
        }
        inline = inline || false;
        switch (options.name) {
        case '$js':
            result = this.injectJS(file, inline);
            break;
        case '$css':
            result = this.injectCSS(file, inline);
            break;
        }
        return new Handlebars.SafeString(result);
    };

    /**
     *
     *
     *
     */
    ImportsHelper.prototype.register = function () {
        Handlebars.registerHelper('$js', this.render.bind(this));
        Handlebars.registerHelper('$css', this.render.bind(this));
    };

    module.exports = ImportsHelper;

}());<|endoftext|>


<commit_before>module.exports = {
  queryString: [
    ''
  ],
  queryStringSubQuery: [
    'androiddev',
    'topcashback',
    'Top_CashBack',
    'firozansar',
    'developer',
    'tech',
    'innovation',
    'technology',
    'iosdev'
  ],
  blockedStrings: [
    'porn',
    'sex',
    'bet',
    'binayajha'

  ],
  resultType: [
    'mixed',
    'recent',
    'popular'
  ],
  responseString: [
    'Hi @screenName thanks for the follow!',
    '@screenName thanks for following!',
    'Hey @screenName thanks for the follow!',
    'Thanks for following @screenName!',
    'Hey @screenName, thanks for following!',
    'Awesome @screenName, thanks for following!',
    'Thanks for the follow @screenName!',
    'Thanks for following @screenName! Have a nice day!'
  ]
}
<commit_msg>refactoring<commit_after>module.exports = {
  queryString: [
    ''
  ],
  queryStringSubQuery: [
    'androiddev',
    'Top_CashBack',
    'tech',
    'technology',
    'iosdev'
  ],
  blockedStrings: [
    'porn',
    'sex',
    'bet',
    'binayajha',
    'fuck',
    'sucks'
  ],
  resultType: [
    'mixed',
    'recent',
    'popular'
  ],
  responseString: [
    'Hi @screenName thanks for the follow!',
    '@screenName thanks for following!',
    'Hey @screenName thanks for the follow!',
    'Thanks for following @screenName!',
    'Hey @screenName, thanks for following!',
    'Awesome @screenName, thanks for following!',
    'Thanks for the follow @screenName!',
    'Thanks for following @screenName! Have a nice day!'
  ]
}
<|endoftext|>


<commit_before>module.exports = {
    queryString: [
        '',
        '',
        ''
    ],
    queryStringSubQuery: [
        '',
        '',
        ''
    ],
    blockedStrings: [
        'porn',
        'sex'
    ],
    resultType: [
        'mixed',
        'recent',
        'popular'
    ],
    responseString: [
        'Hi @screenName thanks for the follow! !CR',
        '@screenName thanks for following! !CR',
        'Hey @screenName thanks for the follow! !CR',
        'Thanks for following @screenName! !CR',
        'Hey @screenName, thanks for following! !CR',
        'Awesome @screenName, thanks for following! !CR',
        'Thanks for the follow @screenName! !CR',
        'Thanks for following @screenName! How are you today? !CR'
    ]
};
<commit_msg>remove empty strings :book:<commit_after>module.exports = {
    queryString: [
        ''
    ],
    queryStringSubQuery: [
        ''
    ],
    blockedStrings: [
        'porn',
        'sex'
    ],
    resultType: [
        'mixed',
        'recent',
        'popular'
    ],
    responseString: [
        'Hi @screenName thanks for the follow! !CR',
        '@screenName thanks for following! !CR',
        'Hey @screenName thanks for the follow! !CR',
        'Thanks for following @screenName! !CR',
        'Hey @screenName, thanks for following! !CR',
        'Awesome @screenName, thanks for following! !CR',
        'Thanks for the follow @screenName! !CR',
        'Thanks for following @screenName! How are you today? !CR'
    ]
};
<|endoftext|>