<commit_before>/**
 * Copyright (c) 2014-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

import React from 'react';
import Link from 'gatsby-link';
import Page from '../../components/Page';
import Padded from '../../components/Padded';
import {Row, Col} from 'antd';
import './index.css';

const CATEGORY_TITLE = {
  'getting-started': 'Getting Started',
  properties: 'Properties',
  examples: 'Examples',
  contributing: 'Contributing',
};

export default ({data}) => (
  <Page className="docs-landing" shouldShowFooter title="Documentation">
    <Padded>
      <Row className="heading">
        <Col xl={16} lg={16} md={24} sm={24} xs={24}>

Documentation

            Welcome to Yoga's documentation page. Below you will find helpful
            documentation covering all the features of the library. Each page
            comes with an interactive playground for you to explore that
            feature. The examples section showcases some of the most common
            layouts and how to build them. This is a community projects and
            contributions within documentation, code, and tests are more then
            welcome. The contributing section below covers how to get started.

        </Col>
      </Row>
      <Row>
        {['getting-started', 'properties', 'examples', 'contributing'].map(
          category => (
            <Col lg={6} md={12} xs={24} key={category} className="category">

{CATEGORY_TITLE[category]}

              {data.allMarkdownRemark.edges
                .filter(
                  ({node}) =>
                    node.fileAbsolutePath.indexOf(`/${category}/`) > -1,
                )
                .map(
                  ({node}) =>
                    node.frontmatter.redirect ? (

                        {node.frontmatter.title}

                    ) : (
                      <Link key={node.id} to={node.frontmatter.path}>
                        {node.frontmatter.title}
                      </Link>
                    ),
                )}
            </Col>
          ),
        )}
      </Row>
    </Padded>
  </Page>
);

export const query = graphql`
  query IndexQuery {
    allMarkdownRemark {
      edges {
        node {
          id
          frontmatter {
            title
            path
            redirect
          }
          fileAbsolutePath
        }
      }
    }
  }
`;
<commit_msg>Use correct spelling ('then' -> 'than') (#782)<commit_after>/**
 * Copyright (c) 2014-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

import React from 'react';
import Link from 'gatsby-link';
import Page from '../../components/Page';
import Padded from '../../components/Padded';
import {Row, Col} from 'antd';
import './index.css';

const CATEGORY_TITLE = {
  'getting-started': 'Getting Started',
  properties: 'Properties',
  examples: 'Examples',
  contributing: 'Contributing',
};

export default ({data}) => (
  <Page className="docs-landing" shouldShowFooter title="Documentation">
    <Padded>
      <Row className="heading">
        <Col xl={16} lg={16} md={24} sm={24} xs={24}>

Documentation

            Welcome to Yoga's documentation page. Below you will find helpful
            documentation covering all the features of the library. Each page
            comes with an interactive playground for you to explore that
            feature. The examples section showcases some of the most common
            layouts and how to build them. This is a community projects and
            contributions within documentation, code, and tests are more than
            welcome. The contributing section below covers how to get started.

        </Col>
      </Row>
      <Row>
        {['getting-started', 'properties', 'examples', 'contributing'].map(
          category => (
            <Col lg={6} md={12} xs={24} key={category} className="category">

{CATEGORY_TITLE[category]}

              {data.allMarkdownRemark.edges
                .filter(
                  ({node}) =>
                    node.fileAbsolutePath.indexOf(`/${category}/`) > -1,
                )
                .map(
                  ({node}) =>
                    node.frontmatter.redirect ? (

                        {node.frontmatter.title}

                    ) : (
                      <Link key={node.id} to={node.frontmatter.path}>
                        {node.frontmatter.title}
                      </Link>
                    ),
                )}
            </Col>
          ),
        )}
      </Row>
    </Padded>
  </Page>
);

export const query = graphql`
  query IndexQuery {
    allMarkdownRemark {
      edges {
        node {
          id
          frontmatter {
            title
            path
            redirect
          }
          fileAbsolutePath
        }
      }
    }
  }
`;
<|endoftext|>


<commit_before>
/**
 * renderView
 *
 * @param {Object} _options - Object litteral of parameters
 * @param {string} _options.template - HTML reference of template
 * @param {string} _options.template_path - The path from which templates for template_url are located
 * @param {Object} _options.template_url - Object of templates to load from template_path
 * @param {string} _options.template_url.url - Template name to load from template_path
 * @param {{}} _options.data - Object of data to be merged with template placeholders
 * @param {string} _options.data_url - External source of data
 * @param _options.target - Element to append view onto
 * @param _options.initializer - Attach an initializer to view
 * @param _options.callback_rendered - Callback rendered
 * @returns view
*/
u.renderView = function(_options) {

	u.bug("u.renderView", _options);

	var template = false;
	var template_url = false;

	var template_path = "/templates/";
	var type = "html";

	var data = false;
	var data_url = false;

	var target = false;

	var initializer = false;

	var callback_rendered = "templateRendered";

	// apply parameters
	if (obj(_options)) {
		var _argument;
		for(_argument in _options) {
			switch(_argument) {

				case "template"          : template            = _options[_argument]; break;
				case "template_path"     : template_path       = _options[_argument]; break;

				case "template_url"      : template_url        = _options[_argument]; break;
				case "type"              : type                = _options[_argument]; break;

				case "data"              : data                = _options[_argument]; break;
				case "data_url"          : data_url            = _options[_argument]; break;

				case "target"            : target              = _options[_argument]; break;

				case "initializer"       : initializer         = _options[_argument]; break;

				case "callback_rendered" : callback_rendered   = _options[_argument]; break;

			}
		}
	}

	var view;

	if(target) {

		view = u.ae(target, "div", {class:"view loading"});

	}
	else {

		view = document.createElement("div");
		u.ac(view, "view loading");

	}

	// Map base properties
	view._rv_template_path = template_path;
	view._rv_type = type;

	view._rv_template_url = template_url;
	view._rv_data_url = data_url;

	view._rv_initializer = initializer;

	view.update = function(_options) {
		u.bug("update", _options, this._rv_data, this._rv_template);

		// Clean up template
		this.innerHTML = "";

		var data = this._rv_data;
		var template = this._rv_template;

		this._rv_data = false;
		this._rv_template = false;

		// apply parameters
		if (obj(_options)) {
			var _argument;
			for(_argument in _options) {
				switch(_argument) {

					case "template"          : template            = _options[_argument]; break;
					case "data"              : data                = _options[_argument]; break;

				}
			}
		}

		if(template) {

			this._rv_template = template;
			this._stateChanged();

		}
		else if(this._rv_template_url) {

			this.loadTemplate();

		}

		if(data) {
			u.bug("data", data);

			this._rv_data = data;
			this._stateChanged();

		}
		else if(data_url) {

			this.loadData();

		}

	}

	view.loadTemplate = function() {
		u.bug("loadTemplate", this._rv_template_path + this._rv_template_url.url)

		this._rv_template = false;

		// Once template has been received (response = template)
		this._templateLoaded = function(response) {
			u.bug("response", response)

			this._rv_template = response.children[0];

			this._stateChanged();
		}

		// Get template
		u.request(this, this._rv_template_path + this._rv_template_url.url, {
			"method":(this._rv_template_url.method ? this._rv_template_url.method : "GET"),
			"data":(this._rv_template_url.data ? this._rv_template_url.data : ""),

			"callback":"_templateLoaded"
		});

	}

	view.loadData = function(_options) {

		this._rv_data = false;

		this._dataLoaded = function(response) {
			this._rv_data = response;

			this._stateChanged();
		}

		u.request(this, this._rv_data_url.url, {
			"method":(this._rv_template_url.method ? this._rv_template_url.method : "GET"),
			"data":(this._rv_template_url.data ? this._rv_template_url.data : ""),

			callback: "_dataLoaded"
		});

	}

	view._stateChanged = function() {
		u.bug("_stateChanged", this._rv_template, this._rv_data);

		if(this._rv_template && this._rv_data) {

			// Merge data with template and append the resulting "rendered template" to the view
			var template = u.template(this._rv_template, this._rv_data, {append: this});

			console.log(template)

			// Reference to rendered template on view
			this.template = this.firstChild;

			// Reference to view on rendered template
			this.firstChild.view = this;

			// Init the initializer on template
			if (this._rv_initializer) {
				Util.Objects[this._rv_initializer].init(this.firstChild);
			}

			if(this["templateRendered"]) {
				this["templateRendered"]();
			}

			u.rc(this, "loading");
		}
	}

	view.update(_options);

	return view;
}<commit_msg>Initalizer on view and not template<commit_after>
/**
 * renderView
 *
 * @param {Object} _options - Object litteral of parameters
 * @param {string} _options.template - HTML reference of template
 * @param {string} _options.template_path - The path from which templates for template_url are located
 * @param {Object} _options.template_url - Object of templates to load from template_path
 * @param {string} _options.template_url.url - Template url to load from template_path
 * @param {string} _options.template_url.method - Request method
 * @param {{}} _options.template_url.data - Data to be merged with requested template
 * @param {{}} _options.data - Object of data to be merged with template placeholders
 * @param {string} _options.data_url - External source of data
 * @param _options.target - Element to append view onto
 * @param _options.initializer - Attach an initializer to rendered template
 * @param _options.callback_rendered - Callback rendered
 * @returns view
*/
u.renderView = function(_options) {

	// u.bug("u.renderView", _options);

	var template = false;
	var template_url = false;

	var template_path = "/templates/";
	var type = "html";

	var data = false;
	var data_url = false;

	var target = false;

	var initializer = false;

	var callback_rendered = "templateRendered";

	// apply parameters
	if (obj(_options)) {
		var _argument;
		for(_argument in _options) {
			switch(_argument) {

				case "template"          : template            = _options[_argument]; break;
				case "template_path"     : template_path       = _options[_argument]; break;

				case "template_url"      : template_url        = _options[_argument]; break;
				case "type"              : type                = _options[_argument]; break;

				case "data"              : data                = _options[_argument]; break;
				case "data_url"          : data_url            = _options[_argument]; break;

				case "target"            : target              = _options[_argument]; break;

				case "initializer"       : initializer         = _options[_argument]; break;

				case "callback_rendered" : callback_rendered   = _options[_argument]; break;

			}
		}
	}

	var view;

	if(target) {

		view = u.ae(target, "div", {class:"view loading"});

	}
	else {

		view = document.createElement("div");
		u.ac(view, "view loading");

	}

	// Map base properties
	view._rv_template_path = template_path;
	view._rv_type = type;

	view._rv_template_url = template_url;
	view._rv_data_url = data_url;

	view._rv_initializer = initializer;

	view.update = function(_options) {
		u.bug("update", _options, this._rv_data, this._rv_template);

		// Clean up template
		this.innerHTML = "";

		var data = this._rv_data;
		var template = this._rv_template;

		this._rv_data = false;
		this._rv_template = false;

		// apply parameters
		if (obj(_options)) {
			var _argument;
			for(_argument in _options) {
				switch(_argument) {

					case "template"          : template            = _options[_argument]; break;
					case "data"              : data                = _options[_argument]; break;

				}
			}
		}

		if(template) {

			this._rv_template = template;
			this._stateChanged();

		}
		else if(this._rv_template_url) {

			this.loadTemplate();

		}

		if(data) {
			// u.bug("data", data);

			this._rv_data = data;
			this._stateChanged();

		}
		else if(data_url) {

			this.loadData();

		}

	}

	view.loadTemplate = function() {
		// u.bug("loadTemplate", this._rv_template_path + this._rv_template_url.url)

		this._rv_template = false;

		// Once template has been received (response = template)
		this._templateLoaded = function(response) {
			// u.bug("response", response)

			this._rv_template = response.children[0];

			this._stateChanged();
		}

		// Get template
		u.request(this, this._rv_template_path + this._rv_template_url.url, {
			"method":(this._rv_template_url.method ? this._rv_template_url.method : "GET"),
			"data":(this._rv_template_url.data ? this._rv_template_url.data : ""),

			"callback":"_templateLoaded"
		});

	}

	view.loadData = function(_options) {

		this._rv_data = false;

		this._dataLoaded = function(response) {
			this._rv_data = response;

			this._stateChanged();
		}

		u.request(this, this._rv_data_url.url, {
			"method":(this._rv_template_url.method ? this._rv_template_url.method : "GET"),
			"data":(this._rv_template_url.data ? this._rv_template_url.data : ""),

			callback: "_dataLoaded"
		});

	}

	view._stateChanged = function() {
		// u.bug("_stateChanged", this._rv_template, this._rv_data);

		if(this._rv_template && this._rv_data) {

			// Merge data with template and append the resulting "rendered template" to the view
			var template = u.template(this._rv_template, this._rv_data, {append: this});

			// console.log("template: ", template)

			// Reference to rendered template on view
			this.template = template[0];

			// Reference to view on rendered template
			this.template.view = this;

			// Init the initializer on template
			// if (this._rv_initializer) {
			// 	// Util.Objects[this._rv_initializer].init(this.template);
			// }

			// Only load initializer once
			if (!this._initialized && this._rv_initializer) {
					console.log("View init: ", this);
					this._initialized = true;

					Util.Objects[view._rv_initializer].init(this);
			}

			if(this["templateRendered"]) {
				this["templateRendered"]();
			}

			u.rc(this, "loading");
		}
	}

	view.update(_options);

	return view;
}<|endoftext|>


