<commit_before>import {
  ADD_NOTIFICATION, API_FAIL, UPDATE_COURSE, RECEIVE_COURSE, RECEIVE_COURSE_UPDATE,
  PERSISTED_COURSE, DISMISS_SURVEY_NOTIFICATION, TOGGLE_EDITING_SYLLABUS,
  START_SYLLABUS_UPLOAD, SYLLABUS_UPLOAD_SUCCESS, LINKED_TO_SALESFORCE, COURSE_SLUG_EXISTS
} from '../constants';
import API from '../utils/api.js';
import logErrorMessage from '../utils/log_error_message';
import CourseUtils from '../utils/course_utils';

export const fetchCourse = courseSlug => (dispatch) => {
  return API.fetch(courseSlug, 'course')
    .then(data => dispatch({ type: RECEIVE_COURSE, data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const refetchCourse = courseSlug => (dispatch) => {
  return API.fetch(courseSlug, 'course')
    .then(data => dispatch({ type: RECEIVE_COURSE_UPDATE, data }))
    // These periodic refetches will error if network connection is lost.
    // Missing a refetch is benign. Using `silent`, we don't clutter the
    // interface with error notifications.
    .catch(data => dispatch({ type: API_FAIL, data, silent: true }));
};

export const updateCourse = course => ({ type: UPDATE_COURSE, course });

export const resetCourse = () => (dispatch, getState) => {
  const persistedCourse = getState().persistedCourse;
  dispatch({ type: UPDATE_COURSE, course: { ...persistedCourse } });
};

export const nameHasChanged = () => (_dispatch, getState) => {
  const { course, persistedCourse } = getState();
  if (course.title !== persistedCourse.title) { return true; }
  if (course.term !== persistedCourse.term) { return true; }
  if (course.school !== persistedCourse.school) { return true; }
  return false;
};

const redirectCourse = (newSlug) => {
  if (!newSlug) { return; }
  window.location = `/courses/${newSlug}`;
};

const persistAndRedirect = (course, courseSlug, newSlug, dispatch) => {
  return API.saveCourse({ course }, courseSlug)
    .then(resp => dispatch({ type: PERSISTED_COURSE, data: resp }))
    .then(() => redirectCourse(newSlug))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const persistCourse = (courseSlug = null, redirect = false) => (dispatch, getState) => {
  let course = getState().course;

  let newSlug;
  if (redirect) {
    course = CourseUtils.cleanupCourseSlugComponents(course);
    newSlug = CourseUtils.generateTempId(course);
    course.slug = newSlug;
  }
  return persistAndRedirect(course, courseSlug, newSlug, dispatch);
};

export const updateClonedCourse = (course, courseSlug, newSlug) => (dispatch) => {
  // Ensure course name is unique
  return API.fetch(newSlug, 'check')
    .then((resp) => {
      // Course name is all good, so save it.
      if (!resp.course_exists) {
        return persistAndRedirect(course, courseSlug, newSlug, dispatch);
      }
      // Course name is taken, so show a warning.
      const message = 'This course already exists. Consider changing the name, school, or term to make it unique.';
      return dispatch({ type: COURSE_SLUG_EXISTS, message });
    })
    .catch(data => ({ type: API_FAIL, data }));
};

const needsUpdatePromise = (courseSlug) => {
  return fetch(`/courses/${courseSlug}/needs_update.json`)
     .then((response) => {
       return response.json();
     })
     .catch((err) => {
       logErrorMessage(err);
       return err;
     });
};

const needsUpdateNotification = (response) => {
  return {
    message: response.result,
    closable: true,
    type: 'success'
  };
};

export function needsUpdate(courseSlug) {
  return function (dispatch) {
    return needsUpdatePromise(courseSlug)
      .then(resp => dispatch({ type: ADD_NOTIFICATION, notification: needsUpdateNotification(resp) }))
      .catch(data => dispatch({ type: API_FAIL, data }));
  };
}

export const dismissNotification = id => (dispatch) => {
  return API.dismissNotification(id)
    .then(() => dispatch({ type: DISMISS_SURVEY_NOTIFICATION, id }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const toggleEditingSyllabus = () => {
  return { type: TOGGLE_EDITING_SYLLABUS };
};

export const uploadSyllabus = payload => (dispatch) => {
  dispatch({ type: START_SYLLABUS_UPLOAD });
  return API.uploadSyllabus(payload)
    .then(data => dispatch({ type: SYLLABUS_UPLOAD_SUCCESS, syllabus: data.url }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const linkToSalesforce = (courseId, salesforceId) => (dispatch) => {
  return API.linkToSalesforce(courseId, salesforceId)
    .then(data => dispatch({ type: LINKED_TO_SALESFORCE, data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

// Actions not handled by redux, except for failures

export const updateSalesforceRecord = courseId => (dispatch) => {
  return API.updateSalesforceRecord(courseId)
    .then(data => dispatch({ type: 'UPDATED_SALESFORCE_RECORD', data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const deleteCourse = courseSlug => (dispatch) => {
  return API.deleteCourse(courseSlug)
    .then(data => dispatch({ type: 'DELETED_COURSE', data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const notifyOverdue = courseSlug => (dispatch) => {
  return API.notifyOverdue(courseSlug)
    .then(data => dispatch({ type: 'NOTIFIED_OVERDUE', data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const greetStudents = courseId => (dispatch) => {
  return API.greetStudents(courseId)
    .then(data => dispatch({ type: 'GREETED_STUDENTS', data }))
    .catch(data => ({ type: API_FAIL, data }));
};
<commit_msg>ajax converted to fetch api<commit_after>import {
  ADD_NOTIFICATION, API_FAIL, UPDATE_COURSE, RECEIVE_COURSE, RECEIVE_COURSE_UPDATE,
  PERSISTED_COURSE, DISMISS_SURVEY_NOTIFICATION, TOGGLE_EDITING_SYLLABUS,
  START_SYLLABUS_UPLOAD, SYLLABUS_UPLOAD_SUCCESS, LINKED_TO_SALESFORCE, COURSE_SLUG_EXISTS
} from '../constants';
import API from '../utils/api.js';
import CourseUtils from '../utils/course_utils';

export const fetchCourse = courseSlug => (dispatch) => {
  return API.fetch(courseSlug, 'course')
    .then(data => dispatch({ type: RECEIVE_COURSE, data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const refetchCourse = courseSlug => (dispatch) => {
  return API.fetch(courseSlug, 'course')
    .then(data => dispatch({ type: RECEIVE_COURSE_UPDATE, data }))
    // These periodic refetches will error if network connection is lost.
    // Missing a refetch is benign. Using `silent`, we don't clutter the
    // interface with error notifications.
    .catch(data => dispatch({ type: API_FAIL, data, silent: true }));
};

export const updateCourse = course => ({ type: UPDATE_COURSE, course });

export const resetCourse = () => (dispatch, getState) => {
  const persistedCourse = getState().persistedCourse;
  dispatch({ type: UPDATE_COURSE, course: { ...persistedCourse } });
};

export const nameHasChanged = () => (_dispatch, getState) => {
  const { course, persistedCourse } = getState();
  if (course.title !== persistedCourse.title) { return true; }
  if (course.term !== persistedCourse.term) { return true; }
  if (course.school !== persistedCourse.school) { return true; }
  return false;
};

const redirectCourse = (newSlug) => {
  if (!newSlug) { return; }
  window.location = `/courses/${newSlug}`;
};

const persistAndRedirect = (course, courseSlug, newSlug, dispatch) => {
  return API.saveCourse({ course }, courseSlug)
    .then(resp => dispatch({ type: PERSISTED_COURSE, data: resp }))
    .then(() => redirectCourse(newSlug))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const persistCourse = (courseSlug = null, redirect = false) => (dispatch, getState) => {
  let course = getState().course;

  let newSlug;
  if (redirect) {
    course = CourseUtils.cleanupCourseSlugComponents(course);
    newSlug = CourseUtils.generateTempId(course);
    course.slug = newSlug;
  }
  return persistAndRedirect(course, courseSlug, newSlug, dispatch);
};

export const updateClonedCourse = (course, courseSlug, newSlug) => (dispatch) => {
  // Ensure course name is unique
  return API.fetch(newSlug, 'check')
    .then((resp) => {
      // Course name is all good, so save it.
      if (!resp.course_exists) {
        return persistAndRedirect(course, courseSlug, newSlug, dispatch);
      }
      // Course name is taken, so show a warning.
      const message = 'This course already exists. Consider changing the name, school, or term to make it unique.';
      return dispatch({ type: COURSE_SLUG_EXISTS, message });
    })
    .catch(data => ({ type: API_FAIL, data }));
};

const needsUpdatePromise = (courseSlug,dispatch) => {
  return API.fetch(courseSlug,'needs_update')
  .then(data => dispatch({ type: RECEIVE_COURSE_UPDATE, data }))
  .catch(data => dispatch({ type: API_FAIL, data, silent: true }))
}

const needsUpdateNotification = (response) => {
  return {
    message: response.result,
    closable: true,
    type: 'success'
  };
};

export function needsUpdate(courseSlug) {
  return function (dispatch) {
    return needsUpdatePromise(courseSlug)
      .then(resp => dispatch({ type: ADD_NOTIFICATION, notification: needsUpdateNotification(resp) }))
      .catch(data => dispatch({ type: API_FAIL, data }));
  };
}

export const dismissNotification = id => (dispatch) => {
  return API.dismissNotification(id)
    .then(() => dispatch({ type: DISMISS_SURVEY_NOTIFICATION, id }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const toggleEditingSyllabus = () => {
  return { type: TOGGLE_EDITING_SYLLABUS };
};

export const uploadSyllabus = payload => (dispatch) => {
  dispatch({ type: START_SYLLABUS_UPLOAD });
  return API.uploadSyllabus(payload)
    .then(data => dispatch({ type: SYLLABUS_UPLOAD_SUCCESS, syllabus: data.url }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const linkToSalesforce = (courseId, salesforceId) => (dispatch) => {
  return API.linkToSalesforce(courseId, salesforceId)
    .then(data => dispatch({ type: LINKED_TO_SALESFORCE, data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

// Actions not handled by redux, except for failures

export const updateSalesforceRecord = courseId => (dispatch) => {
  return API.updateSalesforceRecord(courseId)
    .then(data => dispatch({ type: 'UPDATED_SALESFORCE_RECORD', data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const deleteCourse = courseSlug => (dispatch) => {
  return API.deleteCourse(courseSlug)
    .then(data => dispatch({ type: 'DELETED_COURSE', data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const notifyOverdue = courseSlug => (dispatch) => {
  return API.notifyOverdue(courseSlug)
    .then(data => dispatch({ type: 'NOTIFIED_OVERDUE', data }))
    .catch(data => dispatch({ type: API_FAIL, data }));
};

export const greetStudents = courseId => (dispatch) => {
  return API.greetStudents(courseId)
    .then(data => dispatch({ type: 'GREETED_STUDENTS', data }))
    .catch(data => ({ type: API_FAIL, data }));
};
<|endoftext|>


<commit_before>// fix EN locale
Date.getLocale('en').short_no_year = '{d} {Mon}';

// create CS locale
Date.addLocale('cs', {
    'plural': true,
    'capitalizeUnit': false,
    'months': 'ledna,února,března,dubna,května,června,července,srpna,září,října,listopadu,prosince',
    'weekdays': 'neděle,pondělí,úterý,středa,čtvrtek,pátek,sobota',
    'units': 'milisekund:a|y||ou|ami,sekund:a|y||ou|ami,minut:a|y||ou|ami,hodin:a|y||ou|ami,den|dny|dnů|dnem|dny,týden|týdny|týdnů|týdnem|týdny,měsíc:|e|ů|em|emi,rok|roky|let|rokem|lety',
    'short': '{d}. {month} {yyyy}',
    'short_no_year': '{d}. {month}',
    'long': '{d}. {month} {yyyy} {H}:{mm}',
    'full': '{weekday} {d}. {month} {yyyy} {H}:{mm}:{ss}',
    'relative': function(num, unit, ms, format) {
        var numberWithUnit, last = num.toString().slice(-1);
        var mult;
        if (format === 'past' || format === 'future') {
            if (num === 1) mult = 3;
            else mult = 4;
        } else {
            if (num === 1) mult = 0;
            else if (num >= 2 && num <= 4) mult = 1;
            else mult = 2;
        }
        numberWithUnit = num + ' ' + this.units[(mult * 8) + unit];
        switch(format) {
            case 'duration':  return numberWithUnit;
            case 'past':      return 'před ' + numberWithUnit;
            case 'future':    return 'za ' + numberWithUnit;
        }
    }
});

// set the current date locale
Date.setLocale(I18n.locale);
<commit_msg>fix js dates in chinese<commit_after>// fix EN locale
Date.getLocale('en').short_no_year = '{d} {Mon}';

// create CS locale
Date.addLocale('cs', {
    'plural': true,
    'capitalizeUnit': false,
    'months': 'ledna,února,března,dubna,května,června,července,srpna,září,října,listopadu,prosince',
    'weekdays': 'neděle,pondělí,úterý,středa,čtvrtek,pátek,sobota',
    'units': 'milisekund:a|y||ou|ami,sekund:a|y||ou|ami,minut:a|y||ou|ami,hodin:a|y||ou|ami,den|dny|dnů|dnem|dny,týden|týdny|týdnů|týdnem|týdny,měsíc:|e|ů|em|emi,rok|roky|let|rokem|lety',
    'short': '{d}. {month} {yyyy}',
    'short_no_year': '{d}. {month}',
    'long': '{d}. {month} {yyyy} {H}:{mm}',
    'full': '{weekday} {d}. {month} {yyyy} {H}:{mm}:{ss}',
    'relative': function(num, unit, ms, format) {
        var numberWithUnit, last = num.toString().slice(-1);
        var mult;
        if (format === 'past' || format === 'future') {
            if (num === 1) mult = 3;
            else mult = 4;
        } else {
            if (num === 1) mult = 0;
            else if (num >= 2 && num <= 4) mult = 1;
            else mult = 2;
        }
        numberWithUnit = num + ' ' + this.units[(mult * 8) + unit];
        switch(format) {
            case 'duration':  return numberWithUnit;
            case 'past':      return 'před ' + numberWithUnit;
            case 'future':    return 'za ' + numberWithUnit;
        }
    }
});

// set the current date locale, replace underscore with dash to make zh_CN work
Date.setLocale(I18n.locale.replace("_","-"));
<|endoftext|>


<commit_before>var LogfileTable = function(rows) {

  var render = function(elem) {
    var tpl = $.getTemplate(
        "views/logviewer",
        "zbase_logviewer_logfile_table_tpl");

    var tbody = $("table tbody", tpl);
    for (var i = 0; i < rows.length; i++) {
      renderRow(tbody, rows[i]);
    }

    $.replaceContent(elem, tpl);
  }

  // FIXME combine the three renderRow methods below into 1 or 2 methods

  var renderRow = function(tbody, row) {
    if (row.columns.length > 0) {
      if (row.raw) {
        renderStructuredWithRawRow(tbody, row);
      } else {
        renderStructuredRow(tbody, row);
      }
    } else {
      renderRawRow(tbody, row);
    }
  }

  var renderRawRow = function(tbody, row) {
    var tr = document.createElement('tr');
    var folded_tr = document.createElement("tr");
    tr.className = "folding";
    folded_tr.className = "folded";

    tr.innerHTML =
        "	" +
        DateUtil.printTimestamp(row.time / 1000) +
        "	" + $.wrapText(row.raw) + "	";

    row.columns.forEach(function(column) {
      folded_tr.innerHTML += "" + $.wrapText(column) + "	";
    });

    tbody.appendChild(tr);
    tbody.appendChild(folded_tr);

    tr.addEventListener("click", function() {
      if (this.hasAttribute("data-active")) {
        this.removeAttribute("data-active");
      } else {
        this.setAttribute("data-active", "active");
      }
    }, false);
  };

  var renderStructuredWithRawRow = function(tbody, row) {
    var tr = document.createElement('tr');
    var folded_tr = document.createElement("tr");
    tr.className = "folding";

    tr.innerHTML =
      "	" +
      DateUtil.printTimestamp(row.time / 1000) +"	";

    row.columns.forEach(function(column) {
      tr.innerHTML += "" + $.wrapText(column) + "	";
    });

    folded_tr.className = "folded";
    folded_tr.innerHTML =
      "" +
      $.wrapText(row.raw) + "	";
    tbody.appendChild(tr);
    tbody.appendChild(folded_tr);

    tr.addEventListener("click", function() {
      if (this.hasAttribute("data-active")) {
        this.removeAttribute("data-active");
      } else {
        this.setAttribute("data-active", "active");
      }
    }, false);
  };

  this.renderStructuredRow = function(tbody, row) {
    var tr = document.createElement('tr');
    tr.innerHTML =
        "" + DateUtil.printTimestamp(row.time / 1000) +"	";

    row.columns.forEach(function(column) {
      tr.innerHTML += "" + $.wrapText(column) + "	";
    });

    tbody.appendChild(tr);
  };

  return {
    render: render
  };

};
<commit_msg>don't show toggle icon if there's nothing to show on open<commit_after>var LogfileTable = function(rows) {

  var render = function(elem) {
    var tpl = $.getTemplate(
        "views/logviewer",
        "zbase_logviewer_logfile_table_tpl");

    var tbody = $("table tbody", tpl);
    for (var i = 0; i < rows.length; i++) {
      renderRow(tbody, rows[i]);
    }

    $.replaceContent(elem, tpl);
  }

  // FIXME combine the three renderRow methods below into 1 or 2 methods

  var renderRow = function(tbody, row) {
    if (row.columns.length > 0) {
      if (row.raw) {
        renderStructuredWithRawRow(tbody, row);
      } else {
        renderStructuredRow(tbody, row);
      }
    } else {
      renderRawRow(tbody, row);
    }
  }

  var renderRawRow = function(tbody, row) {
    var tr = document.createElement('tr');
    tr.className = "folding";

    tr.innerHTML =
        "" +
        DateUtil.printTimestamp(row.time / 1000) +
        "	" + $.wrapText(row.raw) + "	";

    tbody.appendChild(tr);
  };

  var renderStructuredWithRawRow = function(tbody, row) {
    var tr = document.createElement('tr');
    var folded_tr = document.createElement("tr");
    tr.className = "folding";

    tr.innerHTML =
      "	" +
      DateUtil.printTimestamp(row.time / 1000) +"	";

    row.columns.forEach(function(column) {
      tr.innerHTML += "" + $.wrapText(column) + "	";
    });

    folded_tr.className = "folded";
    folded_tr.innerHTML =
      "" +
      $.wrapText(row.raw) + "	";
    tbody.appendChild(tr);
    tbody.appendChild(folded_tr);

    tr.addEventListener("click", function() {
      if (this.hasAttribute("data-active")) {
        this.removeAttribute("data-active");
      } else {
        this.setAttribute("data-active", "active");
      }
    }, false);
  };

  this.renderStructuredRow = function(tbody, row) {
    var tr = document.createElement('tr');
    tr.innerHTML =
        "" + DateUtil.printTimestamp(row.time / 1000) +"	";

    row.columns.forEach(function(column) {
      tr.innerHTML += "" + $.wrapText(column) + "	";
    });

    tbody.appendChild(tr);
  };

  return {
    render: render
  };

};
<|endoftext|>


<commit_before>(function() {
  function isButton(item) {
    return item.type === 'button';
  }

  function isButtonTwoState(item) {
    return item.type === 'buttonTwoState' && item.id.indexOf('view') === -1;
  }

  /**
  * Private method for subscribing to rxSubject.
  * For success functuon @see ToolbarController#onRowSelect()
  */
  function subscribeToSubject() {
    ManageIQ.angular.rxSubject.subscribe(function(event) {
      if (event.eventType === 'updateToolbarCount') {
        this.MiQToolbarSettingsService.setCount(event.countSelected);
      } else if (event.rowSelect) {
        this.onRowSelect(event.rowSelect);
      } else if (event.redrawToolbar) {
        this.onUpdateToolbar(event.redrawToolbar);
      } else if (event.update) {
        this.onUpdateItem(event);
      } else if (typeof event.setCount !== 'undefined') {
         this.onSetCount(event.setCount);
      }

      // sync changes
      if (! this.$scope.$$phase) {
        this.$scope.$digest();
      }
    }.bind(this),
    function(err) {
      console.error('Angular RxJs Error: ', err);
    },
    function() {
      console.debug('Angular RxJs subject completed, no more events to catch.');
    });
  }

  /**
  * Private method for setting rootPoint of MiQEndpointsService.
  * @param MiQEndpointsService service responsible for endpoits.
  */
  function initEndpoints(MiQEndpointsService) {
    var urlPrefix = '/' + location.pathname.split('/')[1];
    if (urlPrefix) {
      MiQEndpointsService.rootPoint = urlPrefix;
    }
  }

  /**
  * Constructor of angular's miqToolbarController.
  * @param MiQToolbarSettingsService toolbarSettings service from ui-components.
  * @param MiQEndpointsService endpoits service from ui-components.
  * @param $scope service for managing $scope (for apply and digest reasons).
  * @param $location service for managing browser's location.
  * this contructor will assign all params to `this`, it will init endpoits, set if toolbar is used on list page.
  */
  var ToolbarController = function(MiQToolbarSettingsService, MiQEndpointsService, $scope, $location) {
    this.MiQToolbarSettingsService = MiQToolbarSettingsService;
    this.MiQEndpointsService = MiQEndpointsService;
    this.$scope = $scope;
    this.$location = $location;
    initEndpoints(this.MiQEndpointsService);
    this.isList = _.contains(location.pathname, 'show_list');
  }

  /**
  * Public method which is executed after row in gtl is selected.
  */
  ToolbarController.prototype.onRowSelect = function(data) {
    this.MiQToolbarSettingsService.checkboxClicked(data.checked);
  }

  /**
  * Public method for setting up url of data views, based on last path param (e.g. /show_list).
  */
  ToolbarController.prototype.defaultViewUrl = function() {
    this.dataViews.forEach(function(item) {
      if (item.url === "") {
        var lastSlash = location.pathname.lastIndexOf('/');
        item.url = (lastSlash !== -1) ? location.pathname.substring(lastSlash): "";
      }
    });
  }

  /**
  * Method which will retrieves toolbar settings from server.
  * @see MiQToolbarSettingsService#getSettings for more info.
  * Settings is called with this.isList and $location search object with value of `type`.
  * No need to worry about multiple search params and no complicated function for parsing is needed.
  */
  ToolbarController.prototype.fetchData = function(getData) {
    return this.MiQToolbarSettingsService
      .getSettings(getData)
      .then(function(toolbarItems) {
        this.toolbarItems = toolbarItems.items;
        this.dataViews = toolbarItems.dataViews;
      }.bind(this));
  };

  ToolbarController.prototype.onSetCount = function(count) {
    this.MiQToolbarSettingsService.setCount(count);
    if(!this.$scope.$$phase) {
      this.$scope.$digest();
    }
  };

  ToolbarController.prototype.setClickHandler = function() {
    var buttons = _
      .chain(this.toolbarItems)
      .flatten()
      .map(function(item) {
        return (item && item.hasOwnProperty('items')) ? item.items : item;
      })
      .flatten()
      .filter(function(item) {
        return item.type &&
          (isButton(item) || isButtonTwoState(item))
      })
      .each(function(item) {
        item.eventFunction = function($event) {
          sendDataWithRx({tollbarEvent: 'itemClicked'});
          miqToolbarOnClick.bind($event.delegateTarget)($event);
        }
      })
      .value();
  };

  /**
   * Public method for changing view over data.
   */
  ToolbarController.prototype.onViewClick = function(item, $event) {
    if (item.url.indexOf('/') === 0) {
      var delimiter = (item.url === '/') ? '' : '/';
      var tail = (ManageIQ.record.recordId) ? delimiter + ManageIQ.record.recordId : '';

      location.replace('/' + ManageIQ.controller + item.url + tail + item.url_parms);
    } else {
      miqToolbarOnClick.bind($event.delegateTarget)($event);
    }
  };

  ToolbarController.prototype.initObject = function(toolbarString) {
    subscribeToSubject.bind(this)();
    this.updateToolbar(JSON.parse(toolbarString));
  };

  ToolbarController.prototype.onUpdateToolbar = function(toolbarObject) {
    this.updateToolbar(toolbarObject);
  };

  ToolbarController.prototype.onUpdateItem = function(updateData) {
    var toolbarItem = _.find(_.flatten(this.toolbarItems), {id: updateData.update});
    if (toolbarItem && toolbarItem.hasOwnProperty(updateData.type)) {
      toolbarItem[updateData.type] = updateData.value;
    }
  };

  ToolbarController.prototype.updateToolbar = function(toolbarObject) {
    toolbarItems = this.MiQToolbarSettingsService.generateToolbarObject(toolbarObject);
    this.toolbarItems = toolbarItems.items;
    this.dataViews = toolbarItems.dataViews;
    this.defaultViewUrl();
    this.setClickHandler();
  };

  ToolbarController.$inject = ['MiQToolbarSettingsService', 'MiQEndpointsService', '$scope', '$location'];
  miqHttpInject(angular.module('ManageIQ.toolbar'))
    .controller('miqToolbarController', ToolbarController);
})();
<commit_msg>ToolbarController#showOrHide - hide #toolbar div when toolbar empty<commit_after>(function() {
  function isButton(item) {
    return item.type === 'button';
  }

  function isButtonTwoState(item) {
    return item.type === 'buttonTwoState' && item.id.indexOf('view') === -1;
  }

  /**
  * Private method for subscribing to rxSubject.
  * For success functuon @see ToolbarController#onRowSelect()
  */
  function subscribeToSubject() {
    ManageIQ.angular.rxSubject.subscribe(function(event) {
      if (event.eventType === 'updateToolbarCount') {
        this.MiQToolbarSettingsService.setCount(event.countSelected);
      } else if (event.rowSelect) {
        this.onRowSelect(event.rowSelect);
      } else if (event.redrawToolbar) {
        this.onUpdateToolbar(event.redrawToolbar);
      } else if (event.update) {
        this.onUpdateItem(event);
      } else if (typeof event.setCount !== 'undefined') {
         this.onSetCount(event.setCount);
      }

      // sync changes
      if (! this.$scope.$$phase) {
        this.$scope.$digest();
      }
    }.bind(this),
    function(err) {
      console.error('Angular RxJs Error: ', err);
    },
    function() {
      console.debug('Angular RxJs subject completed, no more events to catch.');
    });
  }

  /**
  * Private method for setting rootPoint of MiQEndpointsService.
  * @param MiQEndpointsService service responsible for endpoits.
  */
  function initEndpoints(MiQEndpointsService) {
    var urlPrefix = '/' + location.pathname.split('/')[1];
    if (urlPrefix) {
      MiQEndpointsService.rootPoint = urlPrefix;
    }
  }

  /**
  * Constructor of angular's miqToolbarController.
  * @param MiQToolbarSettingsService toolbarSettings service from ui-components.
  * @param MiQEndpointsService endpoits service from ui-components.
  * @param $scope service for managing $scope (for apply and digest reasons).
  * @param $location service for managing browser's location.
  * this contructor will assign all params to `this`, it will init endpoits, set if toolbar is used on list page.
  */
  var ToolbarController = function(MiQToolbarSettingsService, MiQEndpointsService, $scope, $location) {
    this.MiQToolbarSettingsService = MiQToolbarSettingsService;
    this.MiQEndpointsService = MiQEndpointsService;
    this.$scope = $scope;
    this.$location = $location;
    initEndpoints(this.MiQEndpointsService);
    this.isList = _.contains(location.pathname, 'show_list');
  }

  /**
  * Public method which is executed after row in gtl is selected.
  */
  ToolbarController.prototype.onRowSelect = function(data) {
    this.MiQToolbarSettingsService.checkboxClicked(data.checked);
  }

  /**
  * Public method for setting up url of data views, based on last path param (e.g. /show_list).
  */
  ToolbarController.prototype.defaultViewUrl = function() {
    this.dataViews.forEach(function(item) {
      if (item.url === "") {
        var lastSlash = location.pathname.lastIndexOf('/');
        item.url = (lastSlash !== -1) ? location.pathname.substring(lastSlash): "";
      }
    });
  }

  /**
  * Method which will retrieves toolbar settings from server.
  * @see MiQToolbarSettingsService#getSettings for more info.
  * Settings is called with this.isList and $location search object with value of `type`.
  * No need to worry about multiple search params and no complicated function for parsing is needed.
  */
  ToolbarController.prototype.fetchData = function(getData) {
    return this.MiQToolbarSettingsService
      .getSettings(getData)
      .then(function(toolbarItems) {
        this.toolbarItems = toolbarItems.items;
        this.dataViews = toolbarItems.dataViews;
      }.bind(this));
  };

  ToolbarController.prototype.onSetCount = function(count) {
    this.MiQToolbarSettingsService.setCount(count);
    if(!this.$scope.$$phase) {
      this.$scope.$digest();
    }
  };

  ToolbarController.prototype.setClickHandler = function() {
    var buttons = _
      .chain(this.toolbarItems)
      .flatten()
      .map(function(item) {
        return (item && item.hasOwnProperty('items')) ? item.items : item;
      })
      .flatten()
      .filter(function(item) {
        return item.type &&
          (isButton(item) || isButtonTwoState(item))
      })
      .each(function(item) {
        item.eventFunction = function($event) {
          sendDataWithRx({tollbarEvent: 'itemClicked'});
          miqToolbarOnClick.bind($event.delegateTarget)($event);
        }
      })
      .value();
  };

  /**
   * Public method for changing view over data.
   */
  ToolbarController.prototype.onViewClick = function(item, $event) {
    if (item.url.indexOf('/') === 0) {
      var delimiter = (item.url === '/') ? '' : '/';
      var tail = (ManageIQ.record.recordId) ? delimiter + ManageIQ.record.recordId : '';

      location.replace('/' + ManageIQ.controller + item.url + tail + item.url_parms);
    } else {
      miqToolbarOnClick.bind($event.delegateTarget)($event);
    }
  };

  ToolbarController.prototype.initObject = function(toolbarString) {
    subscribeToSubject.bind(this)();
    this.updateToolbar(JSON.parse(toolbarString));
  };

  ToolbarController.prototype.onUpdateToolbar = function(toolbarObject) {
    this.updateToolbar(toolbarObject);
  };

  ToolbarController.prototype.onUpdateItem = function(updateData) {
    var toolbarItem = _.find(_.flatten(this.toolbarItems), {id: updateData.update});
    if (toolbarItem && toolbarItem.hasOwnProperty(updateData.type)) {
      toolbarItem[updateData.type] = updateData.value;
    }
  };

  ToolbarController.prototype.updateToolbar = function(toolbarObject) {
    var toolbarItems = this.MiQToolbarSettingsService.generateToolbarObject(toolbarObject);
    this.toolbarItems = toolbarItems.items;
    this.dataViews = toolbarItems.dataViews;
    this.defaultViewUrl();
    this.setClickHandler();
    this.showOrHide();
  };

  ToolbarController.prototype.showOrHide = function() {
    if (this.toolbarItems && this.toolbarItems.length) {
      $('#toolbar').show();
    } else {
      $('#toolbar').hide();
    }
  };

  ToolbarController.$inject = ['MiQToolbarSettingsService', 'MiQEndpointsService', '$scope', '$location'];
  miqHttpInject(angular.module('ManageIQ.toolbar'))
    .controller('miqToolbarController', ToolbarController);
})();
<|endoftext|>


<commit_before>khan_academy.search_result_window = function() {
	var _window, _listview, _detailview, _tabbar;

	var _init = function() {
		_window = control_factory.create_window({
			'title': 'Search Results'
		});

		_searchbar = control_factory.create_searchbar();

		_listview = new ListView();
		_detailview = new DetailView();
		_window.add(_detailview);
		_window.add(_listview);

		_tabbar = _create_tabbar();
		_window.setTitleControl(_tabbar);
	};

	var _create_tabbar = function() {
		_tabbar = Titanium.UI.iOS.createTabbedBar({
			labels: ['List', 'Details'],
			index: 0,
			style: Titanium.UI.iPhone.SystemButtonStyle.BAR,
			backgroundColor: '#5E8C1B'
		});

		// Click event for tabbar
		_tabbar.addEventListener('click', function(e) {
			if (0 === e.index) {
				_window.animate({view: _listview,
					transition: Titanium.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT
				});
			} else if (1 === e.index) {
				_window.animate({view: _detailview,
					transition: Titanium.UI.iPhone.AnimationStyle.FLIP_FROM_RIGHT
				});
			}
		});

		return _tabbar;
	};

	var _update = function( data ) {
		_listview.update(data);
		_detailview.update(data);
	};

	return {// publicly accessible API

		init : function() {
			return _init();
		},
		update: function( data ) {
			return _update( data );
		},
		get_window : function() {
			return _window;
		}
	};
};

var ListView = function() {
	_listview = Titanium.UI.createTableView({
		searchHidden: true,
		filterAttribute: 'filter'
	});

	var _create_row = function(brand) {
		var _image = Ti.UI.createImageView({
			top: 10,
			bottom: 10,
			left: 10,
			right: 10,
			image: brand.logos.full
		});

		var _row = Ti.UI.createTableViewRow({
			height: 80
		});
		_row.addEventListener('click', function() {
			Titanium.Platform.openURL(brand.urls.conversion_url);
		});
		_row.add(_image);
		return _row;
	};

	_listview.update = function( data ) {
		var rows = [];
		for (var i in data.results) {
			rows.push(_create_row(data.results[i].Brand));
		}
		_listview.setData( rows );
	};

	return _listview;
};

var DetailView = function() {
	var _detailview = Titanium.UI.createScrollableView({
		showPagingControl:true,
		pagingControlHeight:15,
		pagingControlAlpha: 0.5,
		currentPage:0
	});

	var _create_view = function( brand ) {
		var _label = Ti.UI.createLabel({
			top: 15,
			textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
			font: {fontSize: 20, fontWeight: 'bold'},
			color: '#C04C80',
			left: 10,
			right: 10,
			text: brand.name
		});
		var _image = Ti.UI.createImageView({
			top: 15,
			left: 10,
			right: 10,
			image: brand.logos.full
		});
		_image.addEventListener('click', function() {
			Titanium.Platform.openURL(brand.urls.conversion_url);
		});
		var _description = Ti.UI.createTextArea({
			editable: false,
			backgroundColor: 'transparent',
			left: 10,
			right: 10,
			bottom: 10,
			top: 15,
			font: {fontSize: 14, fontWeight: 'normal'},
			color: '#445555',
			value: brand.short_description.decode_html(),
			scrollable: true
		});

		var _view = Ti.UI.createView({
			top: 'auto',
			bottom: 'auto',
			layout: 'vertical',
			backgroundColor: 'white'
		});
		_view.add(_label);
		_view.add(_image);
		_view.add(_description);
		return _view;
	};

	_detailview.update = function( data ) {
		var _views = [];

		for (var i in data.results) {
			_views.push(_create_view(data.results[i].Brand));
		}
		_detailview.views = _views;
	};

	return _detailview;
};
<commit_msg>added learn more button<commit_after>khan_academy.search_result_window = function() {
	var _window, _listview, _detailview, _tabbar;

	var _init = function() {
		_window = control_factory.create_window({
			'title': 'Search Results'
		});

		_searchbar = control_factory.create_searchbar();

		_listview = new ListView();
		_detailview = new DetailView();
		_window.add(_detailview);
		_window.add(_listview);

		_tabbar = _create_tabbar();
		_window.setTitleControl(_tabbar);
	};

	var _create_tabbar = function() {
		_tabbar = Titanium.UI.iOS.createTabbedBar({
			labels: ['List', 'Details'],
			index: 0,
			style: Titanium.UI.iPhone.SystemButtonStyle.BAR,
			backgroundColor: '#5E8C1B'
		});

		// Click event for tabbar
		_tabbar.addEventListener('click', function(e) {
			if (0 === e.index) {
				_window.animate({view: _listview,
					transition: Titanium.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT
				});
			} else if (1 === e.index) {
				_window.animate({view: _detailview,
					transition: Titanium.UI.iPhone.AnimationStyle.FLIP_FROM_RIGHT
				});
			}
		});

		return _tabbar;
	};

	var _update = function( data ) {
		_listview.update(data);
		_detailview.update(data);
	};

	return {// publicly accessible API

		init : function() {
			return _init();
		},
		update: function( data ) {
			return _update( data );
		},
		get_window : function() {
			return _window;
		}
	};
};

var ListView = function() {
	_listview = Titanium.UI.createTableView({
		searchHidden: true,
		filterAttribute: 'filter'
	});

	var _create_row = function(brand) {
		var _image = Ti.UI.createImageView({
			top: 10,
			bottom: 10,
			left: 10,
			right: 10,
			image: brand.logos.full
		});

		var _row = Ti.UI.createTableViewRow({
			height: 80
		});
		_row.addEventListener('click', function() {
			Titanium.Platform.openURL(brand.urls.conversion_url);
		});
		_row.add(_image);
		return _row;
	};

	_listview.update = function( data ) {
		var rows = [];
		for (var i in data.results) {
			rows.push(_create_row(data.results[i].Brand));
		}
		_listview.setData( rows );
	};

	return _listview;
};

var DetailView = function() {
	var _detailview = Titanium.UI.createScrollableView({
		showPagingControl:true,
		backgroundColor: 'white',
		pagingControlHeight:15,
		pagingControlAlpha: 0.5,
		currentPage:0
	});

	var _create_view = function( brand ) {
		var _label = Ti.UI.createLabel({
			top: 15,
			textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
			font: {fontSize: 22, fontWeight: 'bold'},
			color: '#C04C80',
			left: 10,
			right: 10,
			text: brand.name
		});
		var _image = Ti.UI.createImageView({
			top: 15,
			left: 10,
			right: 10,
			image: brand.logos.full
		});
		_image.addEventListener('click', function() {
			Titanium.Platform.openURL(brand.urls.conversion_url);
		});
		var _description = Ti.UI.createTextArea({
			editable: false,
			backgroundColor: 'transparent',
			left: 10,
			right: 10,
			top: 15,
			font: {fontSize: 14, fontWeight: 'normal'},
			color: '#445555',
			value: brand.short_description.decode_html()
		});
		_learn_more = Ti.UI.createButton({
			title : 'Learn More',
			height : 45,
			left: 10,
			right: 10,
			bottom: 15,
			backgroundImage: '/images/btn_bg.png',
			color: '#C04C80',
			font: {fontSize: 18, fontWeight: 'bold'}
		});
		_learn_more.addEventListener('click', function() {
			Titanium.Platform.openURL(brand.urls.conversion_url);
		});

		var _view = Ti.UI.createScrollView({
			top: 'auto',
			bottom: 'auto',
			layout: 'vertical',
			scrollabe: true,
			backgroundColor: 'white'
		});
		_view.add(_label);
		_view.add(_image);
		_view.add(_description);
		_view.add(_learn_more);
		return _view;
	};

	_detailview.update = function( data ) {
		var _views = [];

		for (var i in data.results) {
			_views.push(_create_view(data.results[i].Brand));
		}
		_detailview.views = _views;
	};

	return _detailview;
};
<|endoftext|>


<commit_before>/*global Markdown:true, hljs:true */

/**
  Contains methods to help us with markdown formatting.

  @class Markdown
  @namespace Discourse
  @module Discourse
**/
var _validClasses = {},
    _validIframes = [],
    _validTags = {},
    _decoratedCaja = false;

function validateAttribute(tagName, attribName, value) {
  var tag = _validTags[tagName];

  // Handle classes
  if (attribName === "class") {
    if (_validClasses[value]) { return value; }

    if (tag) {
      var classes = tag['class'];
      if (classes && (classes.indexOf(value) !== -1 || classes.indexOf('*') !== -1)) {
        return value;
      }
    }
  } else if (attribName.indexOf('data-') === 0) {
    // data-* attributes
    if (tag) {
      var allowed = tag[attribName] || tag['data-*'];
      if (allowed && (allowed === value || allowed.indexOf('*') !== -1)) { return value; }
    }
  }

  if (tag) {
    var attrs = tag[attribName];
    if (attrs && (attrs.indexOf(value) !== -1 ||
                  attrs.indexOf('*') !== -1) ||
                  _.any(attrs,function(r){return (r instanceof RegExp) && value.search(r) >= 0;})
        ) { return value; }
  }
}

Discourse.Markdown = {

  /**
    Whitelist class for only a certain tag

    @param {String} tagName to whitelist
    @param {String} attribName to whitelist
    @param {String} value to whitelist
  **/
  whiteListTag: function(tagName, attribName, value) {
    _validTags[tagName] = _validTags[tagName] || {};
    _validTags[tagName][attribName] = _validTags[tagName][attribName] || [];
    _validTags[tagName][attribName].push(value || '*');
  },

  /**
    Whitelists more classes for sanitization.

    @param {...String} var_args Classes to whitelist
    @method whiteListClass
  **/
  whiteListClass: function() {
    var args = Array.prototype.slice.call(arguments);
    args.forEach(function (a) { _validClasses[a] = true; });
  },

  /**
    Whitelists iframes for sanitization

    @method whiteListIframe
    @param {Regexp} regexp The regexp to whitelist.
  **/
  whiteListIframe: function(regexp) {
    _validIframes.push(regexp);
  },

  /**
    Convert a raw string to a cooked markdown string.

    @method cook
    @param {String} raw the raw string we want to apply markdown to
    @param {Object} opts the options for the rendering
    @return {String} the cooked markdown string
  **/
  cook: function(raw, opts) {
    if (!opts) opts = {};

    // Make sure we've got a string
    if (!raw || raw.length === 0) return "";

    return this.markdownConverter(opts).makeHtml(raw);
  },

  /**
    Creates a new pagedown markdown editor, supplying i18n translations.

    @method createEditor
    @param {Object} converterOptions custom options for our markdown converter
    @return {Markdown.Editor} the editor instance
  **/
  createEditor: function(converterOptions) {
    if (!converterOptions) converterOptions = {};

    // By default we always sanitize content in the editor
    converterOptions.sanitize = true;

    var markdownConverter = Discourse.Markdown.markdownConverter(converterOptions);

    var editorOptions = {
      strings: {
        bold: I18n.t("composer.bold_title") + "  Ctrl+B",
        boldexample: I18n.t("composer.bold_text"),

        italic: I18n.t("composer.italic_title") + "  Ctrl+I",
        italicexample: I18n.t("composer.italic_text"),

        link: I18n.t("composer.link_title") + "  Ctrl+L",
        linkdescription: I18n.t("composer.link_description"),
        linkdialog: "
" + I18n.t("composer.link_dialog_title") + "

http://example.com/ \"" +
            I18n.t("composer.link_optional_text") + "\"
",

        quote: I18n.t("composer.quote_title") + "
 Ctrl+Q",
        quoteexample: I18n.t("composer.quote_text"),

        code: I18n.t("composer.code_title") + "
 Ctrl+K",
        codeexample: I18n.t("composer.code_text"),

        image: I18n.t("composer.upload_title") + " - Ctrl+G",
        imagedescription: I18n.t("composer.upload_description"),

        olist: I18n.t("composer.olist_title") + "
 Ctrl+O",
        ulist: I18n.t("composer.ulist_title") + "
 Ctrl+U",
        litem: I18n.t("composer.list_item"),

        heading: I18n.t("composer.heading_title") + "
/
 Ctrl+H",
        headingexample: I18n.t("composer.heading_text"),

        hr: I18n.t("composer.hr_title") + "
 Ctrl+R",

        undo: I18n.t("composer.undo_title") + " - Ctrl+Z",
        redo: I18n.t("composer.redo_title") + " - Ctrl+Y",
        redomac: I18n.t("composer.redo_title") + " - Ctrl+Shift+Z",

        help: I18n.t("composer.help")
      }
    };

    return new Markdown.Editor(markdownConverter, undefined, editorOptions);
  },

  /**
    Checks to see if a URL is allowed in the cooked content

    @method urlAllowed
    @param {String} uri Url to check
    @param {Number} effect ignored
    @param {Number} ltype ignored
    @param {Object} hints an object with hints, used to check if this url is from an iframe
    @return {String} url to insert in the cooked content
  **/
  urlAllowed: function (uri, effect, ltype, hints) {
    var url = typeof(uri) === "string" ? uri : uri.toString();

    // escape single quotes
    url = url.replace(/'/g, "%27");

    // whitelist some iframe only
    if (hints && hints.XML_TAG === "iframe" && hints.XML_ATTR === "src") {
      for (var i = 0, length = _validIframes.length; i < length; i++) {
        if(_validIframes[i].test(url)) { return url; }
      }
      return;
    }

    // absolute urls
    if(/^(https?:)?\/\/[\w\.\-]+/i.test(url)) { return url; }
    // relative urls
    if(/^\/[\w\.\-]+/i.test(url)) { return url; }
    // anchors
    if(/^#[\w\.\-]+/i.test(url)) { return url; }
    // mailtos
    if(/^mailto:[\w\.\-@]+/i.test(url)) { return url; }
  },

  /**
    Sanitize text using the sanitizer

    @method sanitize
    @param {String} text The text to sanitize
    @return {String} text The sanitized text
  **/
  sanitize: function(text) {
    if (!window.html_sanitize || !text) return "";

    // Allow things like <3 and <_<
    text = text.replace(/<([^A-Za-z\/\!]|$)/g, "<$1");

    // The first time, let's add some more whitelisted tags
    if (!_decoratedCaja) {

      // Add anything whitelisted to the list of elements if it's not in there
      // already.
      var elements = window.html4.ELEMENTS;
      Object.keys(_validTags).forEach(function(t) {
        if (!elements[t]) {
          elements[t] = 0;
        }
      });
      _decoratedCaja = true;
    }

    return window.html_sanitize(text, Discourse.Markdown.urlAllowed, validateAttribute);
  },

  /**
    Creates a Markdown.Converter that we we can use for formatting

    @method markdownConverter
    @param {Object} opts the converting options
  **/
  markdownConverter: function(opts) {
    if (!opts) opts = {};

    return {
      makeHtml: function(text) {
        text = Discourse.Dialect.cook(text, opts);
        return !text ? "" : text;
      }
    };
  }

};

RSVP.EventTarget.mixin(Discourse.Markdown);

Discourse.Markdown.whiteListTag('a', 'class', 'attachment');
Discourse.Markdown.whiteListTag('a', 'target', '_blank');
Discourse.Markdown.whiteListTag('a', 'class', 'onebox');
Discourse.Markdown.whiteListTag('a', 'class', 'mention');

Discourse.Markdown.whiteListTag('a', 'data-bbcode');
Discourse.Markdown.whiteListTag('a', 'name');

Discourse.Markdown.whiteListTag('img', 'src', /^data:image.*/i);

Discourse.Markdown.whiteListTag('div', 'class', 'title');
Discourse.Markdown.whiteListTag('div', 'class', 'quote-controls');

// explicitly whitelist classes we need allowed through for
// syntax highlighting, grabbed from highlight.js
hljs.listLanguages().forEach(function (language) {
  Discourse.Markdown.whiteListTag('code', 'class', language);
});
Discourse.Markdown.whiteListTag('code', 'class', 'text');
Discourse.Markdown.whiteListTag('code', 'class', 'lang-auto');

Discourse.Markdown.whiteListTag('span', 'class', 'mention');
Discourse.Markdown.whiteListTag('span', 'class', 'spoiler');
Discourse.Markdown.whiteListTag('div', 'class', 'spoiler');
Discourse.Markdown.whiteListTag('aside', 'class', 'quote');
Discourse.Markdown.whiteListTag('aside', 'data-*');

Discourse.Markdown.whiteListTag('span', 'bbcode-b');
Discourse.Markdown.whiteListTag('span', 'bbcode-i');
Discourse.Markdown.whiteListTag('span', 'bbcode-u');
Discourse.Markdown.whiteListTag('span', 'bbcode-s');

// used for pinned topics
Discourse.Markdown.whiteListTag('span', 'class', 'excerpt');

Discourse.Markdown.whiteListIframe(/^(https?:)?\/\/www\.google\.com\/maps\/embed\?.+/i);
<commit_msg>Auto-anchor attribute regexes, document _validTags<commit_after>/*global Markdown:true, hljs:true */

/**
  Contains methods to help us with markdown formatting.

  @class Markdown
  @namespace Discourse
  @module Discourse
**/

/**
 * An object mapping from HTML tag names to an object mapping the valid
 * attributes on that tag to an array of permitted values.
 *
 * The permitted values can be strings or regexes.
 *
 * The pseduo-attribute 'data-*' can be used to validate any data-foo
 * attributes without any specified validations.
 *
 * Code can insert into this map by calling Discourse.Markdown.whiteListTag().
 *
 * Example:
 *
 *

 * {
 *   a: {
 *     href: ['*'],
 *     data-mention-id: [/^\d+$/],
 *     ...
 *   },
 *   code: {
 *     class: ['ada', 'haskell', 'c', 'cpp', ... ]
 *   },
 *   ...
 * }
 *

 *
 * @private
 */
var _validTags = {};
/**
 * Classes valid on all elements. Map from class name to 'true'.
 * @private
 */
var _validClasses = {};
var _validIframes = [];
var _decoratedCaja = false;

function validateAttribute(tagName, attribName, value) {
  var tag = _validTags[tagName];

  // Handle classes
  if (attribName === "class") {
    if (_validClasses[value]) { return value; }

    if (tag) {
      var classes = tag['class'];
      if (classes && (classes.indexOf(value) !== -1 || classes.indexOf('*') !== -1)) {
        return value;
      }
    }
  } else if (attribName.indexOf('data-') === 0) {
    // data-* attributes
    if (tag) {
      var allowed = tag[attribName] || tag['data-*'];
      if (allowed && (allowed === value || allowed.indexOf('*') !== -1)) { return value; }
    }
  }

  if (tag) {
    var attrs = tag[attribName];
    if (attrs && (attrs.indexOf(value) !== -1 ||
                  attrs.indexOf('*') !== -1) ||
                  _.any(attrs,function(r){return (r instanceof RegExp) && value.search(r) >= 0;})
        ) { return value; }
  }
}

function anchorRegexp(regex) {
  if (/^\^.*\$$/.test(regex.source)) {
    return regex; // already anchored
  }

  var flags = "";
  if (regex.global) { throw "Invalid attribute validation regex - cannot be global"; }
  if (regex.ignoreCase) { flags += "i"; }
  if (regex.multiline) { flags += "m"; }
  if (regex.sticky) { throw "Invalid attribute validation regex - cannot be sticky"; }

  return new RegExp("^" + regex.source + "$", flags);
}

Discourse.Markdown = {

  /**
    Add to the attribute whitelist for a certain HTML tag.

    @param {String} tagName tag to whitelist the attr for
    @param {String} attribName attr to whitelist for the tag
    @param {String | RegExp} [value] whitelisted value for the attribute
  **/
  whiteListTag: function(tagName, attribName, value) {
    if (value instanceof RegExp) {
      value = anchorRegexp(value);
    }
    _validTags[tagName] = _validTags[tagName] || {};
    _validTags[tagName][attribName] = _validTags[tagName][attribName] || [];
    _validTags[tagName][attribName].push(value || '*');
  },

  /**
    Whitelists more classes for sanitization.

    @param {...String} var_args Classes to whitelist
    @method whiteListClass
  **/
  whiteListClass: function() {
    var args = Array.prototype.slice.call(arguments);
    args.forEach(function (a) { _validClasses[a] = true; });
  },

  /**
    Whitelists iframes for sanitization

    @method whiteListIframe
    @param {Regexp} regexp The regexp to whitelist.
  **/
  whiteListIframe: function(regexp) {
    _validIframes.push(regexp);
  },

  /**
    Convert a raw string to a cooked markdown string.

    @method cook
    @param {String} raw the raw string we want to apply markdown to
    @param {Object} opts the options for the rendering
    @return {String} the cooked markdown string
  **/
  cook: function(raw, opts) {
    if (!opts) opts = {};

    // Make sure we've got a string
    if (!raw || raw.length === 0) return "";

    return this.markdownConverter(opts).makeHtml(raw);
  },

  /**
    Creates a new pagedown markdown editor, supplying i18n translations.

    @method createEditor
    @param {Object} converterOptions custom options for our markdown converter
    @return {Markdown.Editor} the editor instance
  **/
  createEditor: function(converterOptions) {
    if (!converterOptions) converterOptions = {};

    // By default we always sanitize content in the editor
    converterOptions.sanitize = true;

    var markdownConverter = Discourse.Markdown.markdownConverter(converterOptions);

    var editorOptions = {
      strings: {
        bold: I18n.t("composer.bold_title") + "  Ctrl+B",
        boldexample: I18n.t("composer.bold_text"),

        italic: I18n.t("composer.italic_title") + "  Ctrl+I",
        italicexample: I18n.t("composer.italic_text"),

        link: I18n.t("composer.link_title") + "  Ctrl+L",
        linkdescription: I18n.t("composer.link_description"),
        linkdialog: "
" + I18n.t("composer.link_dialog_title") + "

http://example.com/ \"" +
            I18n.t("composer.link_optional_text") + "\"
",

        quote: I18n.t("composer.quote_title") + "
 Ctrl+Q",
        quoteexample: I18n.t("composer.quote_text"),

        code: I18n.t("composer.code_title") + "
 Ctrl+K",
        codeexample: I18n.t("composer.code_text"),

        image: I18n.t("composer.upload_title") + " - Ctrl+G",
        imagedescription: I18n.t("composer.upload_description"),

        olist: I18n.t("composer.olist_title") + "
 Ctrl+O",
        ulist: I18n.t("composer.ulist_title") + "
 Ctrl+U",
        litem: I18n.t("composer.list_item"),

        heading: I18n.t("composer.heading_title") + "
/
 Ctrl+H",
        headingexample: I18n.t("composer.heading_text"),

        hr: I18n.t("composer.hr_title") + "
 Ctrl+R",

        undo: I18n.t("composer.undo_title") + " - Ctrl+Z",
        redo: I18n.t("composer.redo_title") + " - Ctrl+Y",
        redomac: I18n.t("composer.redo_title") + " - Ctrl+Shift+Z",

        help: I18n.t("composer.help")
      }
    };

    return new Markdown.Editor(markdownConverter, undefined, editorOptions);
  },

  /**
    Checks to see if a URL is allowed in the cooked content

    @method urlAllowed
    @param {String} uri Url to check
    @param {Number} effect ignored
    @param {Number} ltype ignored
    @param {Object} hints an object with hints, used to check if this url is from an iframe
    @return {String} url to insert in the cooked content
  **/
  urlAllowed: function (uri, effect, ltype, hints) {
    var url = typeof(uri) === "string" ? uri : uri.toString();

    // escape single quotes
    url = url.replace(/'/g, "%27");

    // whitelist some iframe only
    if (hints && hints.XML_TAG === "iframe" && hints.XML_ATTR === "src") {
      for (var i = 0, length = _validIframes.length; i < length; i++) {
        if(_validIframes[i].test(url)) { return url; }
      }
      return;
    }

    // absolute urls
    if(/^(https?:)?\/\/[\w\.\-]+/i.test(url)) { return url; }
    // relative urls
    if(/^\/[\w\.\-]+/i.test(url)) { return url; }
    // anchors
    if(/^#[\w\.\-]+/i.test(url)) { return url; }
    // mailtos
    if(/^mailto:[\w\.\-@]+/i.test(url)) { return url; }
  },

  /**
    Sanitize text using the sanitizer

    @method sanitize
    @param {String} text The text to sanitize
    @return {String} text The sanitized text
  **/
  sanitize: function(text) {
    if (!window.html_sanitize || !text) return "";

    // Allow things like <3 and <_<
    text = text.replace(/<([^A-Za-z\/\!]|$)/g, "<$1");

    // The first time, let's add some more whitelisted tags
    if (!_decoratedCaja) {

      // Add anything whitelisted to the list of elements if it's not in there
      // already.
      var elements = window.html4.ELEMENTS;
      Object.keys(_validTags).forEach(function(t) {
        if (!elements[t]) {
          elements[t] = 0;
        }
      });
      _decoratedCaja = true;
    }

    return window.html_sanitize(text, Discourse.Markdown.urlAllowed, validateAttribute);
  },

  /**
    Creates a Markdown.Converter that we we can use for formatting

    @method markdownConverter
    @param {Object} opts the converting options
  **/
  markdownConverter: function(opts) {
    if (!opts) opts = {};

    return {
      makeHtml: function(text) {
        text = Discourse.Dialect.cook(text, opts);
        return !text ? "" : text;
      }
    };
  }

};

RSVP.EventTarget.mixin(Discourse.Markdown);

Discourse.Markdown.whiteListTag('a', 'class', 'attachment');
Discourse.Markdown.whiteListTag('a', 'target', '_blank');
Discourse.Markdown.whiteListTag('a', 'class', 'onebox');
Discourse.Markdown.whiteListTag('a', 'class', 'mention');

Discourse.Markdown.whiteListTag('a', 'data-bbcode');
Discourse.Markdown.whiteListTag('a', 'name');

Discourse.Markdown.whiteListTag('img', 'src', /^data:image.*$/i);

Discourse.Markdown.whiteListTag('div', 'class', 'title');
Discourse.Markdown.whiteListTag('div', 'class', 'quote-controls');

// explicitly whitelist classes we need allowed through for
// syntax highlighting, grabbed from highlight.js
hljs.listLanguages().forEach(function (language) {
  Discourse.Markdown.whiteListTag('code', 'class', language);
});
Discourse.Markdown.whiteListTag('code', 'class', 'text');
Discourse.Markdown.whiteListTag('code', 'class', 'lang-auto');

Discourse.Markdown.whiteListTag('span', 'class', 'mention');
Discourse.Markdown.whiteListTag('span', 'class', 'spoiler');
Discourse.Markdown.whiteListTag('div', 'class', 'spoiler');
Discourse.Markdown.whiteListTag('aside', 'class', 'quote');
Discourse.Markdown.whiteListTag('aside', 'data-*');

Discourse.Markdown.whiteListTag('span', 'bbcode-b');
Discourse.Markdown.whiteListTag('span', 'bbcode-i');
Discourse.Markdown.whiteListTag('span', 'bbcode-u');
Discourse.Markdown.whiteListTag('span', 'bbcode-s');

// used for pinned topics
Discourse.Markdown.whiteListTag('span', 'class', 'excerpt');

Discourse.Markdown.whiteListIframe(/^(https?:)?\/\/www\.google\.com\/maps\/embed\?.+/i);
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { CellActionsLookup } from './cellActions';
import { RowActionsLookup } from './rowActions';
import { TableActionsLookup } from './tableActions';
import { PageActionsLookup } from './pageActions';

export { ACTIONS } from './constants';

export const PageActions = {
  ...CellActionsLookup,
  ...RowActionsLookup,
  ...TableActionsLookup,
  ...PageActionsLookup,
};
<commit_msg>Add export of DATA_SET<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { CellActionsLookup } from './cellActions';
import { RowActionsLookup } from './rowActions';
import { TableActionsLookup } from './tableActions';
import { PageActionsLookup } from './pageActions';

export { ACTIONS, DATA_SET } from './constants';

export const PageActions = {
  ...CellActionsLookup,
  ...RowActionsLookup,
  ...TableActionsLookup,
  ...PageActionsLookup,
};
<|endoftext|>


<commit_before>/**
  @module ember-flexberry-gis
*/

import Ember from 'ember';
import layout from '../templates/components/flexberry-layers-attributes-panel';
import LeafletZoomToFeatureMixin from '../mixins/leaflet-zoom-to-feature';

/**
  The component for editing layers attributes.

  @class FlexberryLayersAttributesPanelComponent
  @uses LeafletZoomToFeatureMixin
  @extends Ember.Component
 */
export default Ember.Component.extend(LeafletZoomToFeatureMixin, {
  /**
    Leaflet.Editable drawing tools instance.

    @property _editTools
    @type L.Ediatble
    @default null
    @private
  */
  _editTools: null,

  /**
    Computed property that builds tab models collection from items.

    @property _tabModels
    @type Object[]
    @private
    @readonly
  */
  _tabModels: Ember.computed('items.[]', function () {
    let editedLayers = this.get('items');
    if (Ember.isPresent(editedLayers)) {
      if (editedLayers.length === 1) {
        this.set('_featureTabsOffset', 0);
        this.send('onTabMove', true);
      }

      return editedLayers.map((item) => {
        let name = Ember.get(item, 'name');
        let header = {};
        let featureLink = {};
        let propertyLink = {};
        let properties = Ember.A();
        let leafletObject = Ember.get(item, 'leafletObject');

        leafletObject.eachLayer((layer) => {
          let props = Ember.get(layer, 'feature.properties');
          let propId = Ember.guidFor(props);
          if (Ember.isNone(layer.feature.leafletLayer)) {
            Ember.set(layer.feature, 'leafletLayer', layer);
          }

          // the hash containing guid of properties object and link to feature layer
          featureLink[propId] = layer;

          // the hash containing guid of properties object and link to that object
          propertyLink[propId] = props;
          properties.pushObject(props);
        });

        for (let p in leafletObject.readFormat.featureType.fields) {
          header[p] = p;
        }

        let tabModel = Ember.Object.extend({
          _top: 5,
          _skip: 0,
          _selectedRows: {},
          _editedRows: {},
          _selectedRowsCount: Ember.computed('_selectedRows', function () {
            let selectedRows = Ember.get(this, '_selectedRows');
            return Object.keys(selectedRows).filter((item) => Ember.get(selectedRows, item)).length;
          }),
          selectAll: false,

          // paging implementation
          propertiesToShow: Ember.computed('properties.[]', '_top', '_skip', function () {
            let properties = this.get('properties');
            let top = this.get('_top');
            let skip = this.get('_skip');
            return properties.slice(skip, skip + top);
          })
        });
        return tabModel.create({
          name: name,
          leafletObject: leafletObject,
          featureLink: featureLink,
          propertyLink: propertyLink,
          header: header,
          properties: properties
        });
      });
    }
  }),

  /**
    Offset of tab panel in pixels.

    @property _featureTabsOffset
    @type Number
    @default 0
    @private
    @readonly
  */
  _featureTabsOffset: 0,

  /**
    Flag indicates whether attributes edit dialog has been already requested by user or not.

    @property _editRowDialogHasBeenRequested
    @type Boolean
    @default false
    @private
  */
  _editRowDialogHasBeenRequested: false,

  /**
    Flag indicates whether to show edit row dialog.

    @property _onEditRowDialogIsVisible
    @type Boolean
    @default false
    @private
  */
  _onEditRowDialogIsVisible: false,

  /**
   * Hash with edited row data.
   */
  _editRowData: null,

  /**
   * Hash with edited row data copy.
   */
  _editRowDataCopy: null,

  /**
    Reference to component's template.
  */
  layout,

  /**
    Selected tab index.

    @property selectedTabIndex
    @type Number
    @default 0
  */
  selectedTabIndex: 0,

  /**
    Flag indicates that the panel is folded or not.

    @property folded
    @type Boolean
    @default false
  */
  folded: false,

  /**
    Collection of tab items.

    @property items
    @type Object[]
    @default null
  */
  items: null,

  /**
    Integration with the layer tree

    @property settings
    @type Boolean
    @default false
  */
  settings: {
      withToolbar: false,
      sidebarOpened: false,
    },

  actions: {
    /**
      Handles click on a tab.

      @param Number index
    */
    onTabSelect(index) {
      if (index === this.get('selectedTabIndex')) {
        this.set('folded', !this.get('folded'));
      } else {
        this.set('selectedTabIndex', index);
        if (this.get('folded')) {
          this.set('folded', false);
        }
      }
    },

    /**
      Handles click on tab close icon.

      @param Number index
    */
    closeTab(index) {
      let editedLayers = this.get('items');
      let selectedTabIndex = this.get('selectedTabIndex');
      editedLayers.removeAt(index);
      if (selectedTabIndex >= index && selectedTabIndex - 1 >= 0) {
        this.set('selectedTabIndex', selectedTabIndex - 1);
      }
    },

    /**
      Handles attributes row selection.

      @param Object tabModel
      @param String rowId
      @param Object options
    */
    onRowSelect(tabModel, rowId, options) {
      let selectedRows = Ember.get(tabModel, '_selectedRows');
      Ember.set(selectedRows, rowId, options.checked);
      Ember.set(tabModel, '_selectedRows', selectedRows);
      tabModel.notifyPropertyChange('_selectedRows');
    },

    /**
      Handles 'Find an item on the map' button click.

      @param Object tabModel
    */
    onFindItemClick(tabModel) {
      let selectedRows = Ember.get(tabModel, '_selectedRows');
      let selectedFeatures = Object.keys(selectedRows).filter((item) => Ember.get(selectedRows, item))
        .map((key) => {
          return tabModel.featureLink[key].feature;
        });
      this.send('zoomTo', selectedFeatures);
    },

    /**
      Handles 'Clear found items' button click.
    */
    onClearFoundItemClick() {
      let serviceLayer = this.get('serviceLayer');
      serviceLayer.clearLayers();
    },

    /**
      Handles 'Select all' checkbox click.

      @param Object tabModel
    */
    onSelectAllClick(tabModel) {
      let selectAll = Ember.get(tabModel, 'selectAll');
      if (!selectAll) {
        Ember.set(tabModel, '_selectedRows', {});
      } else {
        let selectedRows = Object.assign(...Object.keys(Ember.get(tabModel, 'propertyLink')).map(k => ({
          [k]: true
        })));
        Ember.set(tabModel, '_selectedRows', selectedRows);
      }

      tabModel.notifyPropertyChange('_selectedRows');
    },

    /**
      Handles 'getData' action from flexberry-table.

      @param Object tabModel
      @param Object options
    */
    onTabGetData(tabModel, options) {
      Ember.set(tabModel, '_skip', options.skip);
      Ember.set(tabModel, '_top', options.top);
    },

    /**
      Handles tab panel moving.

      @param Boolean left
    */
    onTabMove(left) {
      // move tabs bar on the left or on the right
      let offset = this.get('_featureTabsOffset');
      if (left) {
        if (offset > 0) {
          offset -= Math.min(25, offset);
        }
      } else {
        let panelWidth = Ember.$('.bottompanel-tab-nav-panel-tabs').innerWidth();
        let itemsWidth = 0;
        const navButtonWidth = 25;
        Ember.$('.bottompanel-tab-nav-panel-tabs').children().each((index, item) => {
          itemsWidth += Ember.$(item).outerWidth();
        });
        let offsetDelta = Math.min(25, (panelWidth >= itemsWidth ? 0 :
          (offset >= itemsWidth - panelWidth + navButtonWidth ? 0 :
            itemsWidth - panelWidth + navButtonWidth - offset)));
        offset += offsetDelta;
      }

      this.set('_featureTabsOffset', offset);
      Ember.$('.bottompanel-tab-nav-panel-tabs').css('left', `-${offset}px`);
    },

    /**
      Handles 'Delete selected items' button click.

      @param Object tabModel
    */
    onDeleteItemClick(tabModel) {
      let selectedRows = Ember.get(tabModel, '_selectedRows');
      let selectedFeatureKeys = Object.keys(selectedRows).filter((item) => Ember.get(selectedRows, item));
      selectedFeatureKeys.forEach((key) => {
        tabModel.leafletObject.removeLayer(tabModel.featureLink[key]);
        delete selectedRows[key];
        tabModel.properties.removeObject(tabModel.propertyLink[key]);
      });
      Ember.set(tabModel, '_selectedRows', selectedRows);
      tabModel.notifyPropertyChange('_selectedRows');
    },

    onRowEdit(tabModel, rowId) {
      let editedProperty = tabModel.propertyLink[rowId];
      this.set('_editRowData', editedProperty);
      this.set('_editRowDataCopy', Ember.copy(editedProperty, false));
      this.set('_editRowFieldTypes', Ember.get(tabModel, 'leafletObject.readFormat.featureType.fieldTypes'));
      this.set('_editRowFieldParsers', Ember.get(tabModel, 'leafletObject.readFormat.featureType.fields'));

      // Include dialog to markup.
      this.set('_editRowDialogHasBeenRequested', true);

      // Show dialog.
      this.set('_onEditRowDialogIsVisible', true);
    },

    onEditRowDialogApprove(data) {
      for (var key in data) {
        if (data.hasOwnProperty(key)) {
          var element = data[key];
          if (!Ember.isEqual(element, this.get(`_editRowData.${key}`))) {
            this.set(`_editRowData.${key}`, element);
          }
        }
      }
    },

    onRowGeometryEdit(tabModel, rowId) {
      // Toggle row geometry editing
      let editedRows = Ember.get(tabModel, '_editedRows');
      let edit = Ember.get(editedRows, rowId) || false;
      edit = !edit;
      Ember.set(editedRows, rowId, edit);
      Ember.set(tabModel, '_editedRows', editedRows);
      tabModel.notifyPropertyChange('_editedRows');

      // Enable feature editing
      let layer = Ember.get(tabModel, `featureLink.${rowId}`);
      let leafletMap = this.get('leafletMap');

      let editTools = this._getEditTools();
      Ember.set(leafletMap, 'editTools', editTools);

      if (edit) {
        layer.enableEdit(leafletMap);

        // TODO add begin editing event binding - to check that geomentry was changed
      } else {
        layer.disableEdit();

        // TODO add stop editing event binding
      }
    },

    onGeometryTypeSelect(tabModel, mapToolName) {
      // TODO add event listener on mapTool.enable event - to disable drawing tool when user click on any map tool.

      let editTools = this._getEditTools();
      Ember.set(this.get('leafletMap'), 'drawTools', editTools);
      let that = { component: this, tabModel: tabModel };
      editTools.on('editable:drawing:end', this.disableDraw, that);
      this.$().closest('body').off('keydown');
      this.$().closest('body').on('keydown', ((e) => {
        // Esc was pressed
        if (e.which === 27) {
          this.disableDraw.call(that);
        }
      }));

      switch (mapToolName) {
        case 'marker':
          editTools.startMarker();
          break;
        case 'polyline':
          editTools.startPolyline();
          break;
        case 'circle':
          editTools.startCircle();
          break;
        case 'rectangle':
          editTools.startRectangle();
          break;
        case 'polygon':
          editTools.startPolygon();
          break;
      }

    },

    onSaveChangesClick(tabModel) {
      // TODO добавить обработку сохранения изменений
      // layer.save()...

    },

    onNewRowDialogApprove(data) {
      let tabModel = this.get('_newRowTabModel');
      let layer = this.get('_newRowLayer');

      Ember.set(layer, 'feature', { type: 'Feature' });
      Ember.set(layer.feature, 'properties', data);
      Ember.set(layer.feature, 'leafletLayer', layer);
      layer.setStyle(Ember.get(tabModel, 'leafletObject.options.style'));
      tabModel.leafletObject.addLayer(layer);
      layer.disableEdit();

      let propId = Ember.guidFor(data);

      // the hash containing guid of properties object and link to feature layer
      Ember.set(tabModel, `featureLink.${propId}`, layer);

      // the hash containing guid of properties object and link to that object
      Ember.set(tabModel, `propertyLink.${propId}`, data);
      tabModel.properties.pushObject(data);
    },

    onNewRowDialogDeny() {
      let layer = this.get('_newRowLayer');
      this.get('leafletMap').removeLayer(layer);

      this.set('_newRowTabModel', null);
      this.set('_newRowLayer', null);
    }
  },

  _getEditTools() {
    let leafletMap = this.get('leafletMap');

    let editTools = this.get('_editTools');
    if (Ember.isNone(editTools)) {
      editTools = new L.Editable(leafletMap);
      this.set('_editTools', editTools);
    }

    return editTools;
  },

  /**
    Overrides {{#crosslink "LeafletZoomToFeatureMixin/_prepareLayer:method"}} to make a copy of passed layer
    and apply a style to the layer to make it more visible.

    @param Object layer
  */
  _prepareLayer(layer) {
    return L.geoJson(layer.toGeoJSON()).setStyle({
      color: 'salmon',
      weight: 2,
      opacity: 1,
      fillOpacity: 0.3
    });
  },

  _showNewRowDialog(tabModel, layer) {
    let fields = Ember.get(tabModel, 'leafletObject.readFormat.featureType.fields');
    let fieldTypes = Ember.get(tabModel, 'leafletObject.readFormat.featureType.fieldTypes');
    let data = Object.keys(fields).reduce((result, item) => {
      result[item] = null;
      return result;
    }, {});
    this.set('_newRowTabModel', tabModel);
    this.set('_newRowLayer', layer);
    this.set('_newRowData', data);
    this.set('_newRowFieldTypes', fieldTypes);
    this.set('_newRowFieldParsers', fields);

    // Include dialog to markup.
    this.set('_newRowDialogHasBeenRequested', true);

    // Show dialog.
    this.set('_newRowDialogIsVisible', true);
  },

  disableDraw(e) {
    let that = this.component;
    let tabModel = this.tabModel;
    let editTools = that.get('_editTools');

    if (!Ember.isNone(editTools)) {
      editTools.off('editable:drawing:end', that.disableDraw, this);
      editTools.stopDrawing();
    }

    if (!Ember.isNone(e)) {
      let editedLayer = e.layer;
      that._showNewRowDialog(tabModel, editedLayer);
    }
  }
});
<commit_msg>Move event unbinding<commit_after>/**
  @module ember-flexberry-gis
*/

import Ember from 'ember';
import layout from '../templates/components/flexberry-layers-attributes-panel';
import LeafletZoomToFeatureMixin from '../mixins/leaflet-zoom-to-feature';

/**
  The component for editing layers attributes.

  @class FlexberryLayersAttributesPanelComponent
  @uses LeafletZoomToFeatureMixin
  @extends Ember.Component
 */
export default Ember.Component.extend(LeafletZoomToFeatureMixin, {
  /**
    Leaflet.Editable drawing tools instance.

    @property _editTools
    @type L.Ediatble
    @default null
    @private
  */
  _editTools: null,

  /**
    Computed property that builds tab models collection from items.

    @property _tabModels
    @type Object[]
    @private
    @readonly
  */
  _tabModels: Ember.computed('items.[]', function () {
    let editedLayers = this.get('items');
    if (Ember.isPresent(editedLayers)) {
      if (editedLayers.length === 1) {
        this.set('_featureTabsOffset', 0);
        this.send('onTabMove', true);
      }

      return editedLayers.map((item) => {
        let name = Ember.get(item, 'name');
        let header = {};
        let featureLink = {};
        let propertyLink = {};
        let properties = Ember.A();
        let leafletObject = Ember.get(item, 'leafletObject');

        leafletObject.eachLayer((layer) => {
          let props = Ember.get(layer, 'feature.properties');
          let propId = Ember.guidFor(props);
          if (Ember.isNone(layer.feature.leafletLayer)) {
            Ember.set(layer.feature, 'leafletLayer', layer);
          }

          // the hash containing guid of properties object and link to feature layer
          featureLink[propId] = layer;

          // the hash containing guid of properties object and link to that object
          propertyLink[propId] = props;
          properties.pushObject(props);
        });

        for (let p in leafletObject.readFormat.featureType.fields) {
          header[p] = p;
        }

        let tabModel = Ember.Object.extend({
          _top: 5,
          _skip: 0,
          _selectedRows: {},
          _editedRows: {},
          _selectedRowsCount: Ember.computed('_selectedRows', function () {
            let selectedRows = Ember.get(this, '_selectedRows');
            return Object.keys(selectedRows).filter((item) => Ember.get(selectedRows, item)).length;
          }),
          selectAll: false,

          // paging implementation
          propertiesToShow: Ember.computed('properties.[]', '_top', '_skip', function () {
            let properties = this.get('properties');
            let top = this.get('_top');
            let skip = this.get('_skip');
            return properties.slice(skip, skip + top);
          })
        });
        return tabModel.create({
          name: name,
          leafletObject: leafletObject,
          featureLink: featureLink,
          propertyLink: propertyLink,
          header: header,
          properties: properties
        });
      });
    }
  }),

  /**
    Offset of tab panel in pixels.

    @property _featureTabsOffset
    @type Number
    @default 0
    @private
    @readonly
  */
  _featureTabsOffset: 0,

  /**
    Flag indicates whether attributes edit dialog has been already requested by user or not.

    @property _editRowDialogHasBeenRequested
    @type Boolean
    @default false
    @private
  */
  _editRowDialogHasBeenRequested: false,

  /**
    Flag indicates whether to show edit row dialog.

    @property _onEditRowDialogIsVisible
    @type Boolean
    @default false
    @private
  */
  _onEditRowDialogIsVisible: false,

  /**
   * Hash with edited row data.
   */
  _editRowData: null,

  /**
   * Hash with edited row data copy.
   */
  _editRowDataCopy: null,

  /**
    Reference to component's template.
  */
  layout,

  /**
    Selected tab index.

    @property selectedTabIndex
    @type Number
    @default 0
  */
  selectedTabIndex: 0,

  /**
    Flag indicates that the panel is folded or not.

    @property folded
    @type Boolean
    @default false
  */
  folded: false,

  /**
    Collection of tab items.

    @property items
    @type Object[]
    @default null
  */
  items: null,

  /**
    Integration with the layer tree

    @property settings
    @type Boolean
    @default false
  */
  settings: {
      withToolbar: false,
      sidebarOpened: false,
    },

  actions: {
    /**
      Handles click on a tab.

      @param Number index
    */
    onTabSelect(index) {
      if (index === this.get('selectedTabIndex')) {
        this.set('folded', !this.get('folded'));
      } else {
        this.set('selectedTabIndex', index);
        if (this.get('folded')) {
          this.set('folded', false);
        }
      }
    },

    /**
      Handles click on tab close icon.

      @param Number index
    */
    closeTab(index) {
      let editedLayers = this.get('items');
      let selectedTabIndex = this.get('selectedTabIndex');
      editedLayers.removeAt(index);
      if (selectedTabIndex >= index && selectedTabIndex - 1 >= 0) {
        this.set('selectedTabIndex', selectedTabIndex - 1);
      }
    },

    /**
      Handles attributes row selection.

      @param Object tabModel
      @param String rowId
      @param Object options
    */
    onRowSelect(tabModel, rowId, options) {
      let selectedRows = Ember.get(tabModel, '_selectedRows');
      Ember.set(selectedRows, rowId, options.checked);
      Ember.set(tabModel, '_selectedRows', selectedRows);
      tabModel.notifyPropertyChange('_selectedRows');
    },

    /**
      Handles 'Find an item on the map' button click.

      @param Object tabModel
    */
    onFindItemClick(tabModel) {
      let selectedRows = Ember.get(tabModel, '_selectedRows');
      let selectedFeatures = Object.keys(selectedRows).filter((item) => Ember.get(selectedRows, item))
        .map((key) => {
          return tabModel.featureLink[key].feature;
        });
      this.send('zoomTo', selectedFeatures);
    },

    /**
      Handles 'Clear found items' button click.
    */
    onClearFoundItemClick() {
      let serviceLayer = this.get('serviceLayer');
      serviceLayer.clearLayers();
    },

    /**
      Handles 'Select all' checkbox click.

      @param Object tabModel
    */
    onSelectAllClick(tabModel) {
      let selectAll = Ember.get(tabModel, 'selectAll');
      if (!selectAll) {
        Ember.set(tabModel, '_selectedRows', {});
      } else {
        let selectedRows = Object.assign(...Object.keys(Ember.get(tabModel, 'propertyLink')).map(k => ({
          [k]: true
        })));
        Ember.set(tabModel, '_selectedRows', selectedRows);
      }

      tabModel.notifyPropertyChange('_selectedRows');
    },

    /**
      Handles 'getData' action from flexberry-table.

      @param Object tabModel
      @param Object options
    */
    onTabGetData(tabModel, options) {
      Ember.set(tabModel, '_skip', options.skip);
      Ember.set(tabModel, '_top', options.top);
    },

    /**
      Handles tab panel moving.

      @param Boolean left
    */
    onTabMove(left) {
      // move tabs bar on the left or on the right
      let offset = this.get('_featureTabsOffset');
      if (left) {
        if (offset > 0) {
          offset -= Math.min(25, offset);
        }
      } else {
        let panelWidth = Ember.$('.bottompanel-tab-nav-panel-tabs').innerWidth();
        let itemsWidth = 0;
        const navButtonWidth = 25;
        Ember.$('.bottompanel-tab-nav-panel-tabs').children().each((index, item) => {
          itemsWidth += Ember.$(item).outerWidth();
        });
        let offsetDelta = Math.min(25, (panelWidth >= itemsWidth ? 0 :
          (offset >= itemsWidth - panelWidth + navButtonWidth ? 0 :
            itemsWidth - panelWidth + navButtonWidth - offset)));
        offset += offsetDelta;
      }

      this.set('_featureTabsOffset', offset);
      Ember.$('.bottompanel-tab-nav-panel-tabs').css('left', `-${offset}px`);
    },

    /**
      Handles 'Delete selected items' button click.

      @param Object tabModel
    */
    onDeleteItemClick(tabModel) {
      let selectedRows = Ember.get(tabModel, '_selectedRows');
      let selectedFeatureKeys = Object.keys(selectedRows).filter((item) => Ember.get(selectedRows, item));
      selectedFeatureKeys.forEach((key) => {
        tabModel.leafletObject.removeLayer(tabModel.featureLink[key]);
        delete selectedRows[key];
        tabModel.properties.removeObject(tabModel.propertyLink[key]);
      });
      Ember.set(tabModel, '_selectedRows', selectedRows);
      tabModel.notifyPropertyChange('_selectedRows');
    },

    onRowEdit(tabModel, rowId) {
      let editedProperty = tabModel.propertyLink[rowId];
      this.set('_editRowData', editedProperty);
      this.set('_editRowDataCopy', Ember.copy(editedProperty, false));
      this.set('_editRowFieldTypes', Ember.get(tabModel, 'leafletObject.readFormat.featureType.fieldTypes'));
      this.set('_editRowFieldParsers', Ember.get(tabModel, 'leafletObject.readFormat.featureType.fields'));

      // Include dialog to markup.
      this.set('_editRowDialogHasBeenRequested', true);

      // Show dialog.
      this.set('_onEditRowDialogIsVisible', true);
    },

    onEditRowDialogApprove(data) {
      for (var key in data) {
        if (data.hasOwnProperty(key)) {
          var element = data[key];
          if (!Ember.isEqual(element, this.get(`_editRowData.${key}`))) {
            this.set(`_editRowData.${key}`, element);
          }
        }
      }
    },

    onRowGeometryEdit(tabModel, rowId) {
      // Toggle row geometry editing
      let editedRows = Ember.get(tabModel, '_editedRows');
      let edit = Ember.get(editedRows, rowId) || false;
      edit = !edit;
      Ember.set(editedRows, rowId, edit);
      Ember.set(tabModel, '_editedRows', editedRows);
      tabModel.notifyPropertyChange('_editedRows');

      // Enable feature editing
      let layer = Ember.get(tabModel, `featureLink.${rowId}`);
      let leafletMap = this.get('leafletMap');

      let editTools = this._getEditTools();
      Ember.set(leafletMap, 'editTools', editTools);

      if (edit) {
        layer.enableEdit(leafletMap);

        // TODO add begin editing event binding - to check that geomentry was changed
      } else {
        layer.disableEdit();

        // TODO add stop editing event binding
      }
    },

    onGeometryTypeSelect(tabModel, mapToolName) {
      // TODO add event listener on mapTool.enable event - to disable drawing tool when user click on any map tool.

      let editTools = this._getEditTools();
      Ember.set(this.get('leafletMap'), 'drawTools', editTools);
      let that = { component: this, tabModel: tabModel };
      editTools.on('editable:drawing:end', this.disableDraw, that);
      this.$().closest('body').on('keydown', ((e) => {
        // Esc was pressed
        if (e.which === 27) {
          this.disableDraw.call(that);
        }
      }));

      switch (mapToolName) {
        case 'marker':
          editTools.startMarker();
          break;
        case 'polyline':
          editTools.startPolyline();
          break;
        case 'circle':
          editTools.startCircle();
          break;
        case 'rectangle':
          editTools.startRectangle();
          break;
        case 'polygon':
          editTools.startPolygon();
          break;
      }

    },

    onSaveChangesClick(tabModel) {
      // TODO добавить обработку сохранения изменений
      // layer.save()...

    },

    onNewRowDialogApprove(data) {
      let tabModel = this.get('_newRowTabModel');
      let layer = this.get('_newRowLayer');

      Ember.set(layer, 'feature', { type: 'Feature' });
      Ember.set(layer.feature, 'properties', data);
      Ember.set(layer.feature, 'leafletLayer', layer);
      layer.setStyle(Ember.get(tabModel, 'leafletObject.options.style'));
      tabModel.leafletObject.addLayer(layer);
      layer.disableEdit();

      let propId = Ember.guidFor(data);

      // the hash containing guid of properties object and link to feature layer
      Ember.set(tabModel, `featureLink.${propId}`, layer);

      // the hash containing guid of properties object and link to that object
      Ember.set(tabModel, `propertyLink.${propId}`, data);
      tabModel.properties.pushObject(data);
    },

    onNewRowDialogDeny() {
      let layer = this.get('_newRowLayer');
      this.get('leafletMap').removeLayer(layer);

      this.set('_newRowTabModel', null);
      this.set('_newRowLayer', null);
    }
  },

  _getEditTools() {
    let leafletMap = this.get('leafletMap');

    let editTools = this.get('_editTools');
    if (Ember.isNone(editTools)) {
      editTools = new L.Editable(leafletMap);
      this.set('_editTools', editTools);
    }

    return editTools;
  },

  /**
    Overrides {{#crosslink "LeafletZoomToFeatureMixin/_prepareLayer:method"}} to make a copy of passed layer
    and apply a style to the layer to make it more visible.

    @param Object layer
  */
  _prepareLayer(layer) {
    return L.geoJson(layer.toGeoJSON()).setStyle({
      color: 'salmon',
      weight: 2,
      opacity: 1,
      fillOpacity: 0.3
    });
  },

  _showNewRowDialog(tabModel, layer) {
    let fields = Ember.get(tabModel, 'leafletObject.readFormat.featureType.fields');
    let fieldTypes = Ember.get(tabModel, 'leafletObject.readFormat.featureType.fieldTypes');
    let data = Object.keys(fields).reduce((result, item) => {
      result[item] = null;
      return result;
    }, {});
    this.set('_newRowTabModel', tabModel);
    this.set('_newRowLayer', layer);
    this.set('_newRowData', data);
    this.set('_newRowFieldTypes', fieldTypes);
    this.set('_newRowFieldParsers', fields);

    // Include dialog to markup.
    this.set('_newRowDialogHasBeenRequested', true);

    // Show dialog.
    this.set('_newRowDialogIsVisible', true);
  },

  disableDraw(e) {
    let that = this.component;
    let tabModel = this.tabModel;
    let editTools = that.get('_editTools');

    that.$().closest('body').off('keydown');

    if (!Ember.isNone(editTools)) {
      editTools.off('editable:drawing:end', that.disableDraw, this);
      editTools.stopDrawing();
    }

    if (!Ember.isNone(e)) {
      let editedLayer = e.layer;
      that._showNewRowDialog(tabModel, editedLayer);
    }
  }
});
<|endoftext|>


<commit_before>var DateUtil = require('dateutil');

function Programme(programme) {
    this.getCurrentTargetTemperature = function(date) {
        if(!programme.heatingOn) {
            return programme.frostProtectTemp;
        }
        return getOverriddenTemperature(date) || getProgrammeTemperature(date);
    }

    function getOverriddenTemperature(date) {
        if(inOverridePeriod(date)) {
            return getTemperatureForComfortState(programme.override.comfortState);
        }
        return NaN;
    }

    function inOverridePeriod(date) {
        return programme.override && programme.override.until && beforeOverrideEnd(date, programme.override.until);
    }

    function beforeOverrideEnd(date, overrideEndTimeMs) {
        return date.getTime() < overrideEndTimeMs;
    }

    function getProgrammeTemperature(date) {
        return getTemperatureForComfortState(inAnyComfortPeriodForDate(date));
    }

    function getTemperatureForComfortState(comfortState) {
        return comfortState ? programme.comfortTemp : programme.setbackTemp;
    }

    function laterThanComfortPeriodStart(date, period) {
        var start = DateUtil.getDateFromTimeStr(date, period.startTime);
        return DateUtil.isFirstDateBeforeSecondDate(start, date);
    }

    function earlierThanComfortPeriodEnd(date, period) {
        var end = DateUtil.getDateFromTimeStr(date, period.endTime);
        return DateUtil.isFirstDateBeforeSecondDate(date, end);
    }

    function inComfortPeriod(date, comfortPeriod) {
        return laterThanComfortPeriodStart(date, comfortPeriod) && earlierThanComfortPeriodEnd(date, comfortPeriod);
    }

    function inAnyComfortPeriodForDate(date) {
        var periodsForToday = programme.schedule[DateUtil.getDayOfWeek(date)].comfortPeriods;
        for(var i = 0; i < periodsForToday.length; ++i) {
            if(inComfortPeriod(date, periodsForToday[i])) {
                return true;
            }
        }
        return false;
    }
}

module.exports = {
    Programme : Programme
}
<commit_msg>added functions<commit_after>var DateUtil = require('dateutil');

function Programme(programme) {

    var self = this;

    this.getCurrentTargetTemperature = function(date) {
        if(!self.isHeatingEnabled()) {
            return programme.frostProtectTemp;
        }
        return getOverriddenTemperature(date) || getProgrammeTemperature(date);
    }

    this.isHeatingEnabled = function() {
        return programme.heatingOn;
    }

    this.isInComfortMode = function(date) {
        if(self.inOverridePeriod(date)) {
            return programme.override.comfortState;
        }
        else {
            return inAnyComfortPeriodForDate(date);
        }
    }

    this.inOverridePeriod = function(date) {
        return programme.override && programme.override.until && beforeOverrideEnd(date, programme.override.until);
    }

    function getOverriddenTemperature(date) {
        if(self.inOverridePeriod(date)) {
            return getTemperatureForComfortState(programme.override.comfortState);
        }
        return NaN;
    }

    function beforeOverrideEnd(date, overrideEndTimeMs) {
        return date.getTime() < overrideEndTimeMs;
    }

    function getProgrammeTemperature(date) {
        return getTemperatureForComfortState(inAnyComfortPeriodForDate(date));
    }

    function getTemperatureForComfortState(comfortState) {
        return comfortState ? programme.comfortTemp : programme.setbackTemp;
    }

    function laterThanComfortPeriodStart(date, period) {
        var start = DateUtil.getDateFromTimeStr(date, period.startTime);
        return DateUtil.isFirstDateBeforeSecondDate(start, date);
    }

    function earlierThanComfortPeriodEnd(date, period) {
        var end = DateUtil.getDateFromTimeStr(date, period.endTime);
        return DateUtil.isFirstDateBeforeSecondDate(date, end);
    }

    function inComfortPeriod(date, comfortPeriod) {
        return laterThanComfortPeriodStart(date, comfortPeriod) && earlierThanComfortPeriodEnd(date, comfortPeriod);
    }

    function inAnyComfortPeriodForDate(date) {
        var periodsForToday = programme.schedule[DateUtil.getDayOfWeek(date)].comfortPeriods;
        for(var i = 0; i < periodsForToday.length; ++i) {
            if(inComfortPeriod(date, periodsForToday[i])) {
                return true;
            }
        }
        return false;
    }
}

module.exports = {
    Programme : Programme
}
<|endoftext|>