<commit_before>// ==========================================================================
//                        DG.ConnectingLineModel
//
//  Model for connecting lines between case icons in a scatterplot.
//  Points to connect are ordered by x-axis value by default, not case order.
//
//  Author:   Craig D. Miller
//
//  Copyright ©2012-13 Scientific Reasoning Research Institute,
//                  University of Massachusetts Amherst
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('components/graph/adornments/plot_adornment_model');

/**
 * @class  The model for a plotted average (mean or median).
 * @extends SC.Object
*/
DG.ConnectingLineModel = DG.PlotAdornmentModel.extend(
/** @scope DG.ConnectingLineModel.prototype */
{
  /**
   * @private
   * @property{{key: caseID, coordinates: [{ x,y }]}} arrays of one point per non-missing value in the plot keyed
   * by parent case ID
   */
  _values: null,

  values: function( iKey, iValues) {
    if( iValues) {
      this._values = iValues;
    }
    else {
      this.recomputeValueIfNeeded();
    }
    return this._values;
  }.property(),

  parentCollectionFirstAttribute: null,

  /**
   * True if we need to compute new values to match new cells.
   * Note that this does not detect data changes where means need recomputing anyway.
   * @return { Boolean }
   */
  isComputingNeeded: function() {
    return this._needsComputing && this.isVisible;
  },

  /**
   * Note that our mean values are out of date, for lazy evaluation.
   * Dependencies, which will require a recompute
   *  - case-attribute-values added/deleted/changed for the primary and secondary axis attribute(s)
   *  - primary or secondary axis attributes changed (from one attribute to another)
   *  - axis models changed (must be up to date when we use them here)
   */
  setComputingNeeded: function() {
    this._needsComputing = true;
  },

  /**
    Use the bounds of the given axes to recompute slope and intercept.
  */
  recomputeValueIfNeeded: function() {
    if( this.isComputingNeeded())
      this.recomputeValue();
  },

  /**
   * Will be set by ScatterPlotView on creation of ConnectingLineAdornment
   * @property {Function}
   */
  getLineColorFunc: null,

  /**
    * Compute or re-compute the lines. Creates an array of objects, each having a color and
    * an array of world coordinate values to connect, which also determines the connection order.
    */
  recomputeValue: function() {
    var tCases = this.getPath('plotModel.cases'),
        tFirstCase = tCases.at(0),
        tXVarID = this.getPath( 'plotModel.xVarID'),
        tYKey = 'plotModel.' + (this.getPath('plotModel.verticalAxisIsY2') ? 'y2VarID' : 'yVarID'),
        tYVarID = this.getPath(tYKey),
        /* For maps, plotModel is pointing to a mapDataConfiguration whereas for graphs it's pointing to a Plot. */
        tLegendDesc = this.getPath('plotModel.dataConfiguration.legendAttributeDescription') ||
            this.getPath('plotModel.legendAttributeDescription');
    if( !( tXVarID && tYVarID )) {
      return; // too early to recompute, caller must try again later.
    }

    var getCategoryMap = function() {
      var tMap = null,
          tLegendIsCategorical = tLegendDesc && tLegendDesc.get('isCategorical');
      if( tLegendIsCategorical && tFirstCase) {
        var tCaseParentCollectionID = tFirstCase.getPath('parent.collection.id'),
            tLegendCollID = tLegendDesc.getPath( 'collectionClient.id');
        if( tCaseParentCollectionID === tLegendCollID)
            tMap = tLegendDesc.getPath('attribute.categoryMap');
      }
      return tMap;
    }.bind( this);

    var tParent = tFirstCase ? tFirstCase.getPath('parent.collection') : null,
        tParentAttributes = tParent ? tParent.get('attrs') : null,
        tFirstParentAttribute = tParentAttributes ? tParentAttributes[0] : null;
    this.set('parentCollectionFirstAttribute', tFirstParentAttribute);

    // create an array of points to connect for each parent collection
    var tValues = {},
        tCategoryMap = getCategoryMap(),
        tGetLineColorFunc = this.get('getLineColorFunc');
    tCases.forEach( function( iCase, iIndex ) {
      var tXVal = iCase.getForcedNumericValue( tXVarID),
          tYVal = iCase.getForcedNumericValue( tYVarID ),
          tParent = iCase.get('parent' ),
          tParentID = tParent ? tParent.get('id') : 'top';
      if (isFinite(tXVal) && isFinite(tYVal)) { // if both values exist (else skip missing points)
        if (!tValues[tParentID]) {
          tValues[tParentID] = {
            color: (tCategoryMap ? (tCategoryMap[iCase.getValue(tLegendDesc.getPath('attribute.id'))]) :
                (tGetLineColorFunc ? tGetLineColorFunc() : null)),
            coordinates: []
          };
        }
        tValues[ tParentID].coordinates.push( { x: tXVal, y: tYVal, theCase: iCase } );
      }
    });

    this._needsComputing = false;
    this.set( 'values', tValues); // we expect view to observe this change
  },

  /**
    Private cache.
    @property { Boolean }
  */
  _needsComputing: true,

  /**
   * Pass this along
   * @param iParentID {Number} of parent
   * @param iExtend {Boolean}
   */
  selectParent: function( iParentID, iExtend) {
    var tDataContext = this.getPath('plotModel.dataContext'),
        tParent = tDataContext.getCaseByID( iParentID),
        tParentCaseIndex = tParent.get('collection').getCaseIndexByID( iParentID) + 1,
        tChange = {
          operation: 'selectCases',
          collection: tParent.get('collection'),
          cases: [tParent],
          select: true,
          extend: iExtend
        };
    SC.run(function () {
      tDataContext.applyChange(tChange);
      if (tChange.select)
        DG.logUser("lineSelected: %@", tParentCaseIndex);
      else
        DG.logUser("lineDeselected: %@", tParentCaseIndex);
    }.bind(this));
  }

});

DG.PlotAdornmentModel.registry.connectingLine = DG.ConnectingLineModel;

<commit_msg>Minor protection: Error was showing up when clicking on connected line with no parent<commit_after>// ==========================================================================
//                        DG.ConnectingLineModel
//
//  Model for connecting lines between case icons in a scatterplot.
//  Points to connect are ordered by x-axis value by default, not case order.
//
//  Author:   Craig D. Miller
//
//  Copyright ©2012-13 Scientific Reasoning Research Institute,
//                  University of Massachusetts Amherst
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// ==========================================================================

sc_require('components/graph/adornments/plot_adornment_model');

/**
 * @class  The model for a plotted average (mean or median).
 * @extends SC.Object
*/
DG.ConnectingLineModel = DG.PlotAdornmentModel.extend(
/** @scope DG.ConnectingLineModel.prototype */
{
  /**
   * @private
   * @property{{key: caseID, coordinates: [{ x,y }]}} arrays of one point per non-missing value in the plot keyed
   * by parent case ID
   */
  _values: null,

  values: function( iKey, iValues) {
    if( iValues) {
      this._values = iValues;
    }
    else {
      this.recomputeValueIfNeeded();
    }
    return this._values;
  }.property(),

  parentCollectionFirstAttribute: null,

  /**
   * True if we need to compute new values to match new cells.
   * Note that this does not detect data changes where means need recomputing anyway.
   * @return { Boolean }
   */
  isComputingNeeded: function() {
    return this._needsComputing && this.isVisible;
  },

  /**
   * Note that our mean values are out of date, for lazy evaluation.
   * Dependencies, which will require a recompute
   *  - case-attribute-values added/deleted/changed for the primary and secondary axis attribute(s)
   *  - primary or secondary axis attributes changed (from one attribute to another)
   *  - axis models changed (must be up to date when we use them here)
   */
  setComputingNeeded: function() {
    this._needsComputing = true;
  },

  /**
    Use the bounds of the given axes to recompute slope and intercept.
  */
  recomputeValueIfNeeded: function() {
    if( this.isComputingNeeded())
      this.recomputeValue();
  },

  /**
   * Will be set by ScatterPlotView on creation of ConnectingLineAdornment
   * @property {Function}
   */
  getLineColorFunc: null,

  /**
    * Compute or re-compute the lines. Creates an array of objects, each having a color and
    * an array of world coordinate values to connect, which also determines the connection order.
    */
  recomputeValue: function() {
    var tCases = this.getPath('plotModel.cases'),
        tFirstCase = tCases.at(0),
        tXVarID = this.getPath( 'plotModel.xVarID'),
        tYKey = 'plotModel.' + (this.getPath('plotModel.verticalAxisIsY2') ? 'y2VarID' : 'yVarID'),
        tYVarID = this.getPath(tYKey),
        /* For maps, plotModel is pointing to a mapDataConfiguration whereas for graphs it's pointing to a Plot. */
        tLegendDesc = this.getPath('plotModel.dataConfiguration.legendAttributeDescription') ||
            this.getPath('plotModel.legendAttributeDescription');
    if( !( tXVarID && tYVarID )) {
      return; // too early to recompute, caller must try again later.
    }

    var getCategoryMap = function() {
      var tMap = null,
          tLegendIsCategorical = tLegendDesc && tLegendDesc.get('isCategorical');
      if( tLegendIsCategorical && tFirstCase) {
        var tCaseParentCollectionID = tFirstCase.getPath('parent.collection.id'),
            tLegendCollID = tLegendDesc.getPath( 'collectionClient.id');
        if( tCaseParentCollectionID === tLegendCollID)
            tMap = tLegendDesc.getPath('attribute.categoryMap');
      }
      return tMap;
    }.bind( this);

    var tParent = tFirstCase ? tFirstCase.getPath('parent.collection') : null,
        tParentAttributes = tParent ? tParent.get('attrs') : null,
        tFirstParentAttribute = tParentAttributes ? tParentAttributes[0] : null;
    this.set('parentCollectionFirstAttribute', tFirstParentAttribute);

    // create an array of points to connect for each parent collection
    var tValues = {},
        tCategoryMap = getCategoryMap(),
        tGetLineColorFunc = this.get('getLineColorFunc');
    tCases.forEach( function( iCase, iIndex ) {
      var tXVal = iCase.getForcedNumericValue( tXVarID),
          tYVal = iCase.getForcedNumericValue( tYVarID ),
          tParent = iCase.get('parent' ),
          tParentID = tParent ? tParent.get('id') : 'top';
      if (isFinite(tXVal) && isFinite(tYVal)) { // if both values exist (else skip missing points)
        if (!tValues[tParentID]) {
          tValues[tParentID] = {
            color: (tCategoryMap ? (tCategoryMap[iCase.getValue(tLegendDesc.getPath('attribute.id'))]) :
                (tGetLineColorFunc ? tGetLineColorFunc() : null)),
            coordinates: []
          };
        }
        tValues[ tParentID].coordinates.push( { x: tXVal, y: tYVal, theCase: iCase } );
      }
    });

    this._needsComputing = false;
    this.set( 'values', tValues); // we expect view to observe this change
  },

  /**
    Private cache.
    @property { Boolean }
  */
  _needsComputing: true,

  /**
   * Pass this along
   * @param iParentID {Number} of parent
   * @param iExtend {Boolean}
   */
  selectParent: function( iParentID, iExtend) {
    if(!DG.isFinite(iParentID))
      return; // no parent
    var tDataContext = this.getPath('plotModel.dataContext'),
        tParent = tDataContext.getCaseByID( iParentID),
        tParentCaseIndex = tParent.get('collection').getCaseIndexByID( iParentID) + 1,
        tChange = {
          operation: 'selectCases',
          collection: tParent.get('collection'),
          cases: [tParent],
          select: true,
          extend: iExtend
        };
    SC.run(function () {
      tDataContext.applyChange(tChange);
      if (tChange.select)
        DG.logUser("lineSelected: %@", tParentCaseIndex);
      else
        DG.logUser("lineDeselected: %@", tParentCaseIndex);
    }.bind(this));
  }

});