<commit_before>frappe.provide("frappe.ui");
frappe.provide("frappe.web_form");
import EventEmitterMixin from '../../frappe/event_emitter';

export default class WebForm extends frappe.ui.FieldGroup {
	constructor(opts) {
		super();
		Object.assign(this, opts);
		frappe.web_form = this;
		frappe.web_form.events = {};
		Object.assign(frappe.web_form.events, EventEmitterMixin);
		this.current_section = 0;
	}

	prepare(web_form_doc, doc) {
		Object.assign(this, web_form_doc);
		this.fields = web_form_doc.web_form_fields;
		this.doc = doc;
	}

	make() {
		super.make();
		this.set_sections();
		this.set_field_values();
		this.setup_listeners();
		if (this.introduction_text) this.set_form_description(this.introduction_text);
		if (this.allow_print && !this.is_new) this.setup_print_button();
		if (this.allow_delete && !this.is_new) this.setup_delete_button();
		if (this.is_new) this.setup_cancel_button();
		this.setup_primary_action();
		this.setup_previous_next_button();
		this.toggle_section();
		$(".link-btn").remove();

		// webform client script
		frappe.init_client_script && frappe.init_client_script();
		frappe.web_form.events.trigger('after_load');
		this.after_load && this.after_load();
	}

	on(fieldname, handler) {
		let field = this.fields_dict[fieldname];
		field.df.change = () => {
			handler(field, field.value);
		};
	}

	setup_listeners() {
		// Event listener for triggering Save/Next button for Multi Step Forms
		// Do not use `on` event here since that can be used by user which will render this function useless
		// setTimeout has 200ms delay so that all the base_control triggers for the fields have been run
		let me = this;

		if (!me.is_multi_step_form) {
			return;
		}

		for (let field of $(".input-with-feedback")) {
			$(field).change((e) => {
				setTimeout(() => {
					e.stopPropagation();
					me.toggle_buttons();
				}, 200);
			});
		}
	}

	set_sections() {
		if (this.sections.length) return;

		this.sections = $(`.form-section`);
	}

	setup_previous_next_button() {
		let me = this;

		if (!me.is_multi_step_form) {
			return;
		}

		$('.web-form-footer').after(`

				<button class="btn btn-primary btn-previous btn-sm ml-2">${__("Previous")}</button>
				<button class="btn btn-primary btn-next btn-sm ml-2">${__("Next")}</button>

		`);

		$('.btn-previous').on('click', function () {
			let is_validated = me.validate_section();

			if (!is_validated) return;

			/**
				The eslint utility cannot figure out if this is an infinite loop in backwards and
				throws an error. Disabling for-direction just for this section.
				for-direction doesnt throw an error if the values are hardcoded in the
				reverse for-loop, but in this case its a dynamic loop.
				https://eslint.org/docs/rules/for-direction
			*/
			/* eslint-disable for-direction */
			for (let idx = me.current_section; idx < me.sections.length; idx--) {
				let is_empty = me.is_previous_section_empty(idx);
				me.current_section = me.current_section > 0 ? me.current_section - 1 : me.current_section;

				if (!is_empty) {
					break;
				}
			}
			/* eslint-enable for-direction */
			me.toggle_section();
		});

		$('.btn-next').on('click', function () {
			let is_validated = me.validate_section();

			if (!is_validated) return;

			for (let idx = me.current_section; idx < me.sections.length; idx++) {
				let is_empty = me.is_next_section_empty(idx);
				me.current_section = me.current_section < me.sections.length ? me.current_section + 1 : me.current_section;

				if (!is_empty) {
					break;
				}
			}
			me.toggle_section();
		});
	}

	set_field_values() {
		if (this.doc.name) this.set_values(this.doc);
		else return;
	}

	set_default_values() {
		let values = frappe.utils.get_query_params();
		delete values.new;
		this.set_values(values);
	}

	set_form_description(intro) {
		let intro_wrapper = document.getElementById('introduction');
		intro_wrapper.innerHTML = intro;
	}

	add_button(name, type, action, wrapper_class=".web-form-actions") {
		const button = document.createElement("button");
		button.classList.add("btn", "btn-" + type, "btn-sm", "ml-2");
		button.innerHTML = name;
		button.onclick = action;
		document.querySelector(wrapper_class).appendChild(button);
	}

	add_button_to_footer(name, type, action) {
		this.add_button(name, type, action, '.web-form-footer');
	}

	add_button_to_header(name, type, action) {
		this.add_button(name, type, action, '.web-form-actions');
	}

	setup_primary_action() {
		this.add_button_to_header(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
			this.save()
		);

		if (!this.is_multi_step_form && $('main').height() > 600) {
				// add button on footer if page is long
				this.add_button_to_footer(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
				this.save()
			);
		}
	}

	setup_cancel_button() {
		this.add_button_to_header(__("Cancel", null, "Button in web form"), "light", () => this.cancel());
	}

	setup_delete_button() {
		frappe.has_permission(this.doc_type, "", "delete", () => {
			this.add_button_to_header(
				frappe.utils.icon('delete'),
				"danger",
				() => this.delete()
			);
		});
	}

	setup_print_button() {
		this.add_button_to_header(
			frappe.utils.icon('print'),
			"light",
			() => this.print()
		);
	}

	validate_section() {
		if (this.allow_incomplete) return true;

		let fields = $(`.form-section:eq(${this.current_section}) .form-control`);
		let errors = [];
		let invalid_values = [];

		for (let field of fields) {
			let fieldname = $(field).attr("data-fieldname");
			if (!fieldname) continue;

			field = this.fields_dict[fieldname];

			if (field.get_value) {
				let value = field.get_value();
				if (field.df.reqd && is_null(typeof value === 'string' ? strip_html(value) : value)) errors.push(__(field.df.label));

				if (field.df.reqd && field.df.fieldtype === 'Text Editor' && is_null(strip_html(cstr(value)))) errors.push(__(field.df.label));

				if (field.df.invalid) invalid_values.push(__(field.df.label));
			}
		}

		let message = '';
		if (invalid_values.length) {
			message += __('Invalid values for fields:', null, 'Error message in web form');
			message += '

' + invalid_values.join('
') + '
';
		}

		if (errors.length) {
			message += __('Mandatory fields required:', null, 'Error message in web form');
			message += '

' + errors.join('
') + '
';
		}

		if (invalid_values.length || errors.length) {
			frappe.msgprint({
				title: __('Error', null, 'Title of error message in web form'),
				message: message,
				indicator: 'orange'
			});
		}

		return !(errors.length || invalid_values.length);
	}

	toggle_section() {
		if (!this.is_multi_step_form) return;

		this.toggle_previous_button();
		this.hide_sections();
		this.show_section();
		this.toggle_buttons();
	}

	toggle_buttons() {
		for (let idx = this.current_section; idx < this.sections.length; idx++) {
			if (this.is_next_section_empty(idx)) {
				this.show_save_and_hide_next_button();
			} else {
				this.show_next_and_hide_save_button();
				break;
			}
		}
	}

	is_next_section_empty(section) {
		if (section + 1 > this.sections.length) return true;

		let _section = $(`.form-section:eq(${section + 1})`);
		let visible_controls = _section.find(".frappe-control:not(.hide-control)");

		return !visible_controls.length ? true : false;
	}

	is_previous_section_empty(section) {
		if (section - 1 > this.sections.length) return true;

		let _section = $(`.form-section:eq(${section - 1})`);
		let visible_controls = _section.find(".frappe-control:not(.hide-control)");

		return !visible_controls.length ? true : false;
	}

	show_save_and_hide_next_button() {
		$('.btn-next').hide();
		$('.web-form-footer').show();
	}

	show_next_and_hide_save_button() {
		$('.btn-next').show();
		$('.web-form-footer').hide();
	}

	toggle_previous_button() {
		this.current_section == 0 ? $('.btn-previous').hide() : $('.btn-previous').show();
	}

	show_section() {
		$(`.form-section:eq(${this.current_section})`).show();
	}

	hide_sections() {
		for (let idx=0; idx < this.sections.length; idx++) {
			if (idx !== this.current_section) {
				$(`.form-section:eq(${idx})`).hide();
			}
		}
	}

	save() {
		let is_new = this.is_new;
		if (this.validate && !this.validate()) {
			frappe.throw(__("Couldn't save, please check the data you have entered"), __("Validation Error"));
		}

		// validation hack: get_values will check for missing data
		let doc_values = super.get_values(this.allow_incomplete);

		if (!doc_values) return;

		if (window.saving) return;
		let for_payment = Boolean(this.accept_payment && !this.doc.paid);

		Object.assign(this.doc, doc_values);
		this.doc.doctype = this.doc_type;
		this.doc.web_form_name = this.name;

		// Save
		window.saving = true;
		frappe.form_dirty = false;

		frappe.call({
			type: "POST",
			method: "frappe.website.doctype.web_form.web_form.accept",
			args: {
				data: this.doc,
				web_form: this.name,
				docname: this.doc.name,
				for_payment
			},
			callback: response => {
				// Check for any exception in response
				if (!response.exc) {
					// Success
					this.handle_success(response.message);
					frappe.web_form.events.trigger('after_save');
					this.after_save && this.after_save();
					// args doctype and docname added to link doctype in file manager
					if (is_new && (response.message.attachment || response.message.file)) {
						frappe.call({
							type: 'POST',
							method: "frappe.handler.upload_file",
							args: {
								file_url: response.message.attachment || response.message.file,
								doctype: response.message.doctype,
								docname: response.message.name
							}
						});
					}
				}
			},
			always: function() {
				window.saving = false;
			}
		});
		return true;
	}

	delete() {
		frappe.call({
			type: "POST",
			method: "frappe.website.doctype.web_form.web_form.delete",
			args: {
				web_form_name: this.name,
				docname: this.doc.name
			}
		});
	}

	print() {
		window.open(`/printview?
			doctype=${this.doc_type}
			&name=${this.doc.name}
			&format=${this.print_format || "Standard"}`, '_blank');
	}

	cancel() {
		window.location.href = window.location.pathname;
	}

	handle_success(data) {
		if (this.accept_payment && !this.doc.paid) {
			window.location.href = data;
		}

		const success_dialog = new frappe.ui.Dialog({
			title: __("Saved Successfully"),
			secondary_action: () => {
				if (this.success_url) {
					window.location.href = this.success_url;
				} else if(this.login_required) {
					window.location.href =
						window.location.pathname + "?name=" + data.name;
				}
			}
		});

		success_dialog.show();
		const success_message =
			this.success_message || __("Your information has been submitted");
		success_dialog.set_message(success_message);
	}
}
<commit_msg>fix(styles): show save button for multi-part web form<commit_after>frappe.provide("frappe.ui");
frappe.provide("frappe.web_form");
import EventEmitterMixin from '../../frappe/event_emitter';

export default class WebForm extends frappe.ui.FieldGroup {
	constructor(opts) {
		super();
		Object.assign(this, opts);
		frappe.web_form = this;
		frappe.web_form.events = {};
		Object.assign(frappe.web_form.events, EventEmitterMixin);
		this.current_section = 0;
	}

	prepare(web_form_doc, doc) {
		Object.assign(this, web_form_doc);
		this.fields = web_form_doc.web_form_fields;
		this.doc = doc;
	}

	make() {
		super.make();
		this.set_sections();
		this.set_field_values();
		this.setup_listeners();
		if (this.introduction_text) this.set_form_description(this.introduction_text);
		if (this.allow_print && !this.is_new) this.setup_print_button();
		if (this.allow_delete && !this.is_new) this.setup_delete_button();
		if (this.is_new) this.setup_cancel_button();
		this.setup_primary_action();
		this.setup_previous_next_button();
		this.toggle_section();
		$(".link-btn").remove();

		// webform client script
		frappe.init_client_script && frappe.init_client_script();
		frappe.web_form.events.trigger('after_load');
		this.after_load && this.after_load();
	}

	on(fieldname, handler) {
		let field = this.fields_dict[fieldname];
		field.df.change = () => {
			handler(field, field.value);
		};
	}

	setup_listeners() {
		// Event listener for triggering Save/Next button for Multi Step Forms
		// Do not use `on` event here since that can be used by user which will render this function useless
		// setTimeout has 200ms delay so that all the base_control triggers for the fields have been run
		let me = this;

		if (!me.is_multi_step_form) {
			return;
		}

		for (let field of $(".input-with-feedback")) {
			$(field).change((e) => {
				setTimeout(() => {
					e.stopPropagation();
					me.toggle_buttons();
				}, 200);
			});
		}
	}

	set_sections() {
		if (this.sections.length) return;

		this.sections = $(`.form-section`);
	}

	setup_previous_next_button() {
		let me = this;

		if (!me.is_multi_step_form) {
			return;
		}

		$('.web-form-footer').after(`

				<button class="btn btn-primary btn-previous btn-sm ml-2">${__("Previous")}</button>
				<button class="btn btn-primary btn-next btn-sm ml-2">${__("Next")}</button>

		`);

		$('.btn-previous').on('click', function () {
			let is_validated = me.validate_section();

			if (!is_validated) return;

			/**
				The eslint utility cannot figure out if this is an infinite loop in backwards and
				throws an error. Disabling for-direction just for this section.
				for-direction doesnt throw an error if the values are hardcoded in the
				reverse for-loop, but in this case its a dynamic loop.
				https://eslint.org/docs/rules/for-direction
			*/
			/* eslint-disable for-direction */
			for (let idx = me.current_section; idx < me.sections.length; idx--) {
				let is_empty = me.is_previous_section_empty(idx);
				me.current_section = me.current_section > 0 ? me.current_section - 1 : me.current_section;

				if (!is_empty) {
					break;
				}
			}
			/* eslint-enable for-direction */
			me.toggle_section();
		});

		$('.btn-next').on('click', function () {
			let is_validated = me.validate_section();

			if (!is_validated) return;

			for (let idx = me.current_section; idx < me.sections.length; idx++) {
				let is_empty = me.is_next_section_empty(idx);
				me.current_section = me.current_section < me.sections.length ? me.current_section + 1 : me.current_section;

				if (!is_empty) {
					break;
				}
			}
			me.toggle_section();
		});
	}

	set_field_values() {
		if (this.doc.name) this.set_values(this.doc);
		else return;
	}

	set_default_values() {
		let values = frappe.utils.get_query_params();
		delete values.new;
		this.set_values(values);
	}

	set_form_description(intro) {
		let intro_wrapper = document.getElementById('introduction');
		intro_wrapper.innerHTML = intro;
	}

	add_button(name, type, action, wrapper_class=".web-form-actions") {
		const button = document.createElement("button");
		button.classList.add("btn", "btn-" + type, "btn-sm", "ml-2");
		button.innerHTML = name;
		button.onclick = action;
		document.querySelector(wrapper_class).appendChild(button);
	}

	add_button_to_footer(name, type, action) {
		this.add_button(name, type, action, '.web-form-footer');
	}

	add_button_to_header(name, type, action) {
		this.add_button(name, type, action, '.web-form-actions');
	}

	setup_primary_action() {
		this.add_button_to_header(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
			this.save()
		);

		if (!this.is_multi_step_form && $('main').height() > 600) {
			// add button on footer if page is long
			this.add_button_to_footer(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
				this.save()
			);
		}
	}

	setup_cancel_button() {
		this.add_button_to_header(__("Cancel", null, "Button in web form"), "light", () => this.cancel());
	}

	setup_delete_button() {
		frappe.has_permission(this.doc_type, "", "delete", () => {
			this.add_button_to_header(
				frappe.utils.icon('delete'),
				"danger",
				() => this.delete()
			);
		});
	}

	setup_print_button() {
		this.add_button_to_header(
			frappe.utils.icon('print'),
			"light",
			() => this.print()
		);
	}

	validate_section() {
		if (this.allow_incomplete) return true;

		let fields = $(`.form-section:eq(${this.current_section}) .form-control`);
		let errors = [];
		let invalid_values = [];

		for (let field of fields) {
			let fieldname = $(field).attr("data-fieldname");
			if (!fieldname) continue;

			field = this.fields_dict[fieldname];

			if (field.get_value) {
				let value = field.get_value();
				if (field.df.reqd && is_null(typeof value === 'string' ? strip_html(value) : value)) errors.push(__(field.df.label));

				if (field.df.reqd && field.df.fieldtype === 'Text Editor' && is_null(strip_html(cstr(value)))) errors.push(__(field.df.label));

				if (field.df.invalid) invalid_values.push(__(field.df.label));
			}
		}

		let message = '';
		if (invalid_values.length) {
			message += __('Invalid values for fields:', null, 'Error message in web form');
			message += '

' + invalid_values.join('
') + '
';
		}

		if (errors.length) {
			message += __('Mandatory fields required:', null, 'Error message in web form');
			message += '

' + errors.join('
') + '
';
		}

		if (invalid_values.length || errors.length) {
			frappe.msgprint({
				title: __('Error', null, 'Title of error message in web form'),
				message: message,
				indicator: 'orange'
			});
		}

		return !(errors.length || invalid_values.length);
	}

	toggle_section() {
		if (!this.is_multi_step_form) return;

		this.toggle_previous_button();
		this.hide_sections();
		this.show_section();
		this.toggle_buttons();
	}

	toggle_buttons() {
		for (let idx = this.current_section; idx < this.sections.length; idx++) {
			if (this.is_next_section_empty(idx)) {
				this.show_save_and_hide_next_button();
			} else {
				this.show_next_and_hide_save_button();
				break;
			}
		}
	}

	is_next_section_empty(section) {
		if (section + 1 > this.sections.length) return true;

		let _section = $(`.form-section:eq(${section + 1})`);
		let visible_controls = _section.find(".frappe-control:not(.hide-control)");

		return !visible_controls.length ? true : false;
	}

	is_previous_section_empty(section) {
		if (section - 1 > this.sections.length) return true;

		let _section = $(`.form-section:eq(${section - 1})`);
		let visible_controls = _section.find(".frappe-control:not(.hide-control)");

		return !visible_controls.length ? true : false;
	}

	show_save_and_hide_next_button() {
		$('.btn-next').hide();
		$('.web-form-footer').show();
	}

	show_next_and_hide_save_button() {
		$('.btn-next').show();
		$('.web-form-footer').hide();
	}

	toggle_previous_button() {
		this.current_section == 0 ? $('.btn-previous').hide() : $('.btn-previous').show();
	}

	show_section() {
		$(`.form-section:eq(${this.current_section})`).show();
	}

	hide_sections() {
		for (let idx=0; idx < this.sections.length; idx++) {
			if (idx !== this.current_section) {
				$(`.form-section:eq(${idx})`).hide();
			}
		}
	}