DG.PlotAdornmentModel.registry.connectingLine = DG.ConnectingLineModel;

<|endoftext|>


<commit_before>module.exports = {
    parser: "babel-eslint",
    parserOptions: {
        sourceType: "module",
        ecmaFeatures: {
            jsx: true
        }
    },
    // plugins: ["react"],
    env: {
        es6: true,
        browser: true,
        node: true
    },
    settings: {
        react: {
            version: "16.7.0"
        }
      },
    extends: ["eslint:recommended", "plugin:react/recommended"],
    rules: {
        indent: [
            "error",
            4,
            {
                FunctionDeclaration: {parameters: "first"},
                FunctionExpression: {parameters: "first"},
                CallExpression: {arguments: "first"},
                ArrayExpression: "first",
                ObjectExpression: "first",
                ImportDeclaration: "first",
                ignoredNodes: ["JSXAttribute", "JSXSpreadAttribute", "ConditionalExpression", "TemplateLiteral"]
            }
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        quotes: [
            "error",
            "double"
        ],
        semi: [
            "error",
            "always"
        ],
        "no-var": [
            "error"
        ],
        // From Matt
        "arrow-body-style": ["error", "as-needed"],
        "brace-style": 2,
        "camelcase": 1,
        "comma-dangle": [1, "always-multiline"],
        "comma-spacing": 2,
        "comma-style":1,
        "computed-property-spacing": 1,
        "eol-last": 2,
        "eqeqeq": [2, "smart"],
        "keyword-spacing": 2,
        "no-trailing-spaces" : 2,
        "no-console": 0,
        "no-multi-spaces": 2,
        "no-useless-return": 1,
        "no-unused-vars": 1,
        "no-var": 2,
        "object-curly-newline": [1, { "multiline": true, "consistent": true }],
        "object-curly-spacing": [1, "never"],
        "object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }],
        "prefer-const": 2,
        "react/button-has-type": 1,
        "react/jsx-closing-bracket-location": 1,
        "react/jsx-first-prop-new-line": [2, "multiline"],
        "react/no-unused-state": 1,
        "space-infix-ops": 2,
        "space-before-blocks": 2,
        // From Gary
        "consistent-return": [0],
        "key-spacing": [0],
        "no-use-before-define": [2, "nofunc"],
        "jsx-quotes"       : 1,
        "react/display-name": 0,
        "react/forbid-prop-types": 0,
        "react/jsx-boolean-value": 1,
        // "react/jsx-closing-bracket-location": 0,
        "react/jsx-curly-spacing": 1,
        "react/jsx-handler-names": 0,
        "react/jsx-indent-props": [2, 4],
        "react/jsx-indent": 1,
        "react/jsx-key": 1,
        "react/jsx-max-props-per-line": [2, { "when": "multiline", "maximum": 1 }],
        "react/jsx-no-bind": 0,
        "react/jsx-no-duplicate-props": 1,
        "react/jsx-no-literals": 0,
        "react/jsx-no-undef": 1,
        "react/jsx-pascal-case": 1,
        "react/jsx-sort-prop-types": 0,
        "react/jsx-sort-props": 0,
        "react/jsx-uses-react": 1,
        "react/jsx-uses-vars": 1,
        "react/no-danger": 1,
        "react/no-deprecated": 1,
        "react/no-did-mount-set-state": 0,
        "react/no-did-update-set-state": 0,
        "react/no-direct-mutation-state": 1,
        "react/no-is-mounted": 1,
        "react/no-multi-comp": 0,
        "react/no-set-state": 0,
        "react/no-string-refs": 0,
        "react/no-unknown-property": 1,
        "react/prefer-es6-class": 1,
        "react/prop-types": 0,
        "react/react-in-jsx-scope": 1,
        // "react/require-extension": 1,
        "react/self-closing-comp": 0,
        "react/sort-comp": 1,
        // "react/wrap-multilines": 1
    }
};
<commit_msg>override new eslint recommended<commit_after>module.exports = {
    parser: "babel-eslint",
    parserOptions: {
        sourceType: "module",
        ecmaFeatures: {
            jsx: true,
        },
    },
    // plugins: ["react"],
    env: {
        es6: true,
        browser: true,
        node: true,
    },
    settings: {
        react: {
            version: "16.7.0",
        },
    },
    extends: ["eslint:recommended", "plugin:react/recommended"],
    rules: {
        indent: [
            "error",
            4,
            {
                FunctionDeclaration: {parameters: "first"},
                FunctionExpression: {parameters: "first"},
                CallExpression: {arguments: "first"},
                ArrayExpression: "first",
                ObjectExpression: "first",
                ImportDeclaration: "first",
                ignoredNodes: ["JSXAttribute", "JSXSpreadAttribute", "ConditionalExpression", "TemplateLiteral"],
            },
        ],
        "linebreak-style": [
            "error",
            "unix",
        ],
        quotes: [
            "error",
            "double",
        ],
        semi: [
            "error",
            "always",
        ],
        // From Matt
        "arrow-body-style": ["error", "as-needed"],
        "brace-style": 2,
        "camelcase": 1,
        "comma-dangle": [1, "always-multiline"],
        "comma-spacing": 2,
        "comma-style":1,
        "computed-property-spacing": 1,
        "eol-last": 2,
        "eqeqeq": [2, "smart"],
        "keyword-spacing": 2,
        "no-trailing-spaces" : 2,
        "no-console": 0,
        "no-multi-spaces": 2,
        "no-prototype-builtins": 0,
        "no-useless-return": 1,
        "no-unused-vars": 1,
        "no-var": 2,
        "object-curly-newline": [1, {"multiline": true, "consistent": true}],
        "object-curly-spacing": [1, "never"],
        "object-property-newline": ["error", {"allowAllPropertiesOnSameLine": true}],
        "prefer-const": 2,
        "react/button-has-type": 1,
        "react/jsx-closing-bracket-location": 1,
        "react/jsx-first-prop-new-line": [2, "multiline"],
        "react/no-unused-state": 1,
        "space-infix-ops": 2,
        "space-before-blocks": 2,
        // From Gary
        "consistent-return": [0],
        "key-spacing": [0],
        "no-use-before-define": [2, "nofunc"],
        "jsx-quotes"       : 1,
        "react/display-name": 0,
        "react/forbid-prop-types": 0,
        "react/jsx-boolean-value": 1,
        // "react/jsx-closing-bracket-location": 0,
        "react/jsx-curly-spacing": 1,
        "react/jsx-handler-names": 0,
        "react/jsx-indent-props": [2, 4],
        "react/jsx-indent": 1,
        "react/jsx-key": 1,
        "react/jsx-max-props-per-line": [2, {"when": "multiline", "maximum": 1}],
        "react/jsx-no-bind": 0,
        "react/jsx-no-duplicate-props": 1,
        "react/jsx-no-literals": 0,
        "react/jsx-no-undef": 1,
        "react/jsx-pascal-case": 1,
        "react/jsx-sort-prop-types": 0,
        "react/jsx-sort-props": 0,
        "react/jsx-uses-react": 1,
        "react/jsx-uses-vars": 1,
        "react/no-danger": 1,
        "react/no-deprecated": 1,
        "react/no-did-mount-set-state": 0,
        "react/no-did-update-set-state": 0,
        "react/no-direct-mutation-state": 1,
        "react/no-is-mounted": 1,
        "react/no-multi-comp": 0,
        "react/no-set-state": 0,
        "react/no-string-refs": 0,
        "react/no-unknown-property": 1,
        "react/prefer-es6-class": 1,
        "react/prop-types": 0,
        "react/react-in-jsx-scope": 1,
        // "react/require-extension": 1,
        "react/self-closing-comp": 0,
        "react/sort-comp": 1,
        // "react/wrap-multilines": 1
    },
};
<|endoftext|>


<commit_before>// editing the timetable

var WEEKDAY_NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];