	save() {
		let is_new = this.is_new;
		if (this.validate && !this.validate()) {
			frappe.throw(__("Couldn't save, please check the data you have entered"), __("Validation Error"));
		}

		// validation hack: get_values will check for missing data
		let doc_values = super.get_values(this.allow_incomplete);

		if (!doc_values) return;

		if (window.saving) return;
		let for_payment = Boolean(this.accept_payment && !this.doc.paid);

		Object.assign(this.doc, doc_values);
		this.doc.doctype = this.doc_type;
		this.doc.web_form_name = this.name;

		// Save
		window.saving = true;
		frappe.form_dirty = false;

		frappe.call({
			type: "POST",
			method: "frappe.website.doctype.web_form.web_form.accept",
			args: {
				data: this.doc,
				web_form: this.name,
				docname: this.doc.name,
				for_payment
			},
			callback: response => {
				// Check for any exception in response
				if (!response.exc) {
					// Success
					this.handle_success(response.message);
					frappe.web_form.events.trigger('after_save');
					this.after_save && this.after_save();
					// args doctype and docname added to link doctype in file manager
					if (is_new && (response.message.attachment || response.message.file)) {
						frappe.call({
							type: 'POST',
							method: "frappe.handler.upload_file",
							args: {
								file_url: response.message.attachment || response.message.file,
								doctype: response.message.doctype,
								docname: response.message.name
							}
						});
					}
				}
			},
			always: function() {
				window.saving = false;
			}
		});
		return true;
	}

	delete() {
		frappe.call({
			type: "POST",
			method: "frappe.website.doctype.web_form.web_form.delete",
			args: {
				web_form_name: this.name,
				docname: this.doc.name
			}
		});
	}

	print() {
		window.open(`/printview?
			doctype=${this.doc_type}
			&name=${this.doc.name}
			&format=${this.print_format || "Standard"}`, '_blank');
	}

	cancel() {
		window.location.href = window.location.pathname;
	}

	handle_success(data) {
		if (this.accept_payment && !this.doc.paid) {
			window.location.href = data;
		}

		const success_dialog = new frappe.ui.Dialog({
			title: __("Saved Successfully"),
			secondary_action: () => {
				if (this.success_url) {
					window.location.href = this.success_url;
				} else if(this.login_required) {
					window.location.href =
						window.location.pathname + "?name=" + data.name;
				}
			}
		});

		success_dialog.show();
		const success_message =
			this.success_message || __("Your information has been submitted");
		success_dialog.set_message(success_message);
	}
}
<|endoftext|>


<commit_before>/*jshint node: true */
'use strict';

/*
* Name :  PagesListView.js
* Module : FrontEnd::View::PagesView
* Location : /frontend/app/PagesView
*
* History :
* Version       Date        Programmer                  Description
* =================================================================================================
* 0.1.1         2015-06-25  Maria Giovanna Chinellato   Fix test
*
* 1.0.0         2015-06-14  Maria Giovanna Chinellato   Tested
*
* 0.0.2         2015-06-04  Maria Giovanna Chinellato   Add link function, observe and watch
*
* 0.0.1         2015-05-30  Maria Giovanna Chinellato   Initial code
* =================================================================================================
*/

angular.module('norris-nrti')
.directive('pagesList', function(){
	return {
		restrict: 'E', // direttiva di tipo elemento (tag)
		replace: false,
		controller: 'PagesListController', // controller associato
		scope: {},
		template: '
' +
					'
' +
					'
' +
						' {{ page.page.getName() }} ' +
						'
 {{ page.page.getDescription() }}
' +
					'
' +
					'
' +
			'
', // template HTML inserito dalla direttiva
		link: function (scope, element, attrs) {
			scope.socketConnection(); // richiama la funzione del controller che permette di connettersi al server
		}

	};
});
<commit_msg>Update PagesListView.js<commit_after>/*jshint node: true */
'use strict';

/*
* Name :  PagesListView.js
* Module : FrontEnd::View::PagesView
* Location : /frontend/app/PagesView
*
* History :
* Version       Date        Programmer                  Description
* =================================================================================================
* 0.1.1         2015-06-25  Maria Giovanna Chinellato   Fix test
*
* 1.0.0         2015-06-14  Maria Giovanna Chinellato   Tested
*
* 0.0.2         2015-06-04  Maria Giovanna Chinellato   Add link function, observe and watch
*
* 0.0.1         2015-05-30  Maria Giovanna Chinellato   Initial code
* =================================================================================================
*/

angular.module('norris-nrti')
.directive('pagesList', function(){
	return {
		restrict: 'E', // direttiva di tipo elemento (tag)
		replace: false,
		controller: 'PagesListController', // controller associato
		scope: {},
		template: '
' +
					'
' +
					'
' +
						' {{ page.page.getName() }} ' +
						'
 {{ page.page.getDescription() }}
' +
					'
' +
					'
' +
			'
', // template HTML inserito dalla direttiva
		link: function (scope, element, attrs) {
			scope.socketConnection(); // richiama la funzione del controller che permette di connettersi al server
		}

	};
});
<|endoftext|>


<commit_before>import core from 'core-js';
import {hyphenate} from './util';
import {ONE_WAY,TWO_WAY,ONE_TIME} from 'aurelia-binding';

function getObserver(behavior, instance, name){
  var lookup = instance.__observers__;

  if(lookup === undefined){
    lookup = behavior.observerLocator.getObserversLookup(instance);
    behavior.ensurePropertiesDefined(instance, lookup);
  }

  return lookup[name];
}

export class BindableProperty {
  constructor(nameOrConfig){
    if(typeof nameOrConfig === 'string'){
      this.name = nameOrConfig;
    }else{
      Object.assign(this, nameOrConfig);
    }

    this.attribute = this.attribute || hyphenate(this.name);
    this.defaultBindingMode = this.defaultBindingMode || ONE_WAY;
    this.owner = null;
  }

  registerWith(target, behavior){
    behavior.properties.push(this);
    behavior.attributes[this.attribute] = this;
    this.owner = behavior;
  }

  defineOn(target, behavior){
    var name = this.name,
        handlerName;

    if(this.changeHandler === undefined){
      handlerName = name + 'Changed';
      if(handlerName in target.prototype){
        this.changeHandler = handlerName;
      }
    }

    Object.defineProperty(target.prototype, name, {
      configurable: true,
      enumerable: true,
      get: function(){
        return getObserver(behavior, this, name).getValue();
      },
      set: function(value){
        getObserver(behavior, this, name).setValue(value);
      }
    });
  }

  createObserver(executionContext){
    var selfSubscriber = null;

    if(this.hasOptions){
      return;
    }

    if(this.changeHandler !== undefined){
      selfSubscriber = (newValue, oldValue) => executionContext[this.changeHandler](newValue, oldValue);
    }

    return new BehaviorPropertyObserver(this.owner.taskQueue, executionContext, this.name, selfSubscriber);
  }

  initialize(executionContext, observerLookup, attributes, behaviorHandlesBind, boundProperties){
    var selfSubscriber, observer, attribute;

    if(this.isDynamic){
      for(let key in attributes){
        this.createDynamicProperty(executionContext, observerLookup, behaviorHandlesBind, key, attributes[key], boundProperties);
      }
    } else if(!this.hasOptions){
      observer = observerLookup[this.name];

      if(attributes !== undefined){
        selfSubscriber = observer.selfSubscriber;
        attribute = attributes[this.attribute];

        if(behaviorHandlesBind){
          observer.selfSubscriber = null;
        }

        if(typeof attribute === 'string'){
          executionContext[this.name] = attribute;
          observer.call();
        }else if(attribute){
          boundProperties.push({observer:observer, binding:attribute.createBinding(executionContext)});
        }else if(this.defaultValue){
          executionContext[this.name] = this.defaultValue;
          observer.call();
        }

        observer.selfSubscriber = selfSubscriber;
      }

      observer.publishing = true;
    }
  }

  createDynamicProperty(executionContext, observerLookup, behaviorHandlesBind, name, attribute, boundProperties){
    var changeHandlerName = name + 'Changed',
        selfSubscriber = null, observer, info;

    if(changeHandlerName in executionContext){
      selfSubscriber = (newValue, oldValue) => executionContext[changeHandlerName](newValue, oldValue);
    } else if ('dynamicPropertyChanged' in executionContext) {
      selfSubscriber = (newValue, oldValue) => executionContext['dynamicPropertyChanged'](name, newValue, oldValue);
    }

    observer = observerLookup[name] = new BehaviorPropertyObserver(
        this.owner.taskQueue,
        executionContext,
        name,
        selfSubscriber
        );

    Object.defineProperty(executionContext, name, {
      configurable: true,
      enumerable: true,
      get: observer.getValue.bind(observer),
      set: observer.setValue.bind(observer)
    });

    if(behaviorHandlesBind){
      observer.selfSubscriber = null;
    }

    if(typeof attribute === 'string'){
      executionContext[name] = attribute;
      observer.call();
    }else if(attribute){
      info = {observer:observer, binding:attribute.createBinding(executionContext)};
      boundProperties.push(info);
    }

    observer.publishing = true;
    observer.selfSubscriber = selfSubscriber;
  }
}

class BehaviorPropertyObserver {
  constructor(taskQueue, obj, propertyName, selfSubscriber){
    this.taskQueue = taskQueue;
    this.obj = obj;
    this.propertyName = propertyName;
    this.callbacks = [];
    this.notqueued = true;
    this.publishing = false;
    this.selfSubscriber = selfSubscriber;
  }

  getValue(){
    return this.currentValue;
  }

  setValue(newValue){
    var oldValue = this.currentValue;

    if(oldValue != newValue){
      if(this.publishing && this.notqueued){
        this.notqueued = false;
        this.taskQueue.queueMicroTask(this);
      }

      this.oldValue = oldValue;
      this.currentValue = newValue;
    }
  }

  call(){
    var callbacks = this.callbacks,
        i = callbacks.length,
        oldValue = this.oldValue,
        newValue = this.currentValue;

    this.notqueued = true;

    if(newValue != oldValue){
      if(this.selfSubscriber !== null){
        this.selfSubscriber(newValue, oldValue);
      }

      while(i--) {
        callbacks[i](newValue, oldValue);
      }

      this.oldValue = newValue;
    }
  }

  subscribe(callback){
    var callbacks = this.callbacks;
    callbacks.push(callback);
    return function(){
      callbacks.splice(callbacks.indexOf(callback), 1);
    };
  }
}
<commit_msg>fix(bindable-property): ensure changeHandler is always defined, but null by default<commit_after>import core from 'core-js';
import {hyphenate} from './util';
import {ONE_WAY,TWO_WAY,ONE_TIME} from 'aurelia-binding';

function getObserver(behavior, instance, name){
  var lookup = instance.__observers__;

  if(lookup === undefined){
    lookup = behavior.observerLocator.getObserversLookup(instance);
    behavior.ensurePropertiesDefined(instance, lookup);
  }

  return lookup[name];
}

export class BindableProperty {
  constructor(nameOrConfig){
    if(typeof nameOrConfig === 'string'){
      this.name = nameOrConfig;
    }else{
      Object.assign(this, nameOrConfig);
    }

    this.attribute = this.attribute || hyphenate(this.name);
    this.defaultBindingMode = this.defaultBindingMode || ONE_WAY;
    this.changeHandler = this.changeHandler || null;
    this.owner = null;
  }

  registerWith(target, behavior){
    behavior.properties.push(this);
    behavior.attributes[this.attribute] = this;
    this.owner = behavior;
  }

  defineOn(target, behavior){
    var name = this.name,
        handlerName;

    if(this.changeHandler === null){
      handlerName = name + 'Changed';
      if(handlerName in target.prototype){
        this.changeHandler = handlerName;
      }
    }

    Object.defineProperty(target.prototype, name, {
      configurable: true,
      enumerable: true,
      get: function(){
        return getObserver(behavior, this, name).getValue();
      },
      set: function(value){
        getObserver(behavior, this, name).setValue(value);
      }
    });
  }

  createObserver(executionContext){
    var selfSubscriber = null;

    if(this.hasOptions){
      return;
    }

    if(this.changeHandler !== null){
      selfSubscriber = (newValue, oldValue) => executionContext[this.changeHandler](newValue, oldValue);
    }

    return new BehaviorPropertyObserver(this.owner.taskQueue, executionContext, this.name, selfSubscriber);
  }

  initialize(executionContext, observerLookup, attributes, behaviorHandlesBind, boundProperties){
    var selfSubscriber, observer, attribute;

    if(this.isDynamic){
      for(let key in attributes){
        this.createDynamicProperty(executionContext, observerLookup, behaviorHandlesBind, key, attributes[key], boundProperties);
      }
    } else if(!this.hasOptions){
      observer = observerLookup[this.name];

      if(attributes !== undefined){
        selfSubscriber = observer.selfSubscriber;
        attribute = attributes[this.attribute];

        if(behaviorHandlesBind){
          observer.selfSubscriber = null;
        }

        if(typeof attribute === 'string'){
          executionContext[this.name] = attribute;
          observer.call();
        }else if(attribute){
          boundProperties.push({observer:observer, binding:attribute.createBinding(executionContext)});
        }else if(this.defaultValue){
          executionContext[this.name] = this.defaultValue;
          observer.call();
        }

        observer.selfSubscriber = selfSubscriber;
      }

      observer.publishing = true;
    }
  }

  createDynamicProperty(executionContext, observerLookup, behaviorHandlesBind, name, attribute, boundProperties){
    var changeHandlerName = name + 'Changed',
        selfSubscriber = null, observer, info;

    if(changeHandlerName in executionContext){
      selfSubscriber = (newValue, oldValue) => executionContext[changeHandlerName](newValue, oldValue);
    } else if ('dynamicPropertyChanged' in executionContext) {
      selfSubscriber = (newValue, oldValue) => executionContext['dynamicPropertyChanged'](name, newValue, oldValue);
    }

    observer = observerLookup[name] = new BehaviorPropertyObserver(
        this.owner.taskQueue,
        executionContext,
        name,
        selfSubscriber
        );

    Object.defineProperty(executionContext, name, {
      configurable: true,
      enumerable: true,
      get: observer.getValue.bind(observer),
      set: observer.setValue.bind(observer)
    });

    if(behaviorHandlesBind){
      observer.selfSubscriber = null;
    }

    if(typeof attribute === 'string'){
      executionContext[name] = attribute;
      observer.call();
    }else if(attribute){
      info = {observer:observer, binding:attribute.createBinding(executionContext)};
      boundProperties.push(info);
    }

    observer.publishing = true;
    observer.selfSubscriber = selfSubscriber;
  }
}

class BehaviorPropertyObserver {
  constructor(taskQueue, obj, propertyName, selfSubscriber){
    this.taskQueue = taskQueue;
    this.obj = obj;
    this.propertyName = propertyName;
    this.callbacks = [];
    this.notqueued = true;
    this.publishing = false;
    this.selfSubscriber = selfSubscriber;
  }

  getValue(){
    return this.currentValue;
  }

  setValue(newValue){
    var oldValue = this.currentValue;

    if(oldValue != newValue){
      if(this.publishing && this.notqueued){
        this.notqueued = false;
        this.taskQueue.queueMicroTask(this);
      }

      this.oldValue = oldValue;
      this.currentValue = newValue;
    }
  }

  call(){
    var callbacks = this.callbacks,
        i = callbacks.length,
        oldValue = this.oldValue,
        newValue = this.currentValue;

    this.notqueued = true;

    if(newValue != oldValue){
      if(this.selfSubscriber !== null){
        this.selfSubscriber(newValue, oldValue);
      }

      while(i--) {
        callbacks[i](newValue, oldValue);
      }

      this.oldValue = newValue;
    }
  }

  subscribe(callback){
    var callbacks = this.callbacks;
    callbacks.push(callback);
    return function(){
      callbacks.splice(callbacks.indexOf(callback), 1);
    };
  }
}
<|endoftext|>


<commit_before>set_global('narrow_state', {});
set_global('unread', {});
set_global('muting', {});
set_global('message_list', {});
set_global('i18n', global.stub_i18n);

zrequire('hash_util');
zrequire('stream_data');
zrequire('unread');
zrequire('topic_data');
const topic_list_data = zrequire('topic_list_data');

const general = {
    stream_id: 556,
    name: 'general',
};

stream_data.add_sub(general);

function clear() {
    narrow_state.topic = () => undefined;
    topic_data.reset();
    muting.is_topic_muted = () => false;
}

function get_list_info(zoomed) {
    const stream_id = general.stream_id;
    return topic_list_data.get_list_info(
        stream_id, zoomed);
}

run_test('get_list_info w/real topic_data', () => {
    clear();

    let list_info;
    const empty_list_info = get_list_info();

    assert.deepEqual(empty_list_info, {
        items: [],
        more_topics_unreads: 0,
        num_possible_topics: 0,
    });

    for (const i of _.range(7)) {
        const topic_name = 'topic ' + i;
        topic_data.add_message({
            stream_id: general.stream_id,
            topic_name: topic_name,
            message_id: 1000 + i,
        });
    }

    narrow_state.topic = () => 'topic 6';

    list_info = get_list_info();
    assert.equal(list_info.items.length, 5);
    assert.equal(list_info.more_topics_unreads, 0);
    assert.equal(list_info.num_possible_topics, 7);

    assert.deepEqual(list_info.items[0], {
        is_active_topic: true,
        is_muted: false,
        is_zero: true,
        topic_name: 'topic 6',
        unread: 0,
        url: '#narrow/stream/556-general/topic/topic.206',
    });

    // If we zoom in, we'll show all 7 topics.
    const zoomed = true;
    list_info = get_list_info(zoomed);
    assert.equal(list_info.items.length, 7);
    assert.equal(list_info.more_topics_unreads, 0);
    assert.equal(list_info.num_possible_topics, 7);
});

run_test('get_list_info unreads', () => {
    clear();

    let list_info;

    // Going forward, we just stub get_recent_names
    // for simpler test setup.
    topic_data.get_recent_names = () => {
        return _.range(15).map(i => 'topic ' + i);
    };

    const unread_cnt = {};
    unread.num_unread_for_topic = (stream_id, topic_name) => {
        assert.equal(stream_id, general.stream_id);
        return unread_cnt[topic_name] || 0;
    };

    /*
        We have 15 topics, but we only show up
        to 8 topics, depending on how many have
        unread counts.  We only show a max of 5
        fully-read topics.

        So first we'll get 7 topics, where 2 are
        unread.
    */
    unread_cnt['topic 8'] = 8;
    unread_cnt['topic 9'] = 9;

    list_info = get_list_info();
    assert.equal(list_info.items.length, 7);
    assert.equal(list_info.more_topics_unreads, 0);
    assert.equal(list_info.num_possible_topics, 15);

    assert.deepEqual(
        list_info.items.map(li => li.topic_name),
        [
            'topic 0',
            'topic 1',
            'topic 2',
            'topic 3',
            'topic 4',
            'topic 8',
            'topic 9',
        ]);

    unread_cnt['topic 6'] = 6;
    unread_cnt['topic 7'] = 7;

    list_info = get_list_info();
    assert.equal(list_info.items.length, 8);
    assert.equal(list_info.more_topics_unreads, 9);
    assert.equal(list_info.num_possible_topics, 15);

    assert.deepEqual(
        list_info.items.map(li => li.topic_name),
        [
            'topic 0',
            'topic 1',
            'topic 2',
            'topic 3',
            'topic 4',
            'topic 6',
            'topic 7',
            'topic 8',
        ]);

    unread_cnt['topic 4'] = 4;
    unread_cnt['topic 5'] = 5;
    unread_cnt['topic 13'] = 13;

    muting.is_topic_muted = (stream_id, topic_name) => {
        assert.equal(stream_id, general.stream_id);
        return topic_name === 'topic 4';
    };

    list_info = get_list_info();
    assert.equal(list_info.items.length, 8);
    assert.equal(list_info.more_topics_unreads, 9 + 13);
    assert.equal(list_info.num_possible_topics, 15);

    assert.deepEqual(
        list_info.items.map(li => li.topic_name),
        [
            'topic 0',
            'topic 1',
            'topic 2',
            'topic 3',
            'topic 5',
            'topic 6',
            'topic 7',
            'topic 8',
        ]);
});
<commit_msg>tests: Convert unread_cnt from object to Map.<commit_after>set_global('narrow_state', {});
set_global('unread', {});
set_global('muting', {});
set_global('message_list', {});
set_global('i18n', global.stub_i18n);

zrequire('hash_util');
zrequire('stream_data');
zrequire('unread');
zrequire('topic_data');
const topic_list_data = zrequire('topic_list_data');

const general = {
    stream_id: 556,
    name: 'general',
};

stream_data.add_sub(general);

function clear() {
    narrow_state.topic = () => undefined;
    topic_data.reset();
    muting.is_topic_muted = () => false;
}

function get_list_info(zoomed) {
    const stream_id = general.stream_id;
    return topic_list_data.get_list_info(
        stream_id, zoomed);
}

run_test('get_list_info w/real topic_data', () => {
    clear();

    let list_info;
    const empty_list_info = get_list_info();

    assert.deepEqual(empty_list_info, {
        items: [],
        more_topics_unreads: 0,
        num_possible_topics: 0,
    });

    for (const i of _.range(7)) {
        const topic_name = 'topic ' + i;
        topic_data.add_message({
            stream_id: general.stream_id,
            topic_name: topic_name,
            message_id: 1000 + i,
        });
    }

    narrow_state.topic = () => 'topic 6';

    list_info = get_list_info();
    assert.equal(list_info.items.length, 5);
    assert.equal(list_info.more_topics_unreads, 0);
    assert.equal(list_info.num_possible_topics, 7);

    assert.deepEqual(list_info.items[0], {
        is_active_topic: true,
        is_muted: false,
        is_zero: true,
        topic_name: 'topic 6',
        unread: 0,
        url: '#narrow/stream/556-general/topic/topic.206',
    });

    // If we zoom in, we'll show all 7 topics.
    const zoomed = true;
    list_info = get_list_info(zoomed);
    assert.equal(list_info.items.length, 7);
    assert.equal(list_info.more_topics_unreads, 0);
    assert.equal(list_info.num_possible_topics, 7);
});