$(function () {
	if (!window.localStorage) {
		$(".container").prepend($('
This browser is unsupported. Timetable functionality is unavailable.
').alert());
		return false;
	}

	if (localStorage.useTimetable) {
		document.getElementById("check-usetimetable").checked = true;
	} else {
		document.getElementById("check-usetimetable").checked = false;
		disablePage();
	}

	$("#check-usetimetable").click(toggleUseTimetable);

	$("#export").on("show.bs.modal", doExport);
	$("#add-subject").on("show.bs.modal", onOpenAddSubject);
	$("#edit-subject").on("show.bs.modal", onOpenEditSubject);

	showTimetable();
});

function showTimetable() {
	var subjects = JSON.parse(localStorage.classes || "[]"), periods = JSON.parse(localStorage.days || "[]");

	for (var p = 0; p < 6; p++) {
		var tds = $("#tt-p" + p + " td");
		for (var d = 1; d < 6; d++) {
			var td = $(tds[d]);
			timetableCellDisplay(td, d-1, p, subjects, periods);
			if (!(d === 3 && p === 4)) {
				td.click((function (day, pNum) {
					return function () {
						doOpenEditPeriod(day, pNum);
					};
				})(d-1, p));
			}
		}
	}
}

// disable everything (besides #check-usetimetable)
function disablePage() {
	// ...
}

function toggleUseTimetable() {
	if (localStorage.useTimetable) {
		localStorage.removeItem("useTimetable");
		disablePage();
	} else {
		localStorage.useTimetable = true;
	}
}

function timetableCellDisplay(td, day, pNum, subjects, periods) {
	if (day === 2 && pNum === 4) {
		//td.text("Sport");
		return;
	}
	var classes = periods[day];
	if (classes) {
		var period = classes[pNum];
		if (period) {
			if (period.classId === -2) {
				return;
			}
			if (period.classId === -1) {
				td.text("Study");
				return;
			}
			var subject = subjects[period.classId];
			var room = period.room || subject.room;
			if (room) {
				td.text(subject.subjectName + " (" + room + ")");
			} else {
				td.text(subject.subjectName);
			}
		}
	}
}

function resetTimetableDisplay() {
	var subjects = JSON.parse(localStorage.classes), periods = JSON.parse(localStorage.days);
	for (var p = 0; p < 6; p++) {
		var tds = $("#tt-p" + p + " td");
		for (var d = 1; d < 6; d++) {
			timetableCellDisplay($(tds[d]), d-1, p, subjects, periods);
		}
	}
}

function doImport() {
	var text = document.getElementById("text-import").value;
	var obj;
	try {
		obj = JSON.parse(text);
	} catch (ex) {}

	if (obj && (obj.classes || obj.days)) {
		if (obj.classes) {
			localStorage.classes = JSON.stringify(obj.classes);
		}
		if (obj.days) {
			localStorage.days = JSON.stringify(obj.days);
		}
		showTimetable();
	} else {
		alert("That doesn't look like timetable data!");
	}
}

function doExport() {
	$("#text-export").text(JSON.stringify({
		classes: localStorage.classes && JSON.parse(localStorage.classes),
		days: localStorage.days && JSON.parse(localStorage.days)
	}));
}

function onOpenAddSubject() {
	var inputName = document.getElementById("input-add-subject-name");
	inputName.value = "";
	document.getElementById("input-add-subject-room").value = "";
}

function onOpenEditSubject() {
	//
}

function doOpenEditPeriod(day, pNum) {
	$("#edit-period-day").text(WEEKDAY_NAMES[day]);
	$("#edit-period-num").text(pNum);
	document.getElementById("input-period-room").value = "";

	var subjects = JSON.parse(localStorage.classes), days = JSON.parse(localStorage.days || "[]"), classId;
	if (days[day] && days[day][pNum]) {
		classId = days[day][pNum].classId;
	}

	var selectSubjectElem = document.getElementById("select-period-subject");
	selectSubjectElem.innerHTML = "";

	var optElem = document.createElement("option");
	optElem.text = "--- Pick a subject ---";
	selectSubjectElem.add(optElem, null);

	for (var i = 0; i < subjects.length; i++) {
		optElem = document.createElement("option");
		optElem.value = i;
		optElem.text = subjects[i].subjectName;
		if (i === classId) {
			optElem.selected = true;
		}
		selectSubjectElem.add(optElem, null);
	}

	$("#edit-period").modal();
}

function doAddSubject() {
	var subjects = localStorage.classes ? JSON.parse(localStorage.classes) : [];
	subjects.push({
		subjectName: document.getElementById("input-add-subject-name").value,
		room: document.getElementById("input-add-subject-room").value
	});
	localStorage.classes = JSON.stringify(subjects);
	//alert("Done!");
}

function doDelSubject() {
	//
}

function doEditSubject() {
	//
}

function savePeriod() {
	var day = WEEKDAY_NAMES.indexOf($("#edit-period-day").text());
	if (day === -1) {
		console.error("unknown day in #edit-period-day text");
		return;
	}

	var pNum = $("#edit-period-num").text() >>> 0;
	if (pNum > 5) {
		console.error("#edit-period-num out of range");
		return;
	}

	var days = localStorage.days ? JSON.parse(localStorage.days) : [];
	var periods = days[day] || (days[day] = []);
	var period = periods[pNum] || (periods[pNum] = {});

	period.classId = document.getElementById("select-period-subject").value >>> 0;
	var subjects = JSON.parse(localStorage.classes)
	var subject = subjects[period.classId];
	var room = document.getElementById("input-period-room").value;
	if (room && room !== subject.room) {
		period.room = room;
	}

	periods[pNum] = period;
	localStorage.days = JSON.stringify(days);
	//alert("Done!");

	// damn you selectors and your 1-indexing
	timetableCellDisplay($("#tt-p" + pNum + " td:nth-child(" + (day+2) + ")"), day, pNum, subjects, days);
}

function doClearPeriod() {
	var dayName = $("#edit-period-day").text();
	var day = WEEKDAY_NAMES.indexOf(dayName);
	if (day === -1) {
		console.error("unknown day in #edit-period-day text");
		return;
	}

	var pNum = $("#edit-period-num").text() >>> 0;
	if (pNum > 5) {
		console.error("#edit-period-num out of range");
		return;
	}

	if (confirm("Are you sure you want to clear " + dayName + " period " + pNum + "?")) {
		clearPeriod(day, pNum);
	}
}

function clearPeriod(day, pNum) {
	var days = JSON.parse(localStorage.days);
	days[day][pNum] = {
		classId: (pNum === 0 || pNum === 5) ? -2 : -1
	};
	localStorage.days = JSON.stringify(days);
}

function doReset() {
	if (confirm("Are you sure you wish to reset your timetable data?")) {
		localStorage.classes = "[]";
		localStorage.days = "[]";
		//alert("Done!");
	}
}
<commit_msg>edit: fix import and clear period display update<commit_after>// editing the timetable

var WEEKDAY_NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];

$(function () {
	if (!window.localStorage) {
		$(".container").prepend($('
This browser is unsupported. Timetable functionality is unavailable.
').alert());
		return false;
	}

	if (localStorage.useTimetable) {
		document.getElementById("check-usetimetable").checked = true;
	} else {
		document.getElementById("check-usetimetable").checked = false;
		disablePage();
	}

	$("#check-usetimetable").click(toggleUseTimetable);

	$("#export").on("show.bs.modal", doExport);
	$("#add-subject").on("show.bs.modal", onOpenAddSubject);
	$("#edit-subject").on("show.bs.modal", onOpenEditSubject);

	var subjects = JSON.parse(localStorage.classes || "[]"), periods = JSON.parse(localStorage.days || "[]");

	for (var p = 0; p < 6; p++) {
		var tds = $("#tt-p" + p + " td");
		for (var d = 1; d < 6; d++) {
			var td = $(tds[d]);
			timetableCellDisplay(td, d-1, p, subjects, periods);
			if (!(d === 3 && p === 4)) {
				td.click((function (day, pNum) {
					return function () {
						doOpenEditPeriod(day, pNum);
					};
				})(d-1, p));
			}
		}
	}
});

// disable everything (besides #check-usetimetable)
function disablePage() {
	// ...
}

function toggleUseTimetable() {
	if (localStorage.useTimetable) {
		localStorage.removeItem("useTimetable");
		disablePage();
	} else {
		localStorage.useTimetable = true;
	}
}

function timetableCellDisplay(td, day, pNum, subjects, periods) {
	if (day === 2 && pNum === 4) {
		//td.text("Sport");
		return;
	}
	var classes = periods[day];
	if (classes) {
		var period = classes[pNum];
		if (period) {
			if (period.classId === -2) {
				return;
			}
			if (period.classId === -1) {
				td.text("Study");
				return;
			}
			var subject = subjects[period.classId];
			var room = period.room || subject.room;
			if (room) {
				td.text(subject.subjectName + " (" + room + ")");
			} else {
				td.text(subject.subjectName);
			}
		}
	}
}

function resetTimetableDisplay() {
	var subjects = JSON.parse(localStorage.classes), periods = JSON.parse(localStorage.days);
	for (var p = 0; p < 6; p++) {
		var tds = $("#tt-p" + p + " td");
		for (var d = 1; d < 6; d++) {
			timetableCellDisplay($(tds[d]), d-1, p, subjects, periods);
		}
	}
}

function doImport() {
	var text = document.getElementById("text-import").value;
	var obj;
	try {
		obj = JSON.parse(text);
	} catch (ex) {}

	if (obj && (obj.classes || obj.days)) {
		if (obj.classes) {
			localStorage.classes = JSON.stringify(obj.classes);
		}
		if (obj.days) {
			localStorage.days = JSON.stringify(obj.days);
		}
		resetTimetableDisplay();
	} else {
		alert("That doesn't look like timetable data!");
	}
}

function doExport() {
	$("#text-export").text(JSON.stringify({
		classes: localStorage.classes && JSON.parse(localStorage.classes),
		days: localStorage.days && JSON.parse(localStorage.days)
	}));
}

function onOpenAddSubject() {
	var inputName = document.getElementById("input-add-subject-name");
	inputName.value = "";
	document.getElementById("input-add-subject-room").value = "";
}

function onOpenEditSubject() {
	//
}

function doOpenEditPeriod(day, pNum) {
	$("#edit-period-day").text(WEEKDAY_NAMES[day]);
	$("#edit-period-num").text(pNum);
	document.getElementById("input-period-room").value = "";

	var subjects = JSON.parse(localStorage.classes), days = JSON.parse(localStorage.days || "[]"), classId;
	if (days[day] && days[day][pNum]) {
		classId = days[day][pNum].classId;
	}

	var selectSubjectElem = document.getElementById("select-period-subject");
	selectSubjectElem.innerHTML = "";

	var optElem = document.createElement("option");
	optElem.text = "--- Pick a subject ---";
	selectSubjectElem.add(optElem, null);

	for (var i = 0; i < subjects.length; i++) {
		optElem = document.createElement("option");
		optElem.value = i;
		optElem.text = subjects[i].subjectName;
		if (i === classId) {
			optElem.selected = true;
		}
		selectSubjectElem.add(optElem, null);
	}

	$("#edit-period").modal();
}

function doAddSubject() {
	var subjects = localStorage.classes ? JSON.parse(localStorage.classes) : [];
	subjects.push({
		subjectName: document.getElementById("input-add-subject-name").value,
		room: document.getElementById("input-add-subject-room").value
	});
	localStorage.classes = JSON.stringify(subjects);
	//alert("Done!");
}

function doDelSubject() {
	//
}

function doEditSubject() {
	//
}

function savePeriod() {
	var day = WEEKDAY_NAMES.indexOf($("#edit-period-day").text());
	if (day === -1) {
		console.error("unknown day in #edit-period-day text");
		return;
	}

	var pNum = $("#edit-period-num").text() >>> 0;
	if (pNum > 5) {
		console.error("#edit-period-num out of range");
		return;
	}

	var days = localStorage.days ? JSON.parse(localStorage.days) : [];
	var periods = days[day] || (days[day] = []);
	var period = periods[pNum] || (periods[pNum] = {});

	period.classId = document.getElementById("select-period-subject").value >>> 0;
	var subjects = JSON.parse(localStorage.classes);
	var subject = subjects[period.classId];
	var room = document.getElementById("input-period-room").value;
	if (room && room !== subject.room) {
		period.room = room;
	}

	periods[pNum] = period;
	localStorage.days = JSON.stringify(days);
	//alert("Done!");

	// damn you selectors and your 1-indexing
	timetableCellDisplay($("#tt-p" + pNum + " td:nth-child(" + (day+2) + ")"), day, pNum, subjects, days);
}

function doClearPeriod() {
	var dayName = $("#edit-period-day").text();
	var day = WEEKDAY_NAMES.indexOf(dayName);
	if (day === -1) {
		console.error("unknown day in #edit-period-day text");
		return;
	}

	var pNum = $("#edit-period-num").text() >>> 0;
	if (pNum > 5) {
		console.error("#edit-period-num out of range");
		return;
	}

	if (confirm("Are you sure you want to clear " + dayName + " period " + pNum + "?")) {
		clearPeriod(day, pNum);
	}
}

function clearPeriod(day, pNum) {
	var days = JSON.parse(localStorage.days);
	days[day][pNum] = {
		classId: (pNum === 0 || pNum === 5) ? -2 : -1
	};
	localStorage.days = JSON.stringify(days);
	timetableCellDisplay($("#tt-p" + pNum + " td:nth-child(" + (day+2) + ")"), day, pNum, JSON.parse(localStorage.subjects), days);
}

function doReset() {
	if (confirm("Are you sure you wish to reset your timetable data?")) {
		localStorage.classes = "[]";
		localStorage.days = "[]";
		//alert("Done!");
	}
}
<|endoftext|>


<commit_before>/*
 *
 * (c) Copyright Ascensio System Limited 2010-2018
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
 * EU, LV-1021.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
*/
/**
 *  HyperlinkSettingsDialog.js
 *
 *  Created by Alexander Yuzhin on 2/20/14
 *  Copyright (c) 2018 Ascensio System SIA. All rights reserved.
 *
 */

if (Common === undefined)
    var Common = {};

var c_oHyperlinkType = {
    InternalLink:0,
    WebLink: 1
};

define([
    'common/main/lib/util/utils',
    'common/main/lib/component/InputField',
    'common/main/lib/component/Window'
], function () { 'use strict';

    DE.Views.HyperlinkSettingsDialog = Common.UI.Window.extend(_.extend({
        options: {
            width: 350,
            style: 'min-width: 230px;',
            cls: 'modal-dlg'
        },

        initialize : function(options) {
            _.extend(this.options, {
                title: this.textTitle
            }, options || {});

            this.template = [
                '
',
                    '
',
                        '<button type="button" class="btn btn-text-default auto" id="id-dlg-hyperlink-external" style="border-top-right-radius: 0;border-bottom-right-radius: 0;">', this.textExternal,'</button>',
                        '<button type="button" class="btn btn-text-default auto" id="id-dlg-hyperlink-internal" style="border-top-left-radius: 0;border-bottom-left-radius: 0;">', this.textInternal,'</button>',
                    '
',
                    '
',
                        '
',
                            '' + this.textUrl + ' *',
                        '
',
                        '

',
                    '
',
                    '
',
                        '

',
                    '
',
                    '
',
                        '' + this.textDisplay + '',
                    '
',
                    '

',
                    '
',
                        '' + this.textTooltip + '',
                    '
',
                    '

',
                '
',
                '
',
                    '<button class="btn normal dlg-btn primary" result="ok" style="margin-right: 10px;">' + this.okButtonText + '</button>',
                    '<button class="btn normal dlg-btn" result="cancel">' + this.cancelButtonText + '</button>',
                '
'
            ].join('');

            this.options.tpl = _.template(this.template)(this.options);
            this.api = this.options.api;
            this._originalProps = null;

            Common.UI.Window.prototype.initialize.call(this, this.options);
        },

        render: function() {
            Common.UI.Window.prototype.render.call(this);

            var me = this,
                $window = this.getChild();

            me.btnExternal = new Common.UI.Button({
                el: $('#id-dlg-hyperlink-external'),
                enableToggle: true,
                toggleGroup: 'hyperlink-type',
                allowDepress: false,
                pressed: true
            });
            me.btnExternal.on('click', _.bind(me.onLinkTypeClick, me, c_oHyperlinkType.WebLink));

            me.btnInternal = new Common.UI.Button({
                el: $('#id-dlg-hyperlink-internal'),
                enableToggle: true,
                toggleGroup: 'hyperlink-type',
                allowDepress: false
            });
            me.btnInternal.on('click', _.bind(me.onLinkTypeClick, me, c_oHyperlinkType.InternalLink));

            me.inputUrl = new Common.UI.InputField({
                el          : $('#id-dlg-hyperlink-url'),
                allowBlank  : false,
                blankError  : me.txtEmpty,
                style       : 'width: 100%;',
                validateOnBlur: false,
                validation  : function(value) {
                    var urltype = me.api.asc_getUrlType($.trim(value));
                    me.isEmail = (urltype==2);
                    return (urltype>0) ? true : me.txtNotUrl;
                }
            });

            me.inputDisplay = new Common.UI.InputField({
                el          : $('#id-dlg-hyperlink-display'),
                allowBlank  : true,
                validateOnBlur: false,
                style       : 'width: 100%;'
            }).on('changed:after', function() {
                me.isTextChanged = true;
            });

            me.inputTip = new Common.UI.InputField({
                el          : $('#id-dlg-hyperlink-tip'),
                style       : 'width: 100%;',
                maxLength   : Asc.c_oAscMaxTooltipLength
            });

            $window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
            $window.find('input').on('keypress', _.bind(this.onKeyPress, this));
            me.externalPanel = $window.find('#id-external-link');
            me.internalPanel = $window.find('#id-internal-link');
        },

        ShowHideElem: function(value) {
            this.externalPanel.toggleClass('hidden', value !== c_oHyperlinkType.WebLink);
            this.internalPanel.toggleClass('hidden', value !== c_oHyperlinkType.InternalLink);
        },

        onLinkTypeClick: function(type, btn, event) {
            this.ShowHideElem(type);
        },

        show: function() {
            Common.UI.Window.prototype.show.apply(this, arguments);

            var me = this;
            _.delay(function(){
                me.inputUrl.cmpEl.find('input').focus();
            },500);
        },

        setSettings: function (props) {
            if (props) {
                var me = this;

                var type = c_oHyperlinkType.WebLink;//props.get_Type();
                (type == c_oHyperlinkType.WebLink) ? me.btnExternal.toggle(true) : me.btnInternal.toggle(true);
                me.ShowHideElem(type);

                if (props.get_Value()) {
                    me.inputUrl.setValue(props.get_Value().replace(new RegExp(" ",'g'), "%20"));
                } else {
                    me.inputUrl.setValue('');
                }

                if (props.get_Text() !== null) {
                    me.inputDisplay.setValue(props.get_Text());
                    me.inputDisplay.setDisabled(false);
                } else {
                    me.inputDisplay.setValue(this.textDefault);
                    me.inputDisplay.setDisabled(true);
                }

                this.isTextChanged = false;

                me.inputTip.setValue(props.get_ToolTip());
                me._originalProps = props;
            }
        },

        getSettings: function () {
            var me      = this,
                props   = new Asc.CHyperlinkProperty(),
                url     = $.trim(me.inputUrl.getValue());

            if (! /(((^https?)|(^ftp)):\/\/)|(^mailto:)/i.test(url) )
                url = ( (me.isEmail) ? 'mailto:' : 'http://' ) + url;

            url = url.replace(new RegExp("%20",'g')," ");
            props.put_Value(url);

            if (!me.inputDisplay.isDisabled() && ( this.isTextChanged || _.isEmpty(me.inputDisplay.getValue()))) {
                if (_.isEmpty(me.inputDisplay.getValue()))
                    me.inputDisplay.setValue(url);
                props.put_Text(me.inputDisplay.getValue());
            } else {
                props.put_Text(null);
            }

            props.put_ToolTip(me.inputTip.getValue());
            props.put_InternalHyperlink(me._originalProps.get_InternalHyperlink());

            return props;
        },

        onBtnClick: function(event) {
            this._handleInput(event.currentTarget.attributes['result'].value);
        },

        onKeyPress: function(event) {
            if (event.keyCode == Common.UI.Keys.RETURN) {
                this._handleInput('ok');
                return false;
            }
        },

        _handleInput: function(state) {
            if (this.options.handler) {
                if (state == 'ok') {
                    var checkurl = this.inputUrl.checkValidate(),
                        checkdisp = this.inputDisplay.checkValidate();
                    if (checkurl !== true)  {
                        this.inputUrl.cmpEl.find('input').focus();
                        return;
                    }
                    if (checkdisp !== true) {
                        this.inputDisplay.cmpEl.find('input').focus();
                        return;
                    }
                }

                this.options.handler.call(this, this, state);
            }

            this.close();
        },

        textUrl:            'Link to',
        textDisplay:        'Display',
        cancelButtonText:   'Cancel',
        okButtonText:       'Ok',
        txtEmpty:           'This field is required',
        txtNotUrl:          'This field should be a URL in the format \"http://www.example.com\"',
        textTooltip:        'ScreenTip text',
        textDefault:        'Selected text',
        textTitle:          'Hyperlink Settings',
        textExternal:       'External Link',
        textInternal:       'Place in Document'
    }, DE.Views.HyperlinkSettingsDialog || {}))
});<commit_msg>[DE] Add internal hyperlink<commit_after>/*
 *
 * (c) Copyright Ascensio System Limited 2010-2018
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
 * EU, LV-1021.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
*/
/**
 *  HyperlinkSettingsDialog.js
 *
 *  Created by Alexander Yuzhin on 2/20/14
 *  Copyright (c) 2018 Ascensio System SIA. All rights reserved.
 *
 */

if (Common === undefined)
    var Common = {};

var c_oHyperlinkType = {
    InternalLink:0,
    WebLink: 1
};

define([
    'common/main/lib/util/utils',
    'common/main/lib/component/InputField',
    'common/main/lib/component/Window'
], function () { 'use strict';

    DE.Views.HyperlinkSettingsDialog = Common.UI.Window.extend(_.extend({
        options: {
            width: 350,
            style: 'min-width: 230px;',
            cls: 'modal-dlg'
        },

        initialize : function(options) {
            _.extend(this.options, {
                title: this.textTitle
            }, options || {});

            this.template = [
                '
',
                    '
',
                        '<button type="button" class="btn btn-text-default auto" id="id-dlg-hyperlink-external" style="border-top-right-radius: 0;border-bottom-right-radius: 0;">', this.textExternal,'</button>',
                        '<button type="button" class="btn btn-text-default auto" id="id-dlg-hyperlink-internal" style="border-top-left-radius: 0;border-bottom-left-radius: 0;">', this.textInternal,'</button>',
                    '
',
                    '
',
                        '
',
                            '' + this.textUrl + ' *',
                        '
',
                        '

',
                    '
',
                    '
',
                        '

',
                    '
',
                    '
',
                        '' + this.textDisplay + '',
                    '
',
                    '

',
                    '
',
                        '' + this.textTooltip + '',
                    '
',
                    '

',
                '
',
                '
',
                    '<button class="btn normal dlg-btn primary" result="ok" style="margin-right: 10px;">' + this.okButtonText + '</button>',
                    '<button class="btn normal dlg-btn" result="cancel">' + this.cancelButtonText + '</button>',
                '
'
            ].join('');

            this.options.tpl = _.template(this.template)(this.options);
            this.api = this.options.api;
            this._originalProps = null;
            this.linkType = c_oHyperlinkType.WebLink;

            Common.UI.Window.prototype.initialize.call(this, this.options);
        },

        render: function() {
            Common.UI.Window.prototype.render.call(this);

            var me = this,
                $window = this.getChild();

            me.btnExternal = new Common.UI.Button({
                el: $('#id-dlg-hyperlink-external'),
                enableToggle: true,
                toggleGroup: 'hyperlink-type',
                allowDepress: false,
                pressed: true
            });
            me.btnExternal.on('click', _.bind(me.onLinkTypeClick, me, c_oHyperlinkType.WebLink));

            me.btnInternal = new Common.UI.Button({
                el: $('#id-dlg-hyperlink-internal'),
                enableToggle: true,
                toggleGroup: 'hyperlink-type',
                allowDepress: false
            });
            me.btnInternal.on('click', _.bind(me.onLinkTypeClick, me, c_oHyperlinkType.InternalLink));

            me.inputUrl = new Common.UI.InputField({
                el          : $('#id-dlg-hyperlink-url'),
                allowBlank  : false,
                blankError  : me.txtEmpty,
                style       : 'width: 100%;',
                validateOnBlur: false,
                validation  : function(value) {
                    var urltype = me.api.asc_getUrlType($.trim(value));
                    me.isEmail = (urltype==2);
                    return (urltype>0) ? true : me.txtNotUrl;
                }
            });

            me.inputDisplay = new Common.UI.InputField({
                el          : $('#id-dlg-hyperlink-display'),
                allowBlank  : true,
                validateOnBlur: false,
                style       : 'width: 100%;'
            }).on('changed:after', function() {
                me.isTextChanged = true;
            });

            me.inputTip = new Common.UI.InputField({
                el          : $('#id-dlg-hyperlink-tip'),
                style       : 'width: 100%;',
                maxLength   : Asc.c_oAscMaxTooltipLength
            });

            me.internalList = new Common.UI.TreeView({
                el: $('#id-dlg-hyperlink-list'),
                store: new Common.UI.TreeViewStore(),
                enableKeyEvents: false
            });

            $window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
            $window.find('input').on('keypress', _.bind(this.onKeyPress, this));
            me.externalPanel = $window.find('#id-external-link');
            me.internalPanel = $window.find('#id-internal-link');
        },

        ShowHideElem: function(value) {
            this.externalPanel.toggleClass('hidden', value !== c_oHyperlinkType.WebLink);
            this.internalPanel.toggleClass('hidden', value !== c_oHyperlinkType.InternalLink);
            var store = this.internalList.store;
            if (value==c_oHyperlinkType.InternalLink && store.length<1) {
                var anchors = this.api.asc_GetHyperlinkAnchors(),
                    count = anchors.length,
                    prev_level = 0,
                    header_level = 0,
                    arr = [];
                arr.push(new Common.UI.TreeViewModel({
                    name : this.txtBeginning,
                    level: 0,
                    index: 0,
                    hasParent: false,
                    isEmptyItem: false,
                    isNotHeader: true,
                    hasSubItems: false
                }));
                arr.push(new Common.UI.TreeViewModel({
                    name : this.txtHeadings,
                    level: 0,
                    index: 1,
                    hasParent: false,
                    isEmptyItem: false,
                    isNotHeader: false,
                    hasSubItems: false
                }));

                for (var i=0; i<count; i++) {
                    var anchor = anchors[i],
                        level = anchors[i].asc_GetHeadingLevel(),
                        hasParent = true;
                    if (anchor.asc_GetType()== Asc.c_oAscHyperlinkAnchor.Heading){
                        if (level>prev_level)
                            arr[arr.length-1].set('hasSubItems', true);
                        if (level<=header_level) {
                            header_level = level;
                            hasParent = false;
                        }
                        arr.push(new Common.UI.TreeViewModel({
                            name : anchor.asc_GetHeadingText(),
                            level: level,
                            index: i+2,
                            hasParent: hasParent
                        }));
                        prev_level = level;
                    }
                }
                arr.push(new Common.UI.TreeViewModel({
                    name : this.txtBookmarks,
                    level: 0,
                    index: arr.length,
                    hasParent: false,
                    isEmptyItem: false,
                    isNotHeader: false,
                    hasSubItems: false
                }));

                prev_level = 0;
                for (var i=0; i<count; i++) {
                    var anchor = anchors[i],
                        hasParent = true;
                    if (anchor.asc_GetType()== Asc.c_oAscHyperlinkAnchor.Bookmark){
                        if (prev_level<1)
                            arr[arr.length-1].set('hasSubItems', true);
                        arr.push(new Common.UI.TreeViewModel({
                            name : anchor.asc_GetBookmarkName(),
                            level: 1,
                            index: arr.length,
                            hasParent: false
                        }));
                        prev_level = 1;
                    }
                }
                store.reset(arr);
            }
            this.linkType = value;
        },

        onLinkTypeClick: function(type, btn, event) {
            this.ShowHideElem(type);
        },

        show: function() {
            Common.UI.Window.prototype.show.apply(this, arguments);

            var me = this;
            _.delay(function(){
                me.inputUrl.cmpEl.find('input').focus();
            },500);
        },

        setSettings: function (props) {
            if (props) {
                var me = this;

                var bookmark = props.get_Bookmark(),
                    type = (bookmark === null || bookmark=='') ? c_oHyperlinkType.WebLink : c_oHyperlinkType.InternalLink;

                (type == c_oHyperlinkType.WebLink) ? me.btnExternal.toggle(true) : me.btnInternal.toggle(true);
                me.ShowHideElem(type);

                if (type == c_oHyperlinkType.WebLink) {
                    if (props.get_Value()) {
                        me.inputUrl.setValue(props.get_Value().replace(new RegExp(" ",'g'), "%20"));
                    } else {
                        me.inputUrl.setValue('');
                    }
                } else {
                    if (props.is_TopOfDocument())
                        this.internalList.selectByIndex(0);
                    else {
                        var rec = this.internalList.store.findWhere({name: bookmark});
                        if (rec)
                            this.internalList.scrollToRecord(this.internalList.selectRecord(rec));
                    }
                }

                if (props.get_Text() !== null) {
                    me.inputDisplay.setValue(props.get_Text());
                    me.inputDisplay.setDisabled(false);
                } else {
                    me.inputDisplay.setValue(this.textDefault);
                    me.inputDisplay.setDisabled(true);
                }

                this.isTextChanged = false;

                me.inputTip.setValue(props.get_ToolTip());
                me._originalProps = props;
            }
        },

        getSettings: function () {
            var me      = this,
                props   = new Asc.CHyperlinkProperty();

            if (this.linkType == c_oHyperlinkType.WebLink) {
                var url     = $.trim(me.inputUrl.getValue());

                if (! /(((^https?)|(^ftp)):\/\/)|(^mailto:)/i.test(url) )
                    url = ( (me.isEmail) ? 'mailto:' : 'http://' ) + url;

                url = url.replace(new RegExp("%20",'g')," ");
                props.put_Value(url);
                props.put_Bookmark(null);
            } else {
                var rec = this.internalList.getSelectedRec();
                if (rec.length>0) {
                    props.put_Bookmark(rec[0].get('name'));
                    if (rec[0].get('index')==0)
                        props.put_TopOfDocument();
                }
            }

            if (!me.inputDisplay.isDisabled() && ( this.isTextChanged || _.isEmpty(me.inputDisplay.getValue()))) {
                if (_.isEmpty(me.inputDisplay.getValue()))
                    me.inputDisplay.setValue(url);
                props.put_Text(me.inputDisplay.getValue());
            } else {
                props.put_Text(null);
            }

            props.put_ToolTip(me.inputTip.getValue());
            props.put_InternalHyperlink(me._originalProps.get_InternalHyperlink());

            return props;
        },

        onBtnClick: function(event) {
            this._handleInput(event.currentTarget.attributes['result'].value);
        },

        onKeyPress: function(event) {
            if (event.keyCode == Common.UI.Keys.RETURN) {
                this._handleInput('ok');
                return false;
            }
        },

        _handleInput: function(state) {
            if (this.options.handler) {
                if (state == 'ok') {
                    if (this.linkType == c_oHyperlinkType.WebLink) {
                        if (this.inputUrl.checkValidate() !== true)  {
                            this.inputUrl.cmpEl.find('input').focus();
                            return;
                        }
                    } else {
                        var rec = this.internalList.getSelectedRec();
                        if (rec.length<1 || rec[0].get('level')==0 && rec[0].get('index')>0)
                            return;
                    }
                    if (this.inputDisplay.checkValidate() !== true) {
                        this.inputDisplay.cmpEl.find('input').focus();
                        return;
                    }
                }

                this.options.handler.call(this, this, state);
            }

            this.close();
        },

        textUrl:            'Link to',
        textDisplay:        'Display',
        cancelButtonText:   'Cancel',
        okButtonText:       'Ok',
        txtEmpty:           'This field is required',
        txtNotUrl:          'This field should be a URL in the format \"http://www.example.com\"',
        textTooltip:        'ScreenTip text',
        textDefault:        'Selected text',
        textTitle:          'Hyperlink Settings',
        textExternal:       'External Link',
        textInternal:       'Place in Document',
        txtBeginning: 'Beginning of document',
        txtHeadings: 'Headings',
        txtBookmarks: 'Bookmarks'
    }, DE.Views.HyperlinkSettingsDialog || {}))
});<|endoftext|>


<commit_before>module.exports = {
  "env": {
    "browser": true,
    "commonjs": true,
    "node": true
  },
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 6,
    "ecmaFeatures": {
      "jsxnec": true,
    },
  },
  "extends": "eslint:recommended",
  "globals": {
    "Promise": true,
    "Map": true
  },
  "rules": {
    // 0 - off, 1 - warning, 2 - error
    "indent": ["error", 2, { "SwitchCase": 1 }],
    "semi": [0, "always"],
    "comma-dangle": [2, "only-multiline"],
    "no-cond-assign": 2,
    "no-console": [2, { allow: ["warn", "info", "error", "assert"] }],
    "no-constant-condition": 2,
    "no-control-regex": 2,
    "no-debugger": 2,
    "no-dupe-args": 2,
    "no-dupe-keys": 2,
    "no-duplicate-case": 2,
    "no-empty": 1,
    "no-empty-character-class": 2,
    "no-ex-assign": 2,
    "no-extra-boolean-cast": 2,
    "no-extra-parens": 0,
    "no-extra-semi": 2,
    "no-func-assign": 2,
    "no-inner-declarations": 2,
    "no-invalid-regexp": 2,
    "no-irregular-whitespace": 2,
    "no-negated-in-lhs": 2,
    "no-obj-calls": 2,
    // turned of as we want to be able to use this.hasOwnProperty() for instance
    "no-prototype-builtins": 0,
    "no-regex-spaces": 2,
    "no-sparse-arrays": 0,
    "no-unexpected-multiline": 2,
    "no-unreachable": 2,
    "no-unsafe-finally": 2,
    "use-isnan": 2,
    "valid-jsdoc": 0,
    "valid-typeof": 2,
    "strict": [2, "safe"],

    // Best practices
    "accessor-pairs": 0,
    "array-callback-return": 2,
    "block-scoped-var": 2,
    "complexity": [0, 10],
    "consistent-return": 0,
    "curly": [2, "multi-line"],
    "default-case": 2,
    "dot-location": [2, 'property'],
    "dot-notation": 0,
    "eqeqeq": 2,
    "guard-for-in": 2,
    "no-alert": 2,
    "no-caller": 2,
    "no-case-declarations": 2,
    "no-div-regex": 2,
    "no-else-return": 0,
    "no-empty-function": 0,
    // if you want to check for undefined or null use lodash/isNil
    "no-eq-null": 2,
    "no-eval": 2,
    "no-extend-native": 2,
    "no-extra-bind": 2,
    "no-extra-label": 2,
    "no-fallthrough": 2,
    "no-floating-decimal": 2,
    "no-implicit-coercion": 2,
    "no-implicit-globals": 2,
    "no-implied-eval": 2,
    "no-invalid-this": 2,
    "no-iterator": 2,
    "no-labels": 2,
    "no-lone-blocks": 0,
    "no-loop-func": 2,
    "no-magic-numbers": 0,
    "no-multi-spaces": 2,
    "no-multi-str": 0,
    "no-native-reassign": 2,
    "no-new": 0,
    "no-new-func": 0,
    "no-new-wrappers": 2,
    "no-octal": 2,
    "no-octal-escape": 2,
    "no-param-reassign": 0,
    "no-proto": 2,
    "no-redeclare": 2,
    "no-return-assign": 2,
    "no-script-url": 2,
    "no-self-assign": 2,
    "no-self-compare": 2,
    "no-sequences": 2,
    "no-throw-literal": 2,
    "no-unmodified-loop-condition": 2,
    "no-unused-expressions": 2,
    "no-unused-labels": 2,
    "no-useless-call": 2,
    "no-useless-concat": 2,
    "no-useless-escape": 2,
    "no-void": 2,
    "no-warning-comments": 0,
    "no-with": 2,
    "radix": 2,
    "vars-on-top": 0,
    "wrap-iife": 2,
    "yoda": 0,
    // variables
    "init-declarations": 0,
    "no-catch-shadow": 2,
    "no-delete-var": 2,
    "no-label-var": 2,
    "no-restricted-globals": 2,
    "no-shadow": 0,
    "no-shadow-restricted-names": 2,
    "no-undef": 2,
    "no-undef-init": 2,
    "no-undefined": 0,
    "no-unused-vars": 2,
    "no-use-before-define": [2, { "functions": false }]
  }
};<commit_msg>Changed eslint config<commit_after>module.exports = {
  "env": {
    "browser": true,
    "commonjs": true,
    "node": true
  },
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 6,
    "ecmaFeatures": {
      "jsxnec": true,
    },
  },
  "extends": "eslint:recommended",
  "globals": {
    "Promise": true,
    "Map": true
  },
  "rules": {
    // 0 - off, 1 - warning, 2 - error
    "indent": ["error", 2, { "SwitchCase": 1 }],
    "semi": [0, "always"],
    "comma-dangle": [2, "only-multiline"],
    "no-cond-assign": 2,
    "no-console": [2, { allow: ["warn", "info", "error", "assert"] }],
    "no-constant-condition": 2,
    "no-control-regex": 2,
    "no-debugger": 2,
    "no-dupe-args": 2,
    "no-dupe-keys": 2,
    "no-duplicate-case": 2,
    "no-empty": 1,
    "no-empty-character-class": 2,
    "no-ex-assign": 2,
    "no-extra-boolean-cast": 2,
    "no-extra-parens": 0,
    "no-extra-semi": 2,
    "no-func-assign": 2,
    "no-inner-declarations": 2,
    "no-invalid-regexp": 2,
    "no-irregular-whitespace": 2,
    "no-negated-in-lhs": 2,
    "no-obj-calls": 2,
    // turned of as we want to be able to use this.hasOwnProperty() for instance
    "no-prototype-builtins": 0,
    "no-regex-spaces": 2,
    "no-sparse-arrays": 0,
    "no-unexpected-multiline": 2,
    "no-unreachable": 2,
    "no-unsafe-finally": 2,
    "use-isnan": 2,
    "valid-jsdoc": 0,
    "valid-typeof": 2,
    "strict": [2, "safe"],

    // Best practices
    "accessor-pairs": 0,
    "array-callback-return": 2,
    "block-scoped-var": 2,
    "complexity": [0, 10],
    "consistent-return": 0,
    "curly": [2, "multi-line"],
    "default-case": 2,
    "dot-location": [2, 'property'],
    "dot-notation": 0,
    "eqeqeq": 2,
    "guard-for-in": 2,
    "no-alert": 2,
    "no-caller": 2,
    "no-case-declarations": 2,
    "no-div-regex": 2,
    "no-else-return": 0,
    "no-empty-function": 0,
    // if you want to check for undefined or null use lodash/isNil
    "no-eq-null": 2,
    "no-eval": 2,
    "no-extend-native": 2,
    "no-extra-bind": 2,
    "no-extra-label": 2,
    "no-fallthrough": 2,
    "no-floating-decimal": 2,
    "no-implicit-coercion": 2,
    "no-implicit-globals": 2,
    "no-implied-eval": 2,
    "no-invalid-this": 2,
    "no-iterator": 2,
    "no-labels": 2,
    "no-lone-blocks": 0,
    "no-loop-func": 2,
    "no-magic-numbers": 0,
    "no-multi-spaces": 2,
    "no-multi-str": 0,
    "no-native-reassign": 2,
    "no-new": 0,
    "no-new-func": 0,
    "no-new-wrappers": 2,
    "no-octal": 2,
    "no-octal-escape": 2,
    "no-param-reassign": 0,
    "no-proto": 2,
    "no-redeclare": 2,
    "no-return-assign": 2,
    "no-script-url": 2,
    "no-self-assign": 2,
    "no-self-compare": 2,
    "no-sequences": 2,
    "no-throw-literal": 2,
    "no-unmodified-loop-condition": 2,
    "no-unused-expressions": 2,
    "no-unused-labels": 2,
    "no-useless-call": 2,
    "no-useless-concat": 2,
    "no-useless-escape": 2,
    "no-void": 2,
    "no-warning-comments": 0,
    "no-with": 2,
    "radix": 2,
    "vars-on-top": 0,
    "wrap-iife": 2,
    "yoda": 0,
    // variables
    "init-declarations": 0,
    "no-catch-shadow": 2,
    "no-delete-var": 2,
    "no-label-var": 2,
    "no-restricted-globals": 2,
    "no-shadow": 0,
    "no-shadow-restricted-names": 2,
    "no-undef": 2,
    "no-undef-init": 2,
    "no-undefined": 0,
    "no-unused-vars": 2,
    "no-use-before-define": [2, { "functions": false, "classes": false }]
  }
};<|endoftext|>


<commit_before>module.exports = {
    parser: "babel-eslint", // now needed for class properties
    parserOptions: {
        sourceType: "module",
        ecmaFeatures: {
        }
    },
    env: {
        browser: true,
        node: true,

        // babel's transform-runtime converts references to ES6 globals such as
        // Promise and Map to core-js polyfills, so we can use ES6 globals.
        es6: true,
    },
    extends: ["eslint:recommended", "google"],
    plugins: [
        "babel",
    ],
    rules: {
        // rules we've always adhered to or now do
        "max-len": ["error", {
            code: 90,
            ignoreComments: true,
        }],
        curly: ["error", "multi-line"],
        "prefer-const": ["error"],
        "comma-dangle": ["error", {
            arrays: "always-multiline",
            objects: "always-multiline",
            imports: "always-multiline",
            exports: "always-multiline",
            functions: "always-multiline",
        }],

        // loosen jsdoc requirements a little
        "require-jsdoc": ["error", {
            require: {
                FunctionDeclaration: false,
            }
        }],
        "valid-jsdoc": ["error", {
            requireParamDescription: false,
            requireReturn: false,
            requireReturnDescription: false,
        }],

        // rules we do not want from eslint-recommended
        "no-console": ["off"],
        "no-constant-condition": ["off"],
        "no-empty": ["error", { "allowEmptyCatch": true }],

        // rules we do not want from the google styleguide
        "object-curly-spacing": ["off"],
        "spaced-comment": ["off"],

        // in principle we prefer single quotes, but life is too short
        quotes: ["off"],

        // rules we'd ideally like to adhere to, but the current
        // code does not (in most cases because it's still ES5)
        // we set these to warnings, and assert that the number
        // of warnings doesn't exceed a given threshold
        "no-var": ["warn"],
        "brace-style": ["warn", "1tbs", {"allowSingleLine": true}],
        "prefer-rest-params": ["warn"],
        "prefer-spread": ["warn"],
        "one-var": ["warn"],
        "padded-blocks": ["warn"],
        "no-extend-native": ["warn"],
        "camelcase": ["warn"],
        "no-multi-spaces": ["error", { "ignoreEOLComments": true }],
        "space-before-function-paren": ["error", {
            "anonymous": "never",
            "named": "never",
            "asyncArrow": "always",
        }],
        "arrow-parens": "off",

        // eslint's built in no-invalid-this rule breaks with class properties
        "no-invalid-this": "off",
        // so we replace it with a version that is class property aware
        "babel/no-invalid-this": "error",
    }
}
<commit_msg>Disable guard-for-in rule<commit_after>module.exports = {
    parser: "babel-eslint", // now needed for class properties
    parserOptions: {
        sourceType: "module",
        ecmaFeatures: {
        }
    },
    env: {
        browser: true,
        node: true,

        // babel's transform-runtime converts references to ES6 globals such as
        // Promise and Map to core-js polyfills, so we can use ES6 globals.
        es6: true,
    },
    extends: ["eslint:recommended", "google"],
    plugins: [
        "babel",
    ],
    rules: {
        // rules we've always adhered to or now do
        "max-len": ["error", {
            code: 90,
            ignoreComments: true,
        }],
        curly: ["error", "multi-line"],
        "prefer-const": ["error"],
        "comma-dangle": ["error", {
            arrays: "always-multiline",
            objects: "always-multiline",
            imports: "always-multiline",
            exports: "always-multiline",
            functions: "always-multiline",
        }],

        // loosen jsdoc requirements a little
        "require-jsdoc": ["error", {
            require: {
                FunctionDeclaration: false,
            }
        }],
        "valid-jsdoc": ["error", {
            requireParamDescription: false,
            requireReturn: false,
            requireReturnDescription: false,
        }],

        // rules we do not want from eslint-recommended
        "no-console": ["off"],
        "no-constant-condition": ["off"],
        "no-empty": ["error", { "allowEmptyCatch": true }],

        // rules we do not want from the google styleguide
        "object-curly-spacing": ["off"],
        "spaced-comment": ["off"],
        "guard-for-in": ["off"],

        // in principle we prefer single quotes, but life is too short
        quotes: ["off"],

        // rules we'd ideally like to adhere to, but the current
        // code does not (in most cases because it's still ES5)
        // we set these to warnings, and assert that the number
        // of warnings doesn't exceed a given threshold
        "no-var": ["warn"],
        "brace-style": ["warn", "1tbs", {"allowSingleLine": true}],
        "prefer-rest-params": ["warn"],
        "prefer-spread": ["warn"],
        "one-var": ["warn"],
        "padded-blocks": ["warn"],
        "no-extend-native": ["warn"],
        "camelcase": ["warn"],
        "no-multi-spaces": ["error", { "ignoreEOLComments": true }],
        "space-before-function-paren": ["error", {
            "anonymous": "never",
            "named": "never",
            "asyncArrow": "always",
        }],
        "arrow-parens": "off",

        // eslint's built in no-invalid-this rule breaks with class properties
        "no-invalid-this": "off",
        // so we replace it with a version that is class property aware
        "babel/no-invalid-this": "error",
    }
}
<|endoftext|>


<commit_before>/**
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Salakar
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

import cuid from 'cuid';
import Promise from 'bluebird';
import { noop, sha1sum, tryJSONStringify, tryJSONParse } from 'redibox';

/**
 * @class Job
 */
class Job {

  /**
   *
   * @param core
   * @param id
   * @param data
   * @param options
   * @param queueName
   * @param isNew
   * @returns {*}
   */
  constructor(core, id, data = {}, options = {
    unique: false,
    timeout: 60000, // 1 minute default timeout
  }, queueName, isNew) {
    this.id = id;
    this.core = core;
    this.data = data;
    this.status = 'created';
    this.options = options;
    this.queueName = queueName;
    this.subscriptions = [];
    if (isNew) {
      // this Proxy allows chaining methods while still keeping the
      // save() promise valid
      this.proxy = new Proxy(this, {
        get(target, name) {
          if (name in target) {
            return target[name];
          }

          // haxxors
          if (name === 'then') {
            target.promise = target.save();
            return target.promise.then.bind(target.promise);
          }

          return undefined;
        },
      });
      return this.proxy;
    }
    return this;
  }

  /**
   * Query redis for the specified job id and converts it to a new instance of Job.
   * @static
   * @param queue
   * @param id
   */
  static fromId(queue, id) {
    return queue.client.hget(queue.toKey('jobs'), id).then((data) =>
      Job.fromData(queue, id, data)
    );
  }

  /**
   * Converts a JSON string of a job's data to a new instance of Job
   * @static
   * @param queue
   * @param id
   * @param data
   * @returns {Job | null}
   */
  static fromData(queue, id, data) {
    const obj = tryJSONParse(data);
    if (!obj) return null;
    const job = new Job(queue.core, id, obj.data, obj.options, queue.name);
    job.status = obj.data.status;
    return job;
  }

  /**
   * Convert this Job instance to a json string.
   */
  toData() {
    return tryJSONStringify({
      id: this.id,
      data: this.data,
      status: this.status,
      options: this.options,
    });
  }

  /**
   * Internal save that pushes to redis.
   * @private
   */
  _save() {
    this.core.log.verbose(`Saving new job ${this.id} for ${this.queueName}`);
    return this.core.client.addjob(
      this._toQueueKey('jobs'),
      this._toQueueKey('waiting'),
      this._toQueueKey('id'),
      this.toData(),
      !!this.options.unique,
      this.id).then((id) => {
        if (this.options.unique && id === 0) {
          this.status = 'duplicate';
          return Promise.reject(new Error(`ERR_DUPLICATE: Job ${this.id} already exists, save has been aborted.`));
        }
        this.core.log.verbose(`Saved job for ${this.queueName}`);
        this.id = id;
        this.status = 'saved';
        // this.queue.jobs[id] = this;
        return Promise.resolve(this);
      }
    );
  }

  /**
   * Save this instance of Job to redis. Any active queues will pick it up
   * immediately for processing.
   * @returns {*}
   */
  save() {
    this.id = `${this.queueName}-${(this.options.unique ? sha1sum(this.data) : cuid())}`;

    if (this.options.notifySuccess) {
      this.options.notifySuccess = `job:${this.id}:success`;
      this.subscriptions.push(`job:${this.id}:success`);
    }

    if (this.options.notifyFailure) {
      this.options.notifyFailure = `job:${this.id}:failure`;
      this.subscriptions.push(`job:${this.id}:failure`);
    }

    if (this.options.notifySuccess || this.options.notifyFailure) {
      return this.core.pubsub.subscribeOnceOf(this.subscriptions, (message) => { // on message received
        const channel = message.channel;

        // remove the pubsub data
        if (message.data) {
          message = message.data;
        }

        // if there's an error the assume failed.
        if (message.error) {
          return this.onFailureCallback(message);
        }

        // is it from the success channel.
        if (this.subscriptions[0] === channel) {
          return this.onSuccessCallback(message);
        }

        return this.onFailureCallback(message);
      }, this.options.timeout + 2000).then(() =>  // subscribed callback
        this._save()
      ).catch(error =>
        this.onFailureCallback({
          type: 'job',
          error: new Error('Error while subscribing to job events, however this job will still be queued - ' +
            'you may be unable to receive onComplete / onFailure events for this job.'),
          error_actual: error,
        })
      );
    }

    return this._save();
  }

  /**
   * Set the number of times this job will retry on failure
   * @param n
   * @returns {Job}
   */
  retries(n) {
    if (n < 0) {
      throw Error('Retries cannot be negative');
    }
    this.options.retries = n - 1;
    return this.proxy;
  }

  /**
   * Set the onSuccess callback and notify option
   * @param notify
   * @returns {Job}
   */
  onSuccess(notify) {
    this.options.notifySuccess = true;
    this.onSuccessCallback = notify;
    if (!this.onFailureCallback) this.onFailureCallback = noop;
    return this.proxy;
  }

  /**
   * Set the onFailure callback and notify option
   * @param notify
   * @returns {Job}
   */
  onFailure(notify) {
    this.options.notifyFailure = true;
    this.onFailureCallback = notify;
    if (!this.onSuccessCallback) this.onSuccessCallback = noop;
    return this.proxy;
  }

  /**
   *
   * @param bool
   * @returns {Job}
   */
  unique(bool) {
    this.options.unique = bool;
    return this.proxy;
  }

  /**
   * Set how long this job can run before it times out.
   * @param ms
   * @returns {Job}
   */
  timeout(ms) {
    this.options.timeout = ms;
    return this.proxy;
  }

  /**
   *
   * @returns {Job.initialJob|*}
   */
  initialJob() {
    return this._internalData.initialJob;
  }

  /**
   *
   * @returns {Job.initialQueue|*}
   */
  initialQueue() {
    return this._internalData.initialQueue;
  }

  /**
   * Remove this job from all sets.
   * @param cb
   */
  remove(cb = noop) {
    this.core.client.removejob(
      this._toQueueKey('succeeded'), this._toQueueKey('failed'), this._toQueueKey('waiting'),
      this._toQueueKey('active'), this._toQueueKey('stalling'), this._toQueueKey('jobs'),
      this.id, cb);
  }

  /**
   * Re-save this job for the purpose of retrying it.
   * @param cb
   */
  retry(cb = noop) {
    this.core.client.multi()
        .srem(this._toQueueKey('failed'), this.id)
        .lpush(this._toQueueKey('waiting'), this.id)
        .exec(cb);
  }

  /**
   * Callbacks true of false if this job exists in the specified set.
   * @param set
   * @param cb
   */
  isInSet(set, cb = noop) {
    this.core.client.sismember(this._toQueueKey(set), this.id, (err, result) => {
      if (err) return cb(err);
      return cb(null, result === 1);
    });
  }

  /**
   * Generates a queue prefixed key based on the provided string.
   * @param str
   * @returns {string}
   * @private
   */
  _toQueueKey(str) {
    if (this.core.cluster.isCluster()) {
      return `${this.core.hooks.job.options.keyPrefix}:{${this.queueName}}:${str}`;
    }
    return `${this.core.hooks.job.options.keyPrefix}:${this.queueName}:${str}`;
  }

}

export default Job;
<commit_msg>added pubsub subscriber enabled check<commit_after>/**
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Salakar
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

import cuid from 'cuid';
import Promise from 'bluebird';
import { noop, sha1sum, tryJSONStringify, tryJSONParse } from 'redibox';

/**
 * @class Job
 */
class Job {

  /**
   *
   * @param core
   * @param id
   * @param data
   * @param options
   * @param queueName
   * @param isNew
   * @returns {*}
   */
  constructor(core, id, data = {}, options = {
    unique: false,
    timeout: 60000, // 1 minute default timeout
  }, queueName, isNew) {
    this.id = id;
    this.core = core;
    this.data = data;
    this.status = 'created';
    this.options = options;
    this.queueName = queueName;
    this.subscriptions = [];
    if (isNew) {
      // this Proxy allows chaining methods while still keeping the
      // save() promise valid
      this.proxy = new Proxy(this, {
        get(target, name) {
          if (name in target) {
            return target[name];
          }

          // haxxors
          if (name === 'then') {
            target.promise = target.save();
            return target.promise.then.bind(target.promise);
          }

          return undefined;
        },
      });
      return this.proxy;
    }
    return this;
  }

  /**
   * Query redis for the specified job id and converts it to a new instance of Job.
   * @static
   * @param queue
   * @param id
   */
  static fromId(queue, id) {
    return queue.client.hget(queue.toKey('jobs'), id).then((data) =>
      Job.fromData(queue, id, data)
    );
  }

  /**
   * Converts a JSON string of a job's data to a new instance of Job
   * @static
   * @param queue
   * @param id
   * @param data
   * @returns {Job | null}
   */
  static fromData(queue, id, data) {
    const obj = tryJSONParse(data);
    if (!obj) return null;
    const job = new Job(queue.core, id, obj.data, obj.options, queue.name);
    job.status = obj.data.status;
    return job;
  }

  /**
   * Convert this Job instance to a json string.
   */
  toData() {
    return tryJSONStringify({
      id: this.id,
      data: this.data,
      status: this.status,
      options: this.options,
    });
  }

  /**
   * Internal save that pushes to redis.
   * @private
   */
  _save() {
    this.core.log.verbose(`Saving new job ${this.id} for ${this.queueName}`);
    return this.core.client.addjob(
      this._toQueueKey('jobs'),
      this._toQueueKey('waiting'),
      this._toQueueKey('id'),
      this.toData(),
      !!this.options.unique,
      this.id).then((id) => {
        if (this.options.unique && id === 0) {
          this.status = 'duplicate';
          return Promise.reject(new Error(`ERR_DUPLICATE: Job ${this.id} already exists, save has been aborted.`));
        }
        this.core.log.verbose(`Saved job for ${this.queueName}`);
        this.id = id;
        this.status = 'saved';
        // this.queue.jobs[id] = this;
        return Promise.resolve(this);
      }
    );
  }

  /**
   * Save this instance of Job to redis. Any active queues will pick it up
   * immediately for processing.
   * @returns {*}
   */
  save() {
    this.id = `${this.queueName}-${(this.options.unique ? sha1sum(this.data) : cuid())}`;

    if (this.options.notifySuccess) {
      this.options.notifySuccess = `job:${this.id}:success`;
      this.subscriptions.push(`job:${this.id}:success`);
    }

    if (this.options.notifyFailure) {
      this.options.notifyFailure = `job:${this.id}:failure`;
      this.subscriptions.push(`job:${this.id}:failure`);
    }

    if (this.options.notifySuccess || this.options.notifyFailure) {
      if (!this.core.pubsub.options.subscriber) {
        return Promise.reject(
          new Error('Cannot subscribe to job events when RediBox.pubsub \'subscriber\' config is set to disabled.')
        );
      }

      return this.core.pubsub.subscribeOnceOf(this.subscriptions, (message) => { // on message received
        const channel = message.channel;

        // remove the pubsub data
        if (message.data) {
          message = message.data;
        }

        // if there's an error the assume failed.
        if (message.error) {
          return this.onFailureCallback(message);
        }

        // is it from the success channel.
        if (this.subscriptions[0] === channel) {
          return this.onSuccessCallback(message);
        }

        return this.onFailureCallback(message);
      }, this.options.timeout + 2000).then(() =>  // subscribed callback
        this._save()
      ).catch(error =>
        this.onFailureCallback({
          type: 'job',
          error: new Error('Error while subscribing to job events, however this job will still be queued - ' +
            'you may be unable to receive onComplete / onFailure events for this job.'),
          error_actual: error,
        })
      );
    }

    return this._save();
  }

  /**
   * Set the number of times this job will retry on failure
   * @param n
   * @returns {Job}
   */
  retries(n) {
    if (n < 0) {
      throw Error('Retries cannot be negative');
    }
    this.options.retries = n - 1;
    return this.proxy;
  }

  /**
   * Set the onSuccess callback and notify option
   * @param notify
   * @returns {Job}
   */
  onSuccess(notify) {
    this.options.notifySuccess = true;
    this.onSuccessCallback = notify;
    if (!this.onFailureCallback) this.onFailureCallback = noop;
    return this.proxy;
  }

  /**
   * Set the onFailure callback and notify option
   * @param notify
   * @returns {Job}
   */
  onFailure(notify) {
    this.options.notifyFailure = true;
    this.onFailureCallback = notify;
    if (!this.onSuccessCallback) this.onSuccessCallback = noop;
    return this.proxy;
  }

  /**
   *
   * @param bool
   * @returns {Job}
   */
  unique(bool) {
    this.options.unique = bool;
    return this.proxy;
  }

  /**
   * Set how long this job can run before it times out.
   * @param ms
   * @returns {Job}
   */
  timeout(ms) {
    this.options.timeout = ms;
    return this.proxy;
  }

  /**
   *
   * @returns {Job.initialJob|*}
   */
  initialJob() {
    return this._internalData.initialJob;
  }

  /**
   *
   * @returns {Job.initialQueue|*}
   */
  initialQueue() {
    return this._internalData.initialQueue;
  }

  /**
   * Remove this job from all sets.
   * @param cb
   */
  remove(cb = noop) {
    this.core.client.removejob(
      this._toQueueKey('succeeded'), this._toQueueKey('failed'), this._toQueueKey('waiting'),
      this._toQueueKey('active'), this._toQueueKey('stalling'), this._toQueueKey('jobs'),
      this.id, cb);
  }

  /**
   * Re-save this job for the purpose of retrying it.
   * @param cb
   */
  retry(cb = noop) {
    this.core.client.multi()
        .srem(this._toQueueKey('failed'), this.id)
        .lpush(this._toQueueKey('waiting'), this.id)
        .exec(cb);
  }

  /**
   * Callbacks true of false if this job exists in the specified set.
   * @param set
   * @param cb
   */
  isInSet(set, cb = noop) {
    this.core.client.sismember(this._toQueueKey(set), this.id, (err, result) => {
      if (err) return cb(err);
      return cb(null, result === 1);
    });
  }

  /**
   * Generates a queue prefixed key based on the provided string.
   * @param str
   * @returns {string}
   * @private
   */
  _toQueueKey(str) {
    if (this.core.cluster.isCluster()) {
      return `${this.core.hooks.job.options.keyPrefix}:{${this.queueName}}:${str}`;
    }
    return `${this.core.hooks.job.options.keyPrefix}:${this.queueName}:${str}`;
  }

}

export default Job;
<|endoftext|>


<commit_before>module.exports = {
  "extends": "airbnb",
  // Required to validate the code as babel interprets it. Used for static properties
  "parser": "babel-eslint",
  "env": {
    "browser": true,
    "node": true,
    "mocha": true,
    "es6": true
  },
  "plugins": [
    "react",
    "jsx-a11y"
  ],
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "rules": {
    "arrow-parens": ["error", "as-needed"],
    "arrow-body-style": [2, "as-needed"],
    "comma-dangle": [2, "never"],
    "no-plusplus": [2, {
      "allowForLoopAfterthoughts": true,
    }],
    "no-unused-expressions": 1,
    "object-curly-spacing": [2, "never"],
    // Since for in/of loops are not permitted we have to loop with forEach and
    // therefore it's required to be able to assign to param properties
    "no-param-reassign": [2, {"props": false}],
    "import/imports-first": 0,
    "import/newline-after-import": 0,
    "import/no-dynamic-require": 0,
    "import/no-extraneous-dependencies": 0,
    "import/no-named-as-default": 0,
    "import/no-unresolved": 0,
    "import/prefer-default-export": 0,
    "indent": [2, 2, {
      "SwitchCase": 1
    }],
    "jsx-a11y/aria-props": 2,
    "jsx-a11y/heading-has-content": 0,
    "jsx-a11y/href-no-hash": 2,
    "jsx-a11y/mouse-events-have-key-events": 2,
    "jsx-a11y/role-has-required-aria-props": 2,
    "jsx-a11y/role-supports-aria-props": 2,
    "jsx-a11y/no-static-element-interactions": 0,
    "max-len": 0,
    "newline-per-chained-call": 0,
    "no-console": 2,
    "no-use-before-define": 0,
    "prefer-template": 2,
    "class-methods-use-this": 0,
    "react/forbid-prop-types": 0,
    "react/no-array-index-key": 0,
    "react/jsx-first-prop-new-line": [2, "multiline"],
    "react/jsx-filename-extension": 0,
    "react/jsx-no-target-blank": 0,
    "react/require-extension": 0,
    "react/self-closing-comp": 0,
    "react/prop-types": 0,
    "react/no-string-refs": 0,
    "react/prefer-stateless-function": 1,
    "require-yield": 0,
    "require-jsdoc": ["error", {
      "require": {
        "FunctionDeclaration": true,
        "MethodDefinition": true,
        "ClassDeclaration": true,
        "ArrowFunctionExpression": false
      }
    }],
    "valid-jsdoc": ["error", {
      "prefer": {"arg": "param", "argument": "param", "return": "returns", "virtual": "abstract"},
      "requireReturnDescription": false,
      "requireParamDescription": true,
    }],
  }
};
<commit_msg>added proposal for rule changes<commit_after>module.exports = {
  "extends": "airbnb",
  // Required to validate the code as babel interprets it. Used for static properties
  "parser": "babel-eslint",
  "env": {
    "browser": true,
    "node": true,
    "mocha": true,
    "es6": true
  },
  "plugins": [
    "react",
    "jsx-a11y"
  ],
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "rules": {
    "arrow-parens": ["error", "as-needed"],
    "arrow-body-style": [2, "as-needed"],
    "comma-dangle": [2, "never"],
    "no-plusplus": [2, {
      "allowForLoopAfterthoughts": true,
    }],
    "no-unused-expressions": 1,
    "object-curly-spacing": [2, "never"],
    // Since for in/of loops are not permitted we have to loop with forEach and
    // therefore it's required to be able to assign to param properties
    "no-param-reassign": [2, {"props": false}],
    "import/imports-first": 0,
    "import/newline-after-import": 0,
    "import/no-dynamic-require": 0,
    "import/no-extraneous-dependencies": 0,
    "import/no-named-as-default": 0,
    "import/no-unresolved": 0,
    "import/prefer-default-export": 0,
    "indent": [2, 2, {
      "SwitchCase": 1
    }],
    "jsx-a11y/aria-props": 2,
    "jsx-a11y/heading-has-content": 0,
    "jsx-a11y/href-no-hash": 2,
    "jsx-a11y/mouse-events-have-key-events": 2,
    "jsx-a11y/role-has-required-aria-props": 2,
    "jsx-a11y/role-supports-aria-props": 2,
    "jsx-a11y/no-static-element-interactions": 0,
    "max-len": 0,
    "newline-per-chained-call": 0,
    "no-console": 2,
    "no-use-before-define": 0,
    "prefer-template": 2,
    "class-methods-use-this": 0,
    "react/forbid-prop-types": 0,
    "react/no-array-index-key": 0,
    "react/jsx-first-prop-new-line": [2, "multiline"],
    "react/jsx-filename-extension": 0,
    "react/jsx-no-target-blank": 0,
    "react/require-extension": 0,
    "react/self-closing-comp": 0,
    "react/prop-types": 0,
    "react/no-string-refs": 0,
    "react/prefer-stateless-function": 1,
    "require-yield": 0,
    "require-jsdoc": ["error", {
      "require": {
        "FunctionDeclaration": true,
        "MethodDefinition": true,
        "ClassDeclaration": true,
        "ArrowFunctionExpression": false
      }
    }],
    "valid-jsdoc": ["error", {
      "prefer": {"arg": "param", "argument": "param", "return": "returns", "virtual": "abstract"},
      "requireReturn": false,
      "requireReturnDescription": false,
      "requireParamDescription": true,
    }],

    // PROPOSAL
    "no-return-assign": 0,
    "no-mixed-operators": 0
  }
};
<|endoftext|>


<commit_before>module.exports = {
	extends: "c74",
	rules : {
	},
	env : {
		es6: true,
		node: true,
		browser: true
	},
	parser: "babel-eslint",
	parserOptions: {
		sourceType: "module"
	}
}
<commit_msg>disabled no-param-reassign linting rule<commit_after>module.exports = {
	extends: "c74",
	rules : {
		"no-param-reassign": 0
	},
	env : {
		es6: true,
		node: true,
		browser: true
	},
	parser: "babel-eslint",
	parserOptions: {
		sourceType: "module"
	}
}
<|endoftext|>