run_test('get_list_info unreads', () => {
    clear();

    let list_info;

    // Going forward, we just stub get_recent_names
    // for simpler test setup.
    topic_data.get_recent_names = () => {
        return _.range(15).map(i => 'topic ' + i);
    };

    const unread_cnt = new Map();
    unread.num_unread_for_topic = (stream_id, topic_name) => {
        assert.equal(stream_id, general.stream_id);
        return unread_cnt.get(topic_name) || 0;
    };

    /*
        We have 15 topics, but we only show up
        to 8 topics, depending on how many have
        unread counts.  We only show a max of 5
        fully-read topics.

        So first we'll get 7 topics, where 2 are
        unread.
    */
    unread_cnt.set('topic 8', 8);
    unread_cnt.set('topic 9', 9);

    list_info = get_list_info();
    assert.equal(list_info.items.length, 7);
    assert.equal(list_info.more_topics_unreads, 0);
    assert.equal(list_info.num_possible_topics, 15);

    assert.deepEqual(
        list_info.items.map(li => li.topic_name),
        [
            'topic 0',
            'topic 1',
            'topic 2',
            'topic 3',
            'topic 4',
            'topic 8',
            'topic 9',
        ]);

    unread_cnt.set('topic 6', 6);
    unread_cnt.set('topic 7', 7);

    list_info = get_list_info();
    assert.equal(list_info.items.length, 8);
    assert.equal(list_info.more_topics_unreads, 9);
    assert.equal(list_info.num_possible_topics, 15);

    assert.deepEqual(
        list_info.items.map(li => li.topic_name),
        [
            'topic 0',
            'topic 1',
            'topic 2',
            'topic 3',
            'topic 4',
            'topic 6',
            'topic 7',
            'topic 8',
        ]);

    unread_cnt.set('topic 4', 4);
    unread_cnt.set('topic 5', 5);
    unread_cnt.set('topic 13', 13);

    muting.is_topic_muted = (stream_id, topic_name) => {
        assert.equal(stream_id, general.stream_id);
        return topic_name === 'topic 4';
    };

    list_info = get_list_info();
    assert.equal(list_info.items.length, 8);
    assert.equal(list_info.more_topics_unreads, 9 + 13);
    assert.equal(list_info.num_possible_topics, 15);

    assert.deepEqual(
        list_info.items.map(li => li.topic_name),
        [
            'topic 0',
            'topic 1',
            'topic 2',
            'topic 3',
            'topic 5',
            'topic 6',
            'topic 7',
            'topic 8',
        ]);
});
<|endoftext|>


<commit_before>$(function () {
	$(".open-menu").on("click.open-menu", OpenMenu);

	// TODO: Add middle click and shift click events too.
	$("[clickable-link-parent]").on("mousedown", OpenLink);

	ShowPages();
});

function ShowPages() {
	$(".pages").find("li:first").on("click", function () {
		$(this).parent().find(".page").removeClass("hidden");
	});

	$(".pages").each(function () {
		var pages = $(this).find(".page");

		for (var i = window.currentPage - 2; i < window.currentPage; i++) {
			if (i < 0)
				continue;

			$(pages[i - 1]).removeClass("hidden");
		}

		for (var i = window.currentPage; i <= window.currentPage + 2; i++) {
			if (i - 1 > pages.length)
				continue;

			$(pages[i - 1]).removeClass("hidden");
		}
	});
}

function OpenLink(event) {
	var url;

	if ($(event.target).is("a"))
		url = $(event.target).attr("href");
	else
		url = $(event.target).find("a").eq(0).attr("href");

	switch (event.which) {
		case 1:
			if (event.shiftKey)
				window.open(url, "_blank");
			else
				window.location.href = url;
			break;

		case 2:
			window.open(url, "_blank");
			break;
	}

	return true;
}

function OpenMenu() {
	CloseMenu();

	$(this).off("click.open-menu");
	$(this).on("click.close-menu", CloseMenu);
	$(this).find(".drop-down-menu-wrapper").removeClass("hidden");

	setTimeout(function () {
		$("body").on("click.close-menu", CloseMenu);
	}, 50);
}

function CloseMenu() {
	var dropDownMenus = $(".drop-down-menu-wrapper");

	for (var i = 0; i < dropDownMenus.length; i++) {
		var dropDownMenu = $(dropDownMenus[i]);

		if (!dropDownMenu.hasClass("hidden"))
			dropDownMenu.addClass("hidden");
	}

	$(".open-menu").off("click.close-menu");
	$("body").off("click.close-menu");

	$(".open-menu").off("click.open-menu");
	$(".open-menu").on("click.open-menu", OpenMenu);
}

function PostToPath(path, parameters) {
	var antiForgeryTokenValue = $("input[name=__RequestVerificationToken]").val();

	var form = $('<form></form>');

	form.attr("method", "post");
	form.attr("action", path);

	var antiForgeryToken = $("<input />");
	antiForgeryToken.attr("type", "hidden");
	antiForgeryToken.attr("name", "__RequestVerificationToken");
	antiForgeryToken.attr("value", antiForgeryTokenValue);
	form.append(antiForgeryToken);

	$.each(parameters, function (key, value) {
		var field = $('<input></input>');

		field.attr("type", "hidden");
		field.attr("name", key);
		field.attr("value", value);

		form.append(field);
	});

	$(document.body).append(form);
	form.submit();
}<commit_msg>Fixed a bug where clickable link parents weren't finding the href.<commit_after>$(function () {
	$(".open-menu").on("click.open-menu", OpenMenu);

	// TODO: Add middle click and shift click events too.
	$("[clickable-link-parent]").on("mousedown", OpenLink);

	ShowPages();
});

function ShowPages() {
	$(".pages").find("li:first").on("click", function () {
		$(this).parent().find(".page").removeClass("hidden");
	});

	$(".pages").each(function () {
		var pages = $(this).find(".page");

		for (var i = window.currentPage - 2; i < window.currentPage; i++) {
			if (i < 0)
				continue;

			$(pages[i - 1]).removeClass("hidden");
		}

		for (var i = window.currentPage; i <= window.currentPage + 2; i++) {
			if (i - 1 > pages.length)
				continue;

			$(pages[i - 1]).removeClass("hidden");
		}
	});
}

function OpenLink(event) {
	event.stopPropagation();

	var url;

	if ($(event.target).is("a"))
		url = $(event.target).attr("href");
	else
		url = $(event.target).closest("[clickable-link-parent]").find("a").eq(0).attr("href");

	switch (event.which) {
		case 1:
			if (event.shiftKey)
				window.open(url, "_blank");
			else
				window.location.href = url;
			break;

		case 2:
			window.open(url, "_blank");
			break;
	}

	return true;
}

function OpenMenu() {
	CloseMenu();

	$(this).off("click.open-menu");
	$(this).on("click.close-menu", CloseMenu);
	$(this).find(".drop-down-menu-wrapper").removeClass("hidden");

	setTimeout(function () {
		$("body").on("click.close-menu", CloseMenu);
	}, 50);
}

function CloseMenu() {
	var dropDownMenus = $(".drop-down-menu-wrapper");

	for (var i = 0; i < dropDownMenus.length; i++) {
		var dropDownMenu = $(dropDownMenus[i]);

		if (!dropDownMenu.hasClass("hidden"))
			dropDownMenu.addClass("hidden");
	}

	$(".open-menu").off("click.close-menu");
	$("body").off("click.close-menu");

	$(".open-menu").off("click.open-menu");
	$(".open-menu").on("click.open-menu", OpenMenu);
}

function PostToPath(path, parameters) {
	var antiForgeryTokenValue = $("input[name=__RequestVerificationToken]").val();

	var form = $('<form></form>');

	form.attr("method", "post");
	form.attr("action", path);

	var antiForgeryToken = $("<input />");
	antiForgeryToken.attr("type", "hidden");
	antiForgeryToken.attr("name", "__RequestVerificationToken");
	antiForgeryToken.attr("value", antiForgeryTokenValue);
	form.append(antiForgeryToken);

	$.each(parameters, function (key, value) {
		var field = $('<input></input>');

		field.attr("type", "hidden");
		field.attr("name", key);
		field.attr("value", value);

		form.append(field);
	});

	$(document.body).append(form);
	form.submit();
}<|endoftext|>


<commit_before>/* @fileoverview
 * Provides full Bootstrap based, multi-instance WYSIWYG editor.
 *
 * "Name"    = 'bootstrap-wysiwyg'
 * "Author"  = 'Various, see LICENCE'
 * "Version" = '1.0.4'
 * "About"   = 'A tiny Bootstrap and jQuery based WYSIWYG rich text editor based on the browser function execCommand.'
 */
(function ($) {
	'use strict';
	/** underscoreThrottle()
	 * 	From underscore http://underscorejs.org/docs/underscore.html
	 */
	var underscoreThrottle = function(func, wait) {
		var context, args, timeout, result;
		var previous = 0;
		var later = function() {
			previous = new Date();
			timeout = null;
			result = func.apply(context, args);
		};
		return function() {
			var now = new Date();
			var remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0) {
				clearTimeout(timeout);
				timeout = null;
				previous = now;
				result = func.apply(context, args);
			} else if (!timeout) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	};
	var readFileIntoDataUrl = function (fileInfo) {
		var loader = $.Deferred(),
			fReader = new FileReader();
		fReader.onload = function (e) {
			loader.resolve(e.target.result);
		};
		fReader.onerror = loader.reject;
		fReader.onprogress = loader.notify;
		fReader.readAsDataURL(fileInfo);
		return loader.promise();
	};
	$.fn.cleanHtml = function (o) {
		if ( $(this).data("wysiwyg-html-mode") === true ) {
			$(this).html($(this).text());
        	$(this).attr('contenteditable',true);
        	$(this).data('wysiwyg-html-mode',false);
		}

		// Strip the images with src="data:image/.." out;
		if ( o === true && $(this).parent().is("form") ) {
			var gGal = $(this).html;
			if ( $(gGal).has( "img" ).length ) {
				var gImages = $( "img", $(gGal));
				var gResults = [];
				var gEditor = $(this).parent();
				$.each(gImages, function(i,v) {
					if ( $(v).attr('src').match(/^data:image\/.*$/) ) {
						gResults.push(gImages[i]);
						$(gEditor).prepend("<input value='"+$(v).attr('src')+"' type='hidden' name='postedimage/"+i+"' />");
						$(v).attr('src', 'postedimage/'+i);
				}});
			}
		}
		var html = $(this).html();
		return html && html.replace(/(
|\s|

<\/div>| )*$/, '');
	};
	$.fn.wysiwyg = function (userOptions) {
		var editor = this,
			wrapper = $(editor).parent(),
			selectedRange,
			options,
			toolbarBtnSelector,
			updateToolbar = function () {
				if (options.activeToolbarClass) {
					$(options.toolbarSelector,wrapper).find(toolbarBtnSelector).each(underscoreThrottle(function () {
						var commandArr = $(this).data(options.commandRole).split(' '),
							command = commandArr[0];

						// If the command has an argument and its value matches this button. == used for string/number comparison
						if (commandArr.length > 1 && document.queryCommandEnabled(command) && document.queryCommandValue(command) === commandArr[1]) {
							$(this).addClass(options.activeToolbarClass);
						// Else if the command has no arguments and it is active
						} else if (commandArr.length === 1 && document.queryCommandEnabled(command) && document.queryCommandState(command)) {
							$(this).addClass(options.activeToolbarClass);
						// Else the command is not active
						} else {
							$(this).removeClass(options.activeToolbarClass);
						}
					}, options.keypressTimeout));
				}
			},
			execCommand = function (commandWithArgs, valueArg) {
				var commandArr = commandWithArgs.split(' '),
					command = commandArr.shift(),
					args = commandArr.join(' ') + (valueArg || '');

				var parts = commandWithArgs.split('-');

				if ( parts.length === 1 ) {
					document.execCommand(command, 0, args);
				}
				else if ( parts[0] === 'format' && parts.length === 2 ) {
					document.execCommand('formatBlock', false, parts[1] );
				}

				editor.trigger('change');
				updateToolbar();
			},
			bindHotkeys = function (hotKeys) {
				$.each(hotKeys, function (hotkey, command) {
					editor.keydown(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
							execCommand(command);
						}
					}).keyup(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
						}
					});
				});

				editor.keyup(function(){ editor.trigger('change'); });
			},
			getCurrentRange = function () {
                var sel, range;
                if (window.getSelection) {
                    sel = window.getSelection();
                    if (sel.getRangeAt && sel.rangeCount) {
                        range = sel.getRangeAt(0);
				    }
                } else if (document.selection) {
                    range = document.selection.createRange();
                } return range;
			},
			saveSelection = function () {
				selectedRange = getCurrentRange();
			},
			restoreSelection = function () {
				var selection;
                if (window.getSelection || document.createRange) {
                    selection = window.getSelection();
                    if (selectedRange) {
                        try {
                            selection.removeAllRanges();
                        } catch (ex) {
                            document.body.createTextRange().select();
                            document.selection.empty();
                        }
                        selection.addRange(selectedRange);
                    }
                }
                else if (document.selection && selectedRange) {
                	selectedRange.select();
                }
			},

			// Adding Toggle HTML based on the work by @jd0000, but cleaned up a little to work in this context.
            toggleHtmlEdit = function() {
				if ( $(editor).data("wysiwyg-html-mode") !== true ) {
					var oContent = $(editor).html();
					var editorPre = $( "
" );
                	$(editorPre).append( document.createTextNode( oContent ) );
                	$(editorPre).attr('contenteditable',true);
                	$(editor).html(' ');
                	$(editor).append($(editorPre));
                    $(editor).attr('contenteditable', false);
                    $(editor).data("wysiwyg-html-mode", true);
                    $(editorPre).focus();
                }
                else {
                	$(editor).html($(editor).text());
                	$(editor).attr('contenteditable',true);
                	$(editor).data('wysiwyg-html-mode',false);
                    $(editor).focus();
                }
            },

			insertFiles = function (files) {
				editor.focus();
				$.each(files, function (idx, fileInfo) {
					if (/^image\//.test(fileInfo.type)) {
						$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
							execCommand('insertimage', dataUrl);
							editor.trigger('image-inserted');
						}).fail(function (e) {
							options.fileUploadError("file-reader", e);
						});
					} else {
						options.fileUploadError("unsupported-file-type", fileInfo.type);
					}
				});
			},
			markSelection = function (input, color) {
				restoreSelection();
				if (document.queryCommandSupported('hiliteColor')) {
					document.execCommand('hiliteColor', 0, color || 'transparent');
				}
				saveSelection();
				input.data(options.selectionMarker, color);
			},
			bindToolbar = function (toolbar, options) {
				toolbar.find(toolbarBtnSelector, wrapper).click(function () {
					restoreSelection();
					editor.focus();

                    if ($(this).data(options.commandRole) === 'html') {
                        toggleHtmlEdit();
                    }
                    else {
                    	execCommand($(this).data(options.commandRole));
                    }
					saveSelection();
				});
				toolbar.find('[data-toggle=dropdown]').click(restoreSelection);

				toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
					var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
					this.value = '';
					restoreSelection();
					if (newValue) {
						editor.focus();
						execCommand($(this).data(options.commandRole), newValue);
					}
					saveSelection();
				}).on('focus', function () {
					var input = $(this);
					if (!input.data(options.selectionMarker)) {
						markSelection(input, options.selectionColor);
						input.focus();
					}
				}).on('blur', function () {
					var input = $(this);
					if (input.data(options.selectionMarker)) {
						markSelection(input, false);
					}
				});
				toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
					restoreSelection();
					if (this.type === 'file' && this.files && this.files.length > 0) {
						insertFiles(this.files);
					}
					saveSelection();
					this.value = '';
				});
			},
			initFileDrops = function () {
				editor.on('dragenter dragover', false)
					.on('drop', function (e) {
						var dataTransfer = e.originalEvent.dataTransfer;
						e.stopPropagation();
						e.preventDefault();
						if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
							insertFiles(dataTransfer.files);
						}
					});
			};
		options = $.extend(true, {}, $.fn.wysiwyg.defaults, userOptions);
		toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
		bindHotkeys(options.hotKeys);

		// Support placeholder attribute on the DIV
		if ($(this).attr('placeholder') !== '') {
			$(this).addClass('placeholderText');
			$(this).html($(this).attr('placeholder'));
			$(this).bind('focus',function() {
				if ( $(this).attr('placeholder') !== '' && $(this).text() === $(this).attr('placeholder') ) {
					$(this).removeClass('placeholderText');
					$(this).html('');
				}
			});
			$(this).bind('blur',function() {
				if ( $(this).attr('placeholder') !== '' && $(this).text() === '' ) {
					$(this).addClass('placeholderText');
					$(this).html($(this).attr('placeholder'));
				}
			});
		}

		if (options.dragAndDropImages) {
			initFileDrops();
		}
		bindToolbar($(options.toolbarSelector), options);
		editor.attr('contenteditable', true)
			.on('mouseup keyup mouseout', function () {
				saveSelection();
				updateToolbar();
			});
		$(window).bind('touchend', function (e) {
			var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
				currentRange = getCurrentRange(),
				clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
			if (!clear || isInside) {
				saveSelection();
				updateToolbar();
			}
		});
		return this;
	};
	$.fn.wysiwyg.defaults = {
		hotKeys: {
			'Ctrl+b meta+b': 'bold',
			'Ctrl+i meta+i': 'italic',
			'Ctrl+u meta+u': 'underline',
			'Ctrl+z': 'undo',
			'Ctrl+y meta+y meta+shift+z': 'redo',
			'Ctrl+l meta+l': 'justifyleft',
			'Ctrl+r meta+r': 'justifyright',
			'Ctrl+e meta+e': 'justifycenter',
			'Ctrl+j meta+j': 'justifyfull',
			'Shift+tab': 'outdent',
			'tab': 'indent'
		},
		toolbarSelector: '[data-role=editor-toolbar]',
		commandRole: 'edit',
		activeToolbarClass: 'btn-info',
		selectionMarker: 'edit-focus-marker',
		selectionColor: 'darkgrey',
		dragAndDropImages: true,
		keypressTimeout: 200,
		fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
	};
}(window.jQuery));
<commit_msg>fixed misspelling in script masthead<commit_after>/* @fileoverview
 * Provides full Bootstrap based, multi-instance WYSIWYG editor.
 *
 * "Name"    = 'bootstrap-wysiwyg'
 * "Author"  = 'Various, see LICENSE'
 * "Version" = '1.0.4'
 * "About"   = 'A tiny Bootstrap and jQuery based WYSIWYG rich text editor based on the browser function execCommand.'
 */
(function ($) {
	'use strict';
	/** underscoreThrottle()
	 * 	From underscore http://underscorejs.org/docs/underscore.html
	 */
	var underscoreThrottle = function(func, wait) {
		var context, args, timeout, result;
		var previous = 0;
		var later = function() {
			previous = new Date();
			timeout = null;
			result = func.apply(context, args);
		};
		return function() {
			var now = new Date();
			var remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0) {
				clearTimeout(timeout);
				timeout = null;
				previous = now;
				result = func.apply(context, args);
			} else if (!timeout) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	};
	var readFileIntoDataUrl = function (fileInfo) {
		var loader = $.Deferred(),
			fReader = new FileReader();
		fReader.onload = function (e) {
			loader.resolve(e.target.result);
		};
		fReader.onerror = loader.reject;
		fReader.onprogress = loader.notify;
		fReader.readAsDataURL(fileInfo);
		return loader.promise();
	};
	$.fn.cleanHtml = function (o) {
		if ( $(this).data("wysiwyg-html-mode") === true ) {
			$(this).html($(this).text());
        	$(this).attr('contenteditable',true);
        	$(this).data('wysiwyg-html-mode',false);
		}

		// Strip the images with src="data:image/.." out;
		if ( o === true && $(this).parent().is("form") ) {
			var gGal = $(this).html;
			if ( $(gGal).has( "img" ).length ) {
				var gImages = $( "img", $(gGal));
				var gResults = [];
				var gEditor = $(this).parent();
				$.each(gImages, function(i,v) {
					if ( $(v).attr('src').match(/^data:image\/.*$/) ) {
						gResults.push(gImages[i]);
						$(gEditor).prepend("<input value='"+$(v).attr('src')+"' type='hidden' name='postedimage/"+i+"' />");
						$(v).attr('src', 'postedimage/'+i);
				}});
			}
		}
		var html = $(this).html();
		return html && html.replace(/(
|\s|

<\/div>| )*$/, '');
	};
	$.fn.wysiwyg = function (userOptions) {
		var editor = this,
			wrapper = $(editor).parent(),
			selectedRange,
			options,
			toolbarBtnSelector,
			updateToolbar = function () {
				if (options.activeToolbarClass) {
					$(options.toolbarSelector,wrapper).find(toolbarBtnSelector).each(underscoreThrottle(function () {
						var commandArr = $(this).data(options.commandRole).split(' '),
							command = commandArr[0];

						// If the command has an argument and its value matches this button. == used for string/number comparison
						if (commandArr.length > 1 && document.queryCommandEnabled(command) && document.queryCommandValue(command) === commandArr[1]) {
							$(this).addClass(options.activeToolbarClass);
						// Else if the command has no arguments and it is active
						} else if (commandArr.length === 1 && document.queryCommandEnabled(command) && document.queryCommandState(command)) {
							$(this).addClass(options.activeToolbarClass);
						// Else the command is not active
						} else {
							$(this).removeClass(options.activeToolbarClass);
						}
					}, options.keypressTimeout));
				}
			},
			execCommand = function (commandWithArgs, valueArg) {
				var commandArr = commandWithArgs.split(' '),
					command = commandArr.shift(),
					args = commandArr.join(' ') + (valueArg || '');

				var parts = commandWithArgs.split('-');

				if ( parts.length === 1 ) {
					document.execCommand(command, 0, args);
				}
				else if ( parts[0] === 'format' && parts.length === 2 ) {
					document.execCommand('formatBlock', false, parts[1] );
				}

				editor.trigger('change');
				updateToolbar();
			},
			bindHotkeys = function (hotKeys) {
				$.each(hotKeys, function (hotkey, command) {
					editor.keydown(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
							execCommand(command);
						}
					}).keyup(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
						}
					});
				});

				editor.keyup(function(){ editor.trigger('change'); });
			},
			getCurrentRange = function () {
                var sel, range;
                if (window.getSelection) {
                    sel = window.getSelection();
                    if (sel.getRangeAt && sel.rangeCount) {
                        range = sel.getRangeAt(0);
				    }
                } else if (document.selection) {
                    range = document.selection.createRange();
                } return range;
			},
			saveSelection = function () {
				selectedRange = getCurrentRange();
			},
			restoreSelection = function () {
				var selection;
                if (window.getSelection || document.createRange) {
                    selection = window.getSelection();
                    if (selectedRange) {
                        try {
                            selection.removeAllRanges();
                        } catch (ex) {
                            document.body.createTextRange().select();
                            document.selection.empty();
                        }
                        selection.addRange(selectedRange);
                    }
                }
                else if (document.selection && selectedRange) {
                	selectedRange.select();
                }
			},

			// Adding Toggle HTML based on the work by @jd0000, but cleaned up a little to work in this context.
            toggleHtmlEdit = function() {
				if ( $(editor).data("wysiwyg-html-mode") !== true ) {
					var oContent = $(editor).html();
					var editorPre = $( "
" );
                	$(editorPre).append( document.createTextNode( oContent ) );
                	$(editorPre).attr('contenteditable',true);
                	$(editor).html(' ');
                	$(editor).append($(editorPre));
                    $(editor).attr('contenteditable', false);
                    $(editor).data("wysiwyg-html-mode", true);
                    $(editorPre).focus();
                }
                else {
                	$(editor).html($(editor).text());
                	$(editor).attr('contenteditable',true);
                	$(editor).data('wysiwyg-html-mode',false);
                    $(editor).focus();
                }
            },

			insertFiles = function (files) {
				editor.focus();
				$.each(files, function (idx, fileInfo) {
					if (/^image\//.test(fileInfo.type)) {
						$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
							execCommand('insertimage', dataUrl);
							editor.trigger('image-inserted');
						}).fail(function (e) {
							options.fileUploadError("file-reader", e);
						});
					} else {
						options.fileUploadError("unsupported-file-type", fileInfo.type);
					}
				});
			},
			markSelection = function (input, color) {
				restoreSelection();
				if (document.queryCommandSupported('hiliteColor')) {
					document.execCommand('hiliteColor', 0, color || 'transparent');
				}
				saveSelection();
				input.data(options.selectionMarker, color);
			},
			bindToolbar = function (toolbar, options) {
				toolbar.find(toolbarBtnSelector, wrapper).click(function () {
					restoreSelection();
					editor.focus();

                    if ($(this).data(options.commandRole) === 'html') {
                        toggleHtmlEdit();
                    }
                    else {
                    	execCommand($(this).data(options.commandRole));
                    }
					saveSelection();
				});
				toolbar.find('[data-toggle=dropdown]').click(restoreSelection);

				toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
					var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
					this.value = '';
					restoreSelection();
					if (newValue) {
						editor.focus();
						execCommand($(this).data(options.commandRole), newValue);
					}
					saveSelection();
				}).on('focus', function () {
					var input = $(this);
					if (!input.data(options.selectionMarker)) {
						markSelection(input, options.selectionColor);
						input.focus();
					}
				}).on('blur', function () {
					var input = $(this);
					if (input.data(options.selectionMarker)) {
						markSelection(input, false);
					}
				});
				toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
					restoreSelection();
					if (this.type === 'file' && this.files && this.files.length > 0) {
						insertFiles(this.files);
					}
					saveSelection();
					this.value = '';
				});
			},
			initFileDrops = function () {
				editor.on('dragenter dragover', false)
					.on('drop', function (e) {
						var dataTransfer = e.originalEvent.dataTransfer;
						e.stopPropagation();
						e.preventDefault();
						if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
							insertFiles(dataTransfer.files);
						}
					});
			};
		options = $.extend(true, {}, $.fn.wysiwyg.defaults, userOptions);
		toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
		bindHotkeys(options.hotKeys);

		// Support placeholder attribute on the DIV
		if ($(this).attr('placeholder') !== '') {
			$(this).addClass('placeholderText');
			$(this).html($(this).attr('placeholder'));
			$(this).bind('focus',function() {
				if ( $(this).attr('placeholder') !== '' && $(this).text() === $(this).attr('placeholder') ) {
					$(this).removeClass('placeholderText');
					$(this).html('');
				}
			});
			$(this).bind('blur',function() {
				if ( $(this).attr('placeholder') !== '' && $(this).text() === '' ) {
					$(this).addClass('placeholderText');
					$(this).html($(this).attr('placeholder'));
				}
			});
		}

		if (options.dragAndDropImages) {
			initFileDrops();
		}
		bindToolbar($(options.toolbarSelector), options);
		editor.attr('contenteditable', true)
			.on('mouseup keyup mouseout', function () {
				saveSelection();
				updateToolbar();
			});
		$(window).bind('touchend', function (e) {
			var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
				currentRange = getCurrentRange(),
				clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
			if (!clear || isInside) {
				saveSelection();
				updateToolbar();
			}
		});
		return this;
	};
	$.fn.wysiwyg.defaults = {
		hotKeys: {
			'Ctrl+b meta+b': 'bold',
			'Ctrl+i meta+i': 'italic',
			'Ctrl+u meta+u': 'underline',
			'Ctrl+z': 'undo',
			'Ctrl+y meta+y meta+shift+z': 'redo',
			'Ctrl+l meta+l': 'justifyleft',
			'Ctrl+r meta+r': 'justifyright',
			'Ctrl+e meta+e': 'justifycenter',
			'Ctrl+j meta+j': 'justifyfull',
			'Shift+tab': 'outdent',
			'tab': 'indent'
		},
		toolbarSelector: '[data-role=editor-toolbar]',
		commandRole: 'edit',
		activeToolbarClass: 'btn-info',
		selectionMarker: 'edit-focus-marker',
		selectionColor: 'darkgrey',
		dragAndDropImages: true,
		keypressTimeout: 200,
		fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
	};
}(window.jQuery));
<|endoftext|>


<commit_before>/***
 * Questionnaire view script.
 * Define onClick handler for question buttons.
 * DOM structure must be follow:
 * .js-section
 *      .js-section-title
 *          .js-section-badge
 *              .js-section-badge-value
 *              .js-section-badge-total
 *      .js-section-body
 *          .question
 *              .btn-group
 */
define([
    'require',
    'jquery'
], function(require, $) {
    'use strict';

    function changeBadge($question) {
        $question.attr('data-filled', '1');
        var $badge = $question.parents('.js-section').find('.js-section-badge');
        var $badgeVal = $badge.find('.js-section-badge-value');
        var $badgeTotal = $badge.find('.js-section-badge-total');
        $badgeVal.text($question.parents('.js-section-body').find('.question[data-filled="1"]').length);
        if ($badgeVal.text() === $badgeTotal.text()) {
            $badge.removeClass('text-danger').addClass('text-success');
        }
    };

    // document.onload
    $(function () {
        $.each($('.js-question'), function () {
            $(this).attr('data-filled', '0');
        });
        $('.js-question-btns .btn').click(function (evt) {
            evt.preventDefault();
            var $target = $(evt.target);
            var $grp = $target.parent();
            var $old = $grp.find('button.active');
            if ($old.length > 0) {
                $old.removeClass('active');
                $old.find('i').remove();
            }
            $target.addClass('active');
            $target.html('' + $target.html());
            $target.blur();

            var $question = $grp.parents('.js-question');
            changeBadge($question);
        });
        $('input.js-question').change(function (evt) {
            var $target = $(evt.target);
            if ($target.val() !== '') {
                $target.attr('data-filled', '1');
            }
            changeBadge($target);
        });
    });
});<commit_msg>Fix questionnaire.view.js description<commit_after>/***
 * Questionnaire view script.
 * Define onClick handler for question buttons.
 * DOM structure must be follow:
 * .js-section
 *      .js-section-title
 *          .js-section-badge
 *              .js-section-badge-value
 *              .js-section-badge-total
 *      .js-section-body
 *          .js-question
 *              .js-question-btns
 */
define([
    'require',
    'jquery'
], function(require, $) {
    'use strict';

    function changeBadge($question) {
        $question.attr('data-filled', '1');
        var $badge = $question.parents('.js-section').find('.js-section-badge');
        var $badgeVal = $badge.find('.js-section-badge-value');
        var $badgeTotal = $badge.find('.js-section-badge-total');
        $badgeVal.text($question.parents('.js-section-body').find('.question[data-filled="1"]').length);
        if ($badgeVal.text() === $badgeTotal.text()) {
            $badge.removeClass('text-danger').addClass('text-success');
        }
    };

    // document.onload
    $(function () {
        $.each($('.js-question'), function () {
            $(this).attr('data-filled', '0');
        });
        $('.js-question-btns .btn').click(function (evt) {
            evt.preventDefault();
            var $target = $(evt.target);
            var $grp = $target.parent();
            var $old = $grp.find('button.active');
            if ($old.length > 0) {
                $old.removeClass('active');
                $old.find('i').remove();
            }
            $target.addClass('active');
            $target.html('' + $target.html());
            $target.blur();

            var $question = $grp.parents('.js-question');
            changeBadge($question);
        });
        $('input.js-question').change(function (evt) {
            var $target = $(evt.target);
            if ($target.val() !== '') {
                $target.attr('data-filled', '1');
            }
            changeBadge($target);
        });
    });
});<|endoftext|>