<commit_before>import React from 'react';
import './EditorForm.scss';
import update from 'immutability-helper';
import Select from 'react-select';
import includes from 'lodash/includes';
import capitalize from 'lodash/capitalize';
import sortBy from 'lodash/sortBy';

import { validateConditionalRules } from '../../../utils/validators';

export class EditorForm extends React.Component {
  constructor (props) {
    super(props);
    this.generateAttributeElements = this.generateAttributeElements.bind(this);
    this.getActiveValue = this.getActiveValue.bind(this);
    this.getCheckedState = this.getCheckedState.bind(this);
    this.closeEditorForm = this.closeEditorForm.bind(this);
    this.state = {
      newAttributes: this.initializeAttributes(this.props.attributeTypes)
    };
  }

  initializeAttributes (attributeTypes) {
    const { attributes } = this.props;
    let initialState = {};
    for (const key in attributeTypes) {
      if (attributeTypes.hasOwnProperty(key)) {
        initialState = Object.assign({}, initialState, {
          [key]: {
            value: attributes[key] ? attributes[key] : null,
            checked: true
          }
        });
      }
    }
    return initialState;
  }

  onChange (value, key, field) {
    this.setState(update(this.state, {
      newAttributes: {
        [key]: {
          [field]: {
            $set: value
          }
        }
      }
    }));
  }

  getActiveValue (key) {
    if (this.state.newAttributes[key].value) {
      return this.state.newAttributes[key].value;
    }
  }

  getCheckedState (key) {
    if (this.props.editorConfig.action === 'edit' && !this.state.newAttributes[key].value) {
      return false;
    } else {
      return this.state.newAttributes[key].checked;
    }
  }

  getAttributesToShow (attributeTypes, attributes) {
    const { newAttributes } = this.state;
    const attributesToShow = [];
    const getAttributeKeys = (attributes) => {
      const attributeKeys = [];
      for (const key in attributes) {
        attributeKeys.push(key);
      }
      return attributeKeys;
    };

    for (const attributeType in attributeTypes) {
      if (includes(attributeTypes[attributeType].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[attributeType].required ||
          includes(getAttributeKeys(attributes), attributeType)) {
          attributesToShow.push(attributeType);
        }
        if (attributeTypes[attributeType].requiredIf.length) {
          if (validateConditionalRules(attributeType, attributeTypes, newAttributes)) {
            attributesToShow.push(attributeType);
          }
        }
      }
    }

    return this.prepareAttributes(attributesToShow);
  }

  getComplementAttributes (attributeTypes, attributesToShow) {
    const { newAttributes } = this.state;
    const complementAttributes = [];

    for (const key in attributeTypes) {
      if (includes(attributeTypes[key].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[key].requiredIf.length) {
          if (validateConditionalRules(key, attributeTypes, newAttributes)) {
            complementAttributes.push(key);
          }
        } else {
          complementAttributes.push(key);
        }
      }
    }

    return this.prepareAttributes(complementAttributes);
  }

  prepareAttributes (attributesToShow) {
    attributesToShow.splice(attributesToShow.indexOf(`${capitalize(this.props.editorConfig.type)}Type`), 1);
    attributesToShow.splice(attributesToShow.indexOf('TypeSpecifier'), 1);

    const sortedAttributes = sortBy(attributesToShow, (attribute) => (
      this.props.attributeTypes[attribute].index
    ));

    return sortedAttributes;
  }

  generateOptions (array) {
    return array.map(item => ({
      value: item.value,
      label: item.value
    }));
  }

  generateAttributeElements (attributeTypes) {
    let attributesToShow = this.getAttributesToShow(attributeTypes, this.props.attributes);
    if (this.props.editorConfig.action === 'complement') {
      attributesToShow = this.getComplementAttributes(attributeTypes, attributesToShow);
    }
    const attributeElements = [];

    if (attributesToShow.length) {
      for (const key of attributesToShow) {
        if (attributeTypes.hasOwnProperty(key)) {
          if (attributeTypes[key].values.length) {
            const options = this.generateOptions(attributeTypes[key].values);

            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}/>

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                <Select
                  placeholder='Valitse...'
                  autoBlur={true}
                  autofocus={false}
                  className='form-control editor-form__select'
                  clearable={true}
                  disabled={!this.state.newAttributes[key].checked}
                  onChange={(option) => this.onChange(option ? option.value : null, key, 'value')}
                  openOnFocus={true}
                  options={options}
                  value={this.getActiveValue(key)}
                />

            );
          } else if (attributeTypes[key].values.length === 0) {
            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}
                />

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                <input
                  className='form-control edit-attribute__input'
                  value={this.getActiveValue(key)}
                  placeholder={attributeTypes[key].name}
                  onChange={(e) => this.onChange(e.target.value, key, 'value')}
                  disabled={!this.state.newAttributes[key].checked}
                />

            );
          }
        }
      }
    } else {
      attributeElements.push(

Ei täydennettäviä metatietoja

      );
    }

    return attributeElements;
  }

  generateDropdown (elementTypes) {
    const type = [`${capitalize(this.props.editorConfig.type)}Type`];
    const optionsRaw = Object.keys(elementTypes).map(type => {
      return { value: elementTypes[type].name, label: elementTypes[type].name };
    });
    const options = this.generateOptions(optionsRaw);

    return (
      <Select
        placeholder='Valitse...'
        autoBlur={true}
        autofocus={false}
        className='form-control col-xs-6'
        clearable={true}
        onChange={(option) => this.onChange(option ? option.value : null, type, 'value')}
        openOnFocus={true}
        options={options}
        value={this.state.newAttributes[type] ? this.state.newAttributes[type].value : '---'}
      />
    );
  }

  filterAttributes (attributes) {
    const filteredAttributes = Object.assign({}, attributes);
    for (const key in filteredAttributes) {
      if (!filteredAttributes[key].value) {
        delete filteredAttributes[key];
      }
    }
    return filteredAttributes;
  }

  editMetaData (e) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.editMetaDataWithForm(this.filterAttributes(newAttributes));
    this.props.displayMessage({
      title: 'Metatiedot',
      body: 'Tietojen muokkaus onnistui!'
    });
  }

  addRecord (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;

    this.props.elementConfig.createRecord(
      this.filterAttributes(newAttributes),
      targetId
    );
    this.props.displayMessage({
      title: 'Asiakirja',
      body: 'Asiakirjan lisäys onnistui!'
    });
  }

  editElement (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.elementConfig.editWithForm(
      this.filterAttributes(newAttributes),
      targetId
    );
  }

  resolveLabel () {
    const { type, action } = this.props.editorConfig;

    switch (type) {
      case 'function':
        return 'Metatiedot';
      case 'phase':
        if (action === 'add') {
          return 'Uusi käsittelyvaihe';
        }
        if (action === 'edit') {
          return 'Muokkaa käsittelyvaihetta';
        }
        if (action === 'complement') {
          return 'Täydennä käsittelyvaihetta';
        }
        break;
      case 'action':
        if (action === 'add') {
          return 'Uusi toimenpide';
        }
        if (action === 'edit') {
          return 'Muokkaa toimenpidettä';
        }
        if (action === 'complement') {
          return 'Täydennä toimenpidettä';
        }
        break;
      case 'record':
        if (action === 'add') {
          return 'Uusi asiakirja';
        }
        if (action === 'edit') {
          return 'Muokkaa asiakirjaa';
        }
        if (action === 'complement') {
          return 'Täydennä asiakirjaa';
        }
        break;
    }
  }

  resolveOnSubmit (e, targetId) {
    const { action, type } = this.props.editorConfig;

    switch (type) {
      case 'function':
        if (action === 'edit' || action === 'complement') {
          this.editMetaData(e);
        }
        break;
      case 'phase':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Käsittelyvaihe',
            body: 'Käsittelyvaiheen muokkaus onnistui!'
          });
        }
        break;
      case 'action':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Toimenpide',
            body: 'Toimenpiteen muokkaus onnistui!'
          });
        }
        break;
      case 'record':
        if (action === 'add') {
          this.addRecord(e, targetId);
        }
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Asiakirja',
            body: 'Asiakirjan muokkaus onnistui!'
          });
        }
    }
  }

  closeEditorForm (e) {
    e.preventDefault();
    this.props.closeEditorForm();
  }

  renderDescriptions () {
    const typeDropdown = this.generateDropdown(this.props.elementConfig.elementTypes);

    return (

          Tarkenne

          <input
            className='col-xs-6 form-control edit-attribute__input'
            placeholder='Tarkenne'
            value={this.state.newAttributes.TypeSpecifier.value}
            onChange={(e) => this.onChange(e.target.value, 'TypeSpecifier', 'value')}/>

          Tyyppi

          { typeDropdown }

    );
  }

  render () {
    const { attributeTypes, targetId } = this.props;
    const attributeElements = this.generateAttributeElements(attributeTypes);

    return (

{this.resolveLabel()}

        <form onSubmit={(e) => this.resolveOnSubmit(e, targetId)}
              className='editor-form'>
          { this.props.editorConfig.type !== 'function' ? this.renderDescriptions() : null }
          { attributeElements }

            <button
              className='btn btn-primary pull-right editor-form__submit'
              type='submit'>
              Valmis
            </button>
            <button
              className='btn btn-danger pull-right editor-form__cancel'
              onClick={(e) => this.closeEditorForm(e)}>
              Peruuta
            </button>

        </form>

    );
  }
}

EditorForm.propTypes = {
  attributeTypes: React.PropTypes.object.isRequired,
  attributes: React.PropTypes.object.isRequired,
  closeEditorForm: React.PropTypes.func.isRequired,
  displayMessage: React.PropTypes.func.isRequired,
  editMetaDataWithForm: React.PropTypes.func,
  editorConfig: React.PropTypes.shape({
    type: React.PropTypes.string.isRequired,
    action: React.PropTypes.string.isRequired
  }),
  elementConfig: React.PropTypes.shape({
    editWithForm: React.PropTypes.func,
    elementTypes: React.PropTypes.object.isRequired,
    createRecord: React.PropTypes.func // only records created with editorform
  }),
  targetId: React.PropTypes.string
};

export default EditorForm;
<commit_msg>Display text area with "AdditionalInformation"<commit_after>import React from 'react';
import './EditorForm.scss';
import update from 'immutability-helper';
import Select from 'react-select';
import includes from 'lodash/includes';
import capitalize from 'lodash/capitalize';
import sortBy from 'lodash/sortBy';

import { validateConditionalRules } from '../../../utils/validators';

export class EditorForm extends React.Component {
  constructor (props) {
    super(props);
    this.generateAttributeElements = this.generateAttributeElements.bind(this);
    this.getActiveValue = this.getActiveValue.bind(this);
    this.getCheckedState = this.getCheckedState.bind(this);
    this.closeEditorForm = this.closeEditorForm.bind(this);
    this.state = {
      newAttributes: this.initializeAttributes(this.props.attributeTypes)
    };
  }

  initializeAttributes (attributeTypes) {
    const { attributes } = this.props;
    let initialState = {};
    for (const key in attributeTypes) {
      if (attributeTypes.hasOwnProperty(key)) {
        initialState = Object.assign({}, initialState, {
          [key]: {
            value: attributes[key] ? attributes[key] : null,
            checked: true
          }
        });
      }
    }
    return initialState;
  }

  onChange (value, key, field) {
    this.setState(update(this.state, {
      newAttributes: {
        [key]: {
          [field]: {
            $set: value
          }
        }
      }
    }));
  }

  getActiveValue (key) {
    if (this.state.newAttributes[key].value) {
      return this.state.newAttributes[key].value;
    }
  }

  getCheckedState (key) {
    if (this.props.editorConfig.action === 'edit' && !this.state.newAttributes[key].value) {
      return false;
    } else {
      return this.state.newAttributes[key].checked;
    }
  }

  getAttributesToShow (attributeTypes, attributes) {
    const { newAttributes } = this.state;
    const attributesToShow = [];
    const getAttributeKeys = (attributes) => {
      const attributeKeys = [];
      for (const key in attributes) {
        attributeKeys.push(key);
      }
      return attributeKeys;
    };

    for (const attributeType in attributeTypes) {
      if (includes(attributeTypes[attributeType].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[attributeType].required ||
          includes(getAttributeKeys(attributes), attributeType)) {
          attributesToShow.push(attributeType);
        }
        if (attributeTypes[attributeType].requiredIf.length) {
          if (validateConditionalRules(attributeType, attributeTypes, newAttributes)) {
            attributesToShow.push(attributeType);
          }
        }
      }
    }

    return this.prepareAttributes(attributesToShow);
  }

  getComplementAttributes (attributeTypes, attributesToShow) {
    const { newAttributes } = this.state;
    const complementAttributes = [];

    for (const key in attributeTypes) {
      if (includes(attributeTypes[key].allowedIn, this.props.editorConfig.type)) {
        if (attributeTypes[key].requiredIf.length) {
          if (validateConditionalRules(key, attributeTypes, newAttributes)) {
            complementAttributes.push(key);
          }
        } else {
          complementAttributes.push(key);
        }
      }
    }

    return this.prepareAttributes(complementAttributes);
  }

  prepareAttributes (attributesToShow) {
    attributesToShow.splice(attributesToShow.indexOf(`${capitalize(this.props.editorConfig.type)}Type`), 1);
    attributesToShow.splice(attributesToShow.indexOf('TypeSpecifier'), 1);

    const sortedAttributes = sortBy(attributesToShow, (attribute) => (
      this.props.attributeTypes[attribute].index
    ));

    return sortedAttributes;
  }

  generateOptions (array) {
    return array.map(item => ({
      value: item.value,
      label: item.value
    }));
  }

  generateAttributeElements (attributeTypes) {
    let attributesToShow = this.getAttributesToShow(attributeTypes, this.props.attributes);
    if (this.props.editorConfig.action === 'complement') {
      attributesToShow = this.getComplementAttributes(attributeTypes, attributesToShow);
    }
    const attributeElements = [];

    if (attributesToShow.length) {
      for (const key of attributesToShow) {
        if (attributeTypes.hasOwnProperty(key)) {
          if (attributeTypes[key].values.length) {
            const options = this.generateOptions(attributeTypes[key].values);

            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}/>

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                <Select
                  placeholder='Valitse...'
                  autoBlur={true}
                  autofocus={false}
                  className='form-control editor-form__select'
                  clearable={true}
                  disabled={!this.state.newAttributes[key].checked}
                  onChange={(option) => this.onChange(option ? option.value : null, key, 'value')}
                  openOnFocus={true}
                  options={options}
                  value={this.getActiveValue(key)}
                />

            );
          } else if (attributeTypes[key].values.length === 0) {
            attributeElements.push(

                <input
                  type='checkbox'
                  checked={this.getCheckedState(key)}
                  value={this.state.newAttributes[key].checked}
                  onChange={(e) => this.onChange(!this.state.newAttributes[key].checked, key, 'checked')}
                />

                  {attributeTypes[key].name}
                  { attributeTypes[key].required &&

                  }

                { key === 'AdditionalInformation'
                  ? <textarea
                      className='form-control edit-record__input'
                      value={this.getActiveValue(key)}
                      placeholder={attributeTypes[key].name}
                      onChange={(e) => this.onChange(e.target.value, key, 'value')}
                      disabled={!this.state.newAttributes[key].checked}
                    />
                  : <input
                      className='form-control edit-record__input'
                      value={this.getActiveValue(key)}
                      placeholder={attributeTypes[key].name}
                      onChange={(e) => this.onChange(e.target.value, key, 'value')}
                      disabled={!this.state.newAttributes[key].checked}
                    />
                }

            );
          }
        }
      }
    } else {
      attributeElements.push(

Ei täydennettäviä metatietoja

      );
    }

    return attributeElements;
  }

  generateDropdown (elementTypes) {
    const type = [`${capitalize(this.props.editorConfig.type)}Type`];
    const optionsRaw = Object.keys(elementTypes).map(type => {
      return { value: elementTypes[type].name, label: elementTypes[type].name };
    });
    const options = this.generateOptions(optionsRaw);

    return (
      <Select
        placeholder='Valitse...'
        autoBlur={true}
        autofocus={false}
        className='form-control col-xs-6'
        clearable={true}
        onChange={(option) => this.onChange(option ? option.value : null, type, 'value')}
        openOnFocus={true}
        options={options}
        value={this.state.newAttributes[type] ? this.state.newAttributes[type].value : '---'}
      />
    );
  }

  filterAttributes (attributes) {
    const filteredAttributes = Object.assign({}, attributes);
    for (const key in filteredAttributes) {
      if (!filteredAttributes[key].value) {
        delete filteredAttributes[key];
      }
    }
    return filteredAttributes;
  }

  editMetaData (e) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.editMetaDataWithForm(this.filterAttributes(newAttributes));
    this.props.displayMessage({
      title: 'Metatiedot',
      body: 'Tietojen muokkaus onnistui!'
    });
  }

  addRecord (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;

    this.props.elementConfig.createRecord(
      this.filterAttributes(newAttributes),
      targetId
    );
    this.props.displayMessage({
      title: 'Asiakirja',
      body: 'Asiakirjan lisäys onnistui!'
    });
  }

  editElement (e, targetId) {
    e.preventDefault();
    const { newAttributes } = this.state;
    this.props.elementConfig.editWithForm(
      this.filterAttributes(newAttributes),
      targetId
    );
  }

  resolveLabel () {
    const { type, action } = this.props.editorConfig;

    switch (type) {
      case 'function':
        return 'Metatiedot';
      case 'phase':
        if (action === 'add') {
          return 'Uusi käsittelyvaihe';
        }
        if (action === 'edit') {
          return 'Muokkaa käsittelyvaihetta';
        }
        if (action === 'complement') {
          return 'Täydennä käsittelyvaihetta';
        }
        break;
      case 'action':
        if (action === 'add') {
          return 'Uusi toimenpide';
        }
        if (action === 'edit') {
          return 'Muokkaa toimenpidettä';
        }
        if (action === 'complement') {
          return 'Täydennä toimenpidettä';
        }
        break;
      case 'record':
        if (action === 'add') {
          return 'Uusi asiakirja';
        }
        if (action === 'edit') {
          return 'Muokkaa asiakirjaa';
        }
        if (action === 'complement') {
          return 'Täydennä asiakirjaa';
        }
        break;
    }
  }

  resolveOnSubmit (e, targetId) {
    const { action, type } = this.props.editorConfig;

    switch (type) {
      case 'function':
        if (action === 'edit' || action === 'complement') {
          this.editMetaData(e);
        }
        break;
      case 'phase':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Käsittelyvaihe',
            body: 'Käsittelyvaiheen muokkaus onnistui!'
          });
        }
        break;
      case 'action':
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Toimenpide',
            body: 'Toimenpiteen muokkaus onnistui!'
          });
        }
        break;
      case 'record':
        if (action === 'add') {
          this.addRecord(e, targetId);
        }
        if (action === 'edit' || action === 'complement') {
          this.editElement(e, targetId);
          this.props.displayMessage({
            title: 'Asiakirja',
            body: 'Asiakirjan muokkaus onnistui!'
          });
        }
    }
  }

  closeEditorForm (e) {
    e.preventDefault();
    this.props.closeEditorForm();
  }

  renderDescriptions () {
    const typeDropdown = this.generateDropdown(this.props.elementConfig.elementTypes);

    return (

          Tarkenne

          <input
            className='col-xs-6 form-control edit-attribute__input'
            placeholder='Tarkenne'
            value={this.state.newAttributes.TypeSpecifier.value}
            onChange={(e) => this.onChange(e.target.value, 'TypeSpecifier', 'value')}/>

          Tyyppi

          { typeDropdown }

    );
  }

  render () {
    const { attributeTypes, targetId } = this.props;
    const attributeElements = this.generateAttributeElements(attributeTypes);

    return (

{this.resolveLabel()}

        <form onSubmit={(e) => this.resolveOnSubmit(e, targetId)}
              className='editor-form'>
          { this.props.editorConfig.type !== 'function' ? this.renderDescriptions() : null }
          { attributeElements }

            <button
              className='btn btn-primary pull-right editor-form__submit'
              type='submit'>
              Valmis
            </button>
            <button
              className='btn btn-danger pull-right editor-form__cancel'
              onClick={(e) => this.closeEditorForm(e)}>
              Peruuta
            </button>

        </form>

    );
  }
}

EditorForm.propTypes = {
  attributeTypes: React.PropTypes.object.isRequired,
  attributes: React.PropTypes.object.isRequired,
  closeEditorForm: React.PropTypes.func.isRequired,
  displayMessage: React.PropTypes.func.isRequired,
  editMetaDataWithForm: React.PropTypes.func,
  editorConfig: React.PropTypes.shape({
    type: React.PropTypes.string.isRequired,
    action: React.PropTypes.string.isRequired
  }),
  elementConfig: React.PropTypes.shape({
    editWithForm: React.PropTypes.func,
    elementTypes: React.PropTypes.object.isRequired,
    createRecord: React.PropTypes.func // only records created with editorform
  }),
  targetId: React.PropTypes.string
};

export default EditorForm;
<|endoftext|>


<commit_before>
"use strict";

let _messages = [];
let $timeout = undefined;

export default class Report {

    /**
     * Constructor. Injects $timeout service.
     *
     * @param timeout Angular $timeout service.
     * @return void
     */
    constructor(timeout) {
        $timeout = timeout;
    }

    /**
     * Adds a message to the tray. The message disappears when its associated
     * `promise` resolves. Arguments can be passed in random order.
     *
     * @param string body The message body, can contain HTML with directives
     *  etc. (it is compiled for you).
     * @param mixed promise Either `undefined` (defaults to 3 seconds), a number
     *  of milliseconds after which to resolve, or a full-fledged Angular
     *  promise.
     * @param string type Bootstrap message type (`info`, `warning` etc.).
     * @param object scope Scope to use on the message. Is available inside your
     *  body as `msg.data`.
     * @return promise The promise used to remove the message is returned, e.g.
     *  for additional (error) handling.
     */
    add(...args) {
        let body = '';
        let promise = $timeout(() => {}, 3000);
        let type = 'default';
        let scope = {};
        console.log(args);
        args.map(arg => {
            if (arg === undefined) {
                return;
            } else if (typeof arg == 'number') {
                promise = $timeout(() => {}, arg);
            } else if (typeof arg == 'string') {
                if (['default', 'primary', 'success', 'info', 'warning', 'danger'].indexOf(arg) != -1) {
                    type = arg;
                } else {
                    body = arg;
                }
            } else if (typeof arg == 'object') {
                if (arg.then && typeof arg.then == 'function') {
                    promise = arg;
                } else {
                    scope = arg;
                }
            } else {
                let type = typeof arg;
                throw `Argument ${arg} of type ${type} is not valid for messages.`;
            }
        });
        let msg = {body, type, scope};
        let i = _messages.length;
        _messages.push(msg);
        let handler = () => _messages.splice(i, 1);{
        return promise.then(handler, handler);
    }

    /**
     * Getter for your messages. These are by default available on the root
     * controller (`monad`) as `monad.messages`.
     *
     * @return array Array of current messages.
     */
    get messages() {
        return _messages;
    }

    /**
     * Convenience method to add a `default` message.
     *
     * @see Report.add
     * @return promise
     */
    ['default'](...args) {
        return this.add(...args.concat(['default']));
    }

    /**
     * Convenience method to add a `primary` message.
     *
     * @see Report.add
     * @return promise
     */
    primary(...args) {
        return this.add(...args.concat(['primary']));
    }

    /**
     * Convenience method to add a `success` message.
     *
     * @see Report.add
     * @return promise
     */
    success(...args) {
        return this.add(...args.concat(['success']));
    }

    /**
     * Convenience method to add an `info` message.
     *
     * @see Report.add
     * @return promise
     */
    info(...args) {
        return this.add(...args.concat(['info']));
    }

    /**
     * Convenience method to add a `warning` message.
     *
     * @see Report.add
     * @return promise
     */
    warning(...args) {
        return this.add(...args.concat(['warning']));
    }

    /**
     * Convenience method to add a `danger` message.
     *
     * @see Report.add
     * @return promise
     */
    danger(...args) {
        return this.add(...args.concat(['danger']));
    }

}

Report.$inject = ['$timeout'];

<commit_msg>fix typo<commit_after>
"use strict";

let _messages = [];
let $timeout = undefined;

export default class Report {

    /**
     * Constructor. Injects $timeout service.
     *
     * @param timeout Angular $timeout service.
     * @return void
     */
    constructor(timeout) {
        $timeout = timeout;
    }

    /**
     * Adds a message to the tray. The message disappears when its associated
     * `promise` resolves. Arguments can be passed in random order.
     *
     * @param string body The message body, can contain HTML with directives
     *  etc. (it is compiled for you).
     * @param mixed promise Either `undefined` (defaults to 3 seconds), a number
     *  of milliseconds after which to resolve, or a full-fledged Angular
     *  promise.
     * @param string type Bootstrap message type (`info`, `warning` etc.).
     * @param object scope Scope to use on the message. Is available inside your
     *  body as `msg.data`.
     * @return promise The promise used to remove the message is returned, e.g.
     *  for additional (error) handling.
     */
    add(...args) {
        let body = '';
        let promise = $timeout(() => {}, 3000);
        let type = 'default';
        let scope = {};
        console.log(args);
        args.map(arg => {
            if (arg === undefined) {
                return;
            } else if (typeof arg == 'number') {
                promise = $timeout(() => {}, arg);
            } else if (typeof arg == 'string') {
                if (['default', 'primary', 'success', 'info', 'warning', 'danger'].indexOf(arg) != -1) {
                    type = arg;
                } else {
                    body = arg;
                }
            } else if (typeof arg == 'object') {
                if (arg.then && typeof arg.then == 'function') {
                    promise = arg;
                } else {
                    scope = arg;
                }
            } else {
                let type = typeof arg;
                throw `Argument ${arg} of type ${type} is not valid for messages.`;
            }
        });
        let msg = {body, type, scope};
        let i = _messages.length;
        _messages.push(msg);
        let handler = () => _messages.splice(i, 1);
        return promise.then(handler, handler);
    }

    /**
     * Getter for your messages. These are by default available on the root
     * controller (`monad`) as `monad.messages`.
     *
     * @return array Array of current messages.
     */
    get messages() {
        return _messages;
    }

    /**
     * Convenience method to add a `default` message.
     *
     * @see Report.add
     * @return promise
     */
    ['default'](...args) {
        return this.add(...args.concat(['default']));
    }

    /**
     * Convenience method to add a `primary` message.
     *
     * @see Report.add
     * @return promise
     */
    primary(...args) {
        return this.add(...args.concat(['primary']));
    }

    /**
     * Convenience method to add a `success` message.
     *
     * @see Report.add
     * @return promise
     */
    success(...args) {
        return this.add(...args.concat(['success']));
    }

    /**
     * Convenience method to add an `info` message.
     *
     * @see Report.add
     * @return promise
     */
    info(...args) {
        return this.add(...args.concat(['info']));
    }

    /**
     * Convenience method to add a `warning` message.
     *
     * @see Report.add
     * @return promise
     */
    warning(...args) {
        return this.add(...args.concat(['warning']));
    }

    /**
     * Convenience method to add a `danger` message.
     *
     * @see Report.add
     * @return promise
     */
    danger(...args) {
        return this.add(...args.concat(['danger']));
    }

}

Report.$inject = ['$timeout'];

<|endoftext|>


<commit_before>#!/usr/bin/env node
const TextToSVG = require('text-to-svg');
const textToSVG = TextToSVG.loadSync('fonts/Px437_IBM_EGA8.otf');
const fs = require('fs');
const path = (process.argv[2] || '.').replace(/\/$/, '');
var list = ["// This file is auto-generated from icon-gen in the root directory"];

const variationList = [
  ['black', '#000000'],
  ['grayLight', '#bbbbbb']
];

[
  ['[', 'left-brace'],
  [']', 'right-brace'],
  ['[', 'left-brace'],
  ['-', 'hyphen'],
  ['\u25B2', 'arrow-up'],
  ['\u25Bc', 'arrow-down'],
  ['\u2591', 'shade-25'],
  ['\u2592', 'shade-50'],
  ['\u2593', 'shade-75'],

  ['[\u25A0]', 'checkbox-checked'],
  ['[ ]', 'checkbox-unchecked'],
  [`\u253c\u2584`, 'grid'],
  ['(\u2022)', 'radio-checked'],
  ['( )', 'radio-unchecked']
].forEach(row => {
  let [str, string] = row;

  for(const [name, hex] of variationList) {
    let props = {
      x: 0, y: 0,
      fontSize: 16, anchor: 'top',
      attributes: {fill: hex}
    };

    if (row.length > 3) {
      props = Object.assign(props, row[3]);
    }

    if(name == 'grid') {
      props.attributes.fill += '40';
    }

    const
      svg = textToSVG.getSVG(str, props),
      fullpath = `${path}/${string}-${name}.svg`

    fs.writeFileSync(fullpath, svg);

    console.log(fullpath);
  }

  //if(bsname) {
  //  list.push(`$${bsname}: url('data:image/svg+xml,${svg}');`);
  //}
});

//fs.writeFileSync(`v4.4.1/scss/_autogen.scss`, list.join('\n'));
<commit_msg>Adding full block<commit_after>#!/usr/bin/env node
const TextToSVG = require('text-to-svg');
const textToSVG = TextToSVG.loadSync('fonts/Px437_IBM_EGA8.otf');
const fs = require('fs');
const path = (process.argv[2] || '.').replace(/\/$/, '');
var list = ["// This file is auto-generated from icon-gen in the root directory"];

const variationList = [
  ['black', '#000000'],
  ['grayLight', '#bbbbbb']
];

[
  ['[', 'left-brace'],
  [']', 'right-brace'],
  ['[', 'left-brace'],
  ['-', 'hyphen'],
  ['\u25B2', 'arrow-up'],
  ['\u25Bc', 'arrow-down'],
  ['\u2591', 'shade-25'],
  ['\u2592', 'shade-50'],
  ['\u2593', 'shade-75'],
  ['\u2588', 'shade-100'],

  ['[\u25A0]', 'checkbox-checked'],
  ['[ ]', 'checkbox-unchecked'],
  [`\u253c\u2584`, 'grid'],
  ['(\u2022)', 'radio-checked'],
  ['( )', 'radio-unchecked']
].forEach(row => {
  let [str, string] = row;

  for(const [name, hex] of variationList) {
    let props = {
      x: 0, y: 0,
      fontSize: 16, anchor: 'top',
      attributes: {fill: hex}
    };

    if (row.length > 3) {
      props = Object.assign(props, row[3]);
    }

    if(name == 'grid') {
      props.attributes.fill += '40';
    }

    const
      svg = textToSVG.getSVG(str, props),
      fullpath = `${path}/${string}-${name}.svg`

    fs.writeFileSync(fullpath, svg);

    console.log(fullpath);
  }

  //if(bsname) {
  //  list.push(`$${bsname}: url('data:image/svg+xml,${svg}');`);
  //}
});

//fs.writeFileSync(`v4.4.1/scss/_autogen.scss`, list.join('\n'));
<|endoftext|>


<commit_before>(function() {

/**
 * Создает ns.Update
 * @class ns.Update
 * @param {ns.View} view Корневой view.
 * @param {Object} layout Layout для этого view, результат от ns.layout.page()
 * @param {Object} params Параметры, результат от ns.router()
 * @param {Object} [options] Options for ns.Update
 * @param {ns.U.EXEC} [options.execFlag=ns.U.EXEC.GLOBAL] Options for ns.Update
 * @constructor
 * @example
 * var route = ns.router('/folder/123/message/456');
 * var layout = ns.layout.page(route.page, route.params);
 * var update = new ns.Update(AppBlock, layout, route.params);
 * update.start();
 */
ns.Update = function(view, layout, params, options) {
    /**
     * Корневой view.
     * @private
     * @type {ns.View}
     */
    this.view = view;

    // ищем layout от view
    if (this.view.id in layout) {
        this.layout = layout[this.view.id];

    } else {
        // если его нет - ругаемся
        throw new Error("[ns.Update] Can't find view layout");
    }

    this.params = params;

    /**
     * Update ID
     * @type {number}
     * @private
     */
    this.id = ++update_id;

    options = options || {};

    /**
     * Execution flag
     * @type {ns.U.EXEC}
     */
    this.EXEC_FLAG = options.execFlag || ns.U.EXEC.GLOBAL;
};

/**
 * Current ns.Updates.
 * @type ns.Update[]
 * @private
 */
var currentUpdates = [];

/**
 * Id последнего созданного update-а.
 * @type {Number}
 */
var update_id = -1;

/**
 * @see ns.U.STATUS
 * @enum {Number}
 * @borrows ns.U.STATUS as ns.Update.prototype.STATUS
 */
ns.Update.prototype.STATUS = ns.U.STATUS;

/**
 * Порядок событий для View.
 * @type {Array}
 * @private
 */
ns.Update.prototype._EVENTS_ORDER = ['ns-view-hide', 'ns-view-htmldestroy', 'ns-view-htmlinit', 'ns-view-async', 'ns-view-show', 'ns-view-repaint'];

/**
 * Check that this update is still actual (no new ns.Update is started).
 * @returns {boolean}
 */
ns.Update.prototype.iAmActual = function() {
    return this.id === update_id;
};

/**
 * Начинает работу updater'а.
 * @param [async=false] Флаг асинхронного updater'а.
 * @return {no.Promise}
 */
ns.Update.prototype.start = function(async) {
    var resultPromise = new no.Promise();
    this.promise = resultPromise;

    if (!this.addToQueue(this)) {
        this.abort();
        return this.promise;
    }

    var updated = this.view._getRequestViews({
        sync: [],
        async: []
    }, this.layout.views, this.params);

    var that = this;

    var models = views2models(updated.sync);

    // create promise for each async view
    var asyncUpdaterPromises = updated.async.map(function() {
        return new no.Promise();
    });

    var syncModelsPromise = ns.request.models(models)
        .done(function(models) {
            if (that._expired()) {
                that.error({
                    error: that.STATUS.EXPIRED
                });

            } else {
                //FIXME: we should delete this loop when ns.request will can reject promise
                // check that all models is valid
                for (var i = 0, j = models.length; i < j; i++) {
                    if (!models[i].isValid()) {
                        that.error({
                            error: that.STATUS.MODELS,
                            models: models
                        });
                        return;
                    }
                }

                that._update(async);
                // resolve main promise and return promises for async views
                that.done({
                    async: asyncUpdaterPromises
                });
            }
        })
        .fail(function(models) {
            //FIXME: ns.request.models can't reject promise this time, we should fix it
            that.error({
                error: that.STATUS.MODELS,
                models: models
            });
        });

    // Для каждого async-view запрашиваем его модели.
    // Когда они приходят, запускаем точно такой же update.
    // Причем ждем отрисовку sync-view, чтобы точно запуститься после него.
    updated.async.forEach(function(view, asyncViewId) {
        var models = views2models( [ view ] );
        no.Promise.wait([
            syncModelsPromise,
            ns.request.models(models)
        ]).done(function(result) {
            var models = result[1];
            //FIXME: we should delete this loop when ns.request will can reject promise
            // check that all models is valid
            for (var i = 0, j = models.length; i < j; i++) {
                if (!models[i].isValid()) {
                    asyncUpdaterPromises[asyncViewId].reject({
                        error: that.STATUS.MODELS,
                        async_view: view,
                        models: models
                    });
                    return;
                }
            }

            var fakeLayout = {};
            fakeLayout[that.view.id] = that.layout;

            // if this update is still actual
            if (that.iAmActual()) {
                // start update for async views
                new ns.Update(that.view, fakeLayout, that.params, {execFlag: ns.U.EXEC.ASYNC})
                    .start(true)
                    // pipes ns.Update promise to asyncPromise
                    .pipe(asyncUpdaterPromises[asyncViewId]);
            }

        }).fail(function(result) {
            //FIXME: ns.request.models can't reject promise this time, we should fix it
            asyncUpdaterPromises[asyncViewId].reject({
                error: that.STATUS.MODELS,
                async_view: view,
                models: result[1]
            });
        });
    });

    return resultPromise;
};

/**
 * Обновляет DOM и триггерит нужные события
 * @param [async=false] Флаг асинхронного updater'а.
 * @private
 */
ns.Update.prototype._update = function(async) {
    // check that this update is still actual
    if (!this.iAmActual()) {
        return;
    }

    var params = this.params;
    var layout = this.layout;

    var tree = {
        'location': document.location,
        'layout-params': params,
        'views': {}
    };
    this.view._getUpdateTree(tree, layout.views, params);

    ns.log.debug('[ns.Update]', 'start()', this.id, 'updateTree', tree);

    var node;
    // если пустое дерево, то ничего не реднерим,
    // но кидаем события и скрываем/открываем блоки
    if (!ns.object.isEmpty(tree.views)) {
        node = ns.tmpl(tree, null, '');
        ns.log.debug('[ns.Update]', 'start()', this.id, 'new node', node.cloneNode(true));
    }

    var viewEvents = {
        'ns-view-async': [],
        'ns-view-hide': [],
        'ns-view-htmldestroy': [],
        'ns-view-htmlinit': [],
        'ns-view-show': [],
        'ns-view-repaint': []
    };

    this.view._updateHTML(node, layout.views, params, {
        toplevel: true,
        async: async
    }, viewEvents);

    for (var i = 0, j = this._EVENTS_ORDER.length; i < j; i++) {
        var event = this._EVENTS_ORDER[i];
        var views = viewEvents[event];
        for (var k = views.length - 1; k >= 0; k--) {
            views[k].trigger(event, params);
        }
    }
};

//  ---------------------------------------------------------------------------------------------------------------  //

/**
 * @return {Boolean} true in case another update was created after current update.
 * @private
 */
ns.Update.prototype._expired = function() {
    return this.stopped || currentUpdates.indexOf(this) === -1;
};

ns.Update.prototype.abort = function() {
    //TODO: Should we abort ns.request?

    /**
     * Flag that update was stopped.
     * @type {boolean}
     */
    this.stopped = true;

    // reject promise
    this.error({
        error: ns.U.STATUS.EXPIRED
    });
};

/**
 * @private
 * @param result
 */
ns.Update.prototype.done = function(result) {
    this.removeFromQueue();
    this.promise.resolve(result);
};

/**
 * @private
 * @param result
 */
ns.Update.prototype.error = function(result) {
    this.removeFromQueue();
    this.promise.reject(result);
};

/**
 * @private
 */
ns.Update.prototype.removeFromQueue = function() {
    var index = currentUpdates.indexOf(this);
    if (index > -1) {
        currentUpdates.splice(index, 1);
    }
};

/**
 * Check whether it is possible to execute given ns.Update
 * @description
 * ns.Update can be global, async or parallel.
 * Cases:
 *   - Global update can be terminated by another global Update only.
 *   - Global Update terminated all Updates except parallel.
 *   - Async updates execute simultaneously.
 *   - Parallel update can't be terminated.
 * @static
 * @private
 * @param {ns.Update} newUpdate New instance of ns.Update.
 * @returns Boolean
 */
ns.Update.prototype.addToQueue = function(newUpdate) {
    var currentRuns = currentUpdates;
    var FLAGS = ns.U.EXEC;
    var FLAG_GLOBAL = FLAGS.GLOBAL;
    var FLAG_PARALLEL = FLAGS.PARALLEL;
    var FLAG_ASYNC = FLAGS.ASYNC;

    var newRunExecutionFlag = newUpdate.EXEC_FLAG;
    var i,j;

    // if newUpdate is global we should terminate all non-parallel updates
    if (newRunExecutionFlag === FLAG_GLOBAL) {
        var survivedRuns = [];
        //прекращаем текущие runs
        for (i = 0, j = currentRuns.length; i < j; i++) {
            /**
             * @type {ns.Update}
             */
            var run = currentRuns[i];

            // don't terminated paraller updates
            if (run.EXEC_FLAG === FLAG_PARALLEL) {
                survivedRuns.push(run);

            } else {
                run.abort();
            }
        }
        // save survived updates
        currentUpdates = survivedRuns;

    } else if (newRunExecutionFlag === FLAG_ASYNC) { // async update

        // check whether we have one global update
        for (i = 0, j = currentRuns.length; i < j; i++) {
            if (currentRuns[i].EXEC_FLAG === FLAG_GLOBAL) {
                return false;
            }
        }

    }

    currentUpdates.push(newUpdate);

    return true;
};

// ----------------------------------------------------------------------------------------------------------------- //

function views2models(views) {
    var added = {};
    var models = [];

    for (var i = 0, l = views.length; i < l; i++) {
        var viewModels = views[i].models;
        for (var model_id in viewModels) {
            var model = viewModels[model_id];
            var key = model.key;
            if ( !added[key] ) {
                models.push(model);
                added[key] = true;
            }
        }
    }

    return models;
}

})();

<commit_msg>Revert "check for new ns.update"<commit_after>(function() {

/**
 * Создает ns.Update
 * @class ns.Update
 * @param {ns.View} view Корневой view.
 * @param {Object} layout Layout для этого view, результат от ns.layout.page()
 * @param {Object} params Параметры, результат от ns.router()
 * @param {Object} [options] Options for ns.Update
 * @param {ns.U.EXEC} [options.execFlag=ns.U.EXEC.GLOBAL] Options for ns.Update
 * @constructor
 * @example
 * var route = ns.router('/folder/123/message/456');
 * var layout = ns.layout.page(route.page, route.params);
 * var update = new ns.Update(AppBlock, layout, route.params);
 * update.start();
 */
ns.Update = function(view, layout, params, options) {
    /**
     * Корневой view.
     * @private
     * @type {ns.View}
     */
    this.view = view;

    // ищем layout от view
    if (this.view.id in layout) {
        this.layout = layout[this.view.id];

    } else {
        // если его нет - ругаемся
        throw new Error("[ns.Update] Can't find view layout");
    }

    this.params = params;

    this.id = ++update_id;

    options = options || {};

    /**
     * Execution flag
     * @type {ns.U.EXEC}
     */
    this.EXEC_FLAG = options.execFlag || ns.U.EXEC.GLOBAL;
};

/**
 * Current ns.Updates.
 * @type ns.Update[]
 * @private
 */
var currentUpdates = [];

/**
 * Id последнего созданного update-а.
 * @type {Number}
 */
var update_id = -1;

/**
 * @see ns.U.STATUS
 * @enum {Number}
 * @borrows ns.U.STATUS as ns.Update.prototype.STATUS
 */
ns.Update.prototype.STATUS = ns.U.STATUS;

/**
 * Порядок событий для View.
 * @type {Array}
 * @private
 */
ns.Update.prototype._EVENTS_ORDER = ['ns-view-hide', 'ns-view-htmldestroy', 'ns-view-htmlinit', 'ns-view-async', 'ns-view-show', 'ns-view-repaint'];

/**
 * Начинает работу updater'а.
 * @param [async=false] Флаг асинхронного updater'а.
 * @return {no.Promise}
 */
ns.Update.prototype.start = function(async) {
    var resultPromise = new no.Promise();
    this.promise = resultPromise;

    if (!this.addToQueue(this)) {
        this.abort();
        return this.promise;
    }

    var updated = this.view._getRequestViews({
        sync: [],
        async: []
    }, this.layout.views, this.params);

    var that = this;

    var models = views2models(updated.sync);

    // create promise for each async view
    var asyncUpdaterPromises = updated.async.map(function() {
        return new no.Promise();
    });

    var syncModelsPromise = ns.request.models(models)
        .done(function(models) {
            if (that._expired()) {
                that.error({
                    error: that.STATUS.EXPIRED
                });

            } else {
                //FIXME: we should delete this loop when ns.request will can reject promise
                // check that all models is valid
                for (var i = 0, j = models.length; i < j; i++) {
                    if (!models[i].isValid()) {
                        that.error({
                            error: that.STATUS.MODELS,
                            models: models
                        });
                        return;
                    }
                }

                that._update(async);
                // resolve main promise and return promises for async views
                that.done({
                    async: asyncUpdaterPromises
                });
            }
        })
        .fail(function(models) {
            //FIXME: ns.request.models can't reject promise this time, we should fix it
            that.error({
                error: that.STATUS.MODELS,
                models: models
            });
        });

    // Для каждого async-view запрашиваем его модели.
    // Когда они приходят, запускаем точно такой же update.
    // Причем ждем отрисовку sync-view, чтобы точно запуститься после него.
    updated.async.forEach(function(view, asyncViewId) {
        var models = views2models( [ view ] );
        no.Promise.wait([
            syncModelsPromise,
            ns.request.models(models)
        ]).done(function(result) {
            var models = result[1];
            //FIXME: we should delete this loop when ns.request will can reject promise
            // check that all models is valid
            for (var i = 0, j = models.length; i < j; i++) {
                if (!models[i].isValid()) {
                    asyncUpdaterPromises[asyncViewId].reject({
                        error: that.STATUS.MODELS,
                        async_view: view,
                        models: models
                    });
                    return;
                }
            }

            var fakeLayout = {};
            fakeLayout[that.view.id] = that.layout;
            new ns.Update(that.view, fakeLayout, that.params, {execFlag: ns.U.EXEC.ASYNC})
                .start(true)
                // pipes ns.Update promise to asyncPromise
                .pipe(asyncUpdaterPromises[asyncViewId]);

        }).fail(function(result) {
            //FIXME: ns.request.models can't reject promise this time, we should fix it
            asyncUpdaterPromises[asyncViewId].reject({
                error: that.STATUS.MODELS,
                async_view: view,
                models: result[1]
            });
        });
    });

    return resultPromise;
};

/**
 * Обновляет DOM и триггерит нужные события
 * @param [async=false] Флаг асинхронного updater'а.
 * @private
 */
ns.Update.prototype._update = function(async) {
    //  TODO: Проверить, что не начался уже более новый апдейт.

    var params = this.params;
    var layout = this.layout;

    var tree = {
        'location': document.location,
        'layout-params': params,
        'views': {}
    };
    this.view._getUpdateTree(tree, layout.views, params);

    ns.log.debug('[ns.Update]', 'start()', this.id, 'updateTree', tree);

    var node;
    // если пустое дерево, то ничего не реднерим,
    // но кидаем события и скрываем/открываем блоки
    if (!ns.object.isEmpty(tree.views)) {
        node = ns.tmpl(tree, null, '');
        ns.log.debug('[ns.Update]', 'start()', this.id, 'new node', node.cloneNode(true));
    }

    var viewEvents = {
        'ns-view-async': [],
        'ns-view-hide': [],
        'ns-view-htmldestroy': [],
        'ns-view-htmlinit': [],
        'ns-view-show': [],
        'ns-view-repaint': []
    };

    this.view._updateHTML(node, layout.views, params, {
        toplevel: true,
        async: async
    }, viewEvents);

    for (var i = 0, j = this._EVENTS_ORDER.length; i < j; i++) {
        var event = this._EVENTS_ORDER[i];
        var views = viewEvents[event];
        for (var k = views.length - 1; k >= 0; k--) {
            views[k].trigger(event, params);
        }
    }
};

//  ---------------------------------------------------------------------------------------------------------------  //

/**
 * @return {Boolean} true in case another update was created after current update.
 * @private
 */
ns.Update.prototype._expired = function() {
    return this.stopped || currentUpdates.indexOf(this) === -1;
};

ns.Update.prototype.abort = function() {
    //TODO: Should we abort ns.request?

    /**
     * Flag that update was stopped.
     * @type {boolean}
     */
    this.stopped = true;

    // reject promise
    this.error({
        error: ns.U.STATUS.EXPIRED
    });
};

/**
 * @private
 * @param result
 */
ns.Update.prototype.done = function(result) {
    this.removeFromQueue();
    this.promise.resolve(result);
};

/**
 * @private
 * @param result
 */
ns.Update.prototype.error = function(result) {
    this.removeFromQueue();
    this.promise.reject(result);
};

/**
 * @private
 */
ns.Update.prototype.removeFromQueue = function() {
    var index = currentUpdates.indexOf(this);
    if (index > -1) {
        currentUpdates.splice(index, 1);
    }
};

/**
 * Check whether it is possible to execute given ns.Update
 * @description
 * ns.Update can be global, async or parallel.
 * Cases:
 *   - Global update can be terminated by another global Update only.
 *   - Global Update terminated all Updates except parallel.
 *   - Async updates execute simultaneously.
 *   - Parallel update can't be terminated.
 * @static
 * @private
 * @param {ns.Update} newUpdate New instance of ns.Update.
 * @returns Boolean
 */
ns.Update.prototype.addToQueue = function(newUpdate) {
    var currentRuns = currentUpdates;
    var FLAGS = ns.U.EXEC;
    var FLAG_GLOBAL = FLAGS.GLOBAL;
    var FLAG_PARALLEL = FLAGS.PARALLEL;
    var FLAG_ASYNC = FLAGS.ASYNC;

    var newRunExecutionFlag = newUpdate.EXEC_FLAG;
    var i,j;

    // if newUpdate is global we should terminate all non-parallel updates
    if (newRunExecutionFlag === FLAG_GLOBAL) {
        var survivedRuns = [];
        //прекращаем текущие runs
        for (i = 0, j = currentRuns.length; i < j; i++) {
            /**
             * @type {ns.Update}
             */
            var run = currentRuns[i];

            // don't terminated paraller updates
            if (run.EXEC_FLAG === FLAG_PARALLEL) {
                survivedRuns.push(run);

            } else {
                run.abort();
            }
        }
        // save survived updates
        currentUpdates = survivedRuns;

    } else if (newRunExecutionFlag === FLAG_ASYNC) { // async update

        // check whether we have one global update
        for (i = 0, j = currentRuns.length; i < j; i++) {
            if (currentRuns[i].EXEC_FLAG === FLAG_GLOBAL) {
                return false;
            }
        }

    }

    currentUpdates.push(newUpdate);

    return true;
};

// ----------------------------------------------------------------------------------------------------------------- //

function views2models(views) {
    var added = {};
    var models = [];

    for (var i = 0, l = views.length; i < l; i++) {
        var viewModels = views[i].models;
        for (var model_id in viewModels) {
            var model = viewModels[model_id];
            var key = model.key;
            if ( !added[key] ) {
                models.push(model);
                added[key] = true;
            }
        }
    }

    return models;
}

})();

<|endoftext|>


<commit_before>const {textRange, parentNode} = require("./dom")

function windowRect() {
  return {left: 0, right: window.innerWidth,
          top: 0, bottom: window.innerHeight}
}

function scrollRectIntoView(view, rect) {
  let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin")
  if (scrollMargin == null) scrollMargin = 5
  for (let parent = view.dom;; parent = parentNode(parent)) {
    if (!parent) break
    let atBody = parent == document.body
    let bounding = atBody ? windowRect() : parent.getBoundingClientRect()
    let moveX = 0, moveY = 0
    if (rect.top < bounding.top + scrollThreshold)
      moveY = -(bounding.top - rect.top + scrollMargin)
    else if (rect.bottom > bounding.bottom - scrollThreshold)
      moveY = rect.bottom - bounding.bottom + scrollMargin
    if (rect.left < bounding.left + scrollThreshold)
      moveX = -(bounding.left - rect.left + scrollMargin)
    else if (rect.right > bounding.right - scrollThreshold)
      moveX = rect.right - bounding.right + scrollMargin
    if (moveX || moveY) {
      if (atBody) {
        window.scrollBy(moveX, moveY)
      } else {
        if (moveY) parent.scrollTop += moveY
        if (moveX) parent.scrollLeft += moveX
      }
    }
    if (atBody) break
  }
}
exports.scrollRectIntoView = scrollRectIntoView

// Store the scroll position of the editor's parent nodes, along with
// the top position of an element near the top of the editor, which
// will be used to make sure the visible viewport remains stable even
// when the size of the content above changes.
function storeScrollPos(view) {
  let rect = view.dom.getBoundingClientRect(), startY = Math.max(0, rect.top)
  let refDOM, refTop
  for (let x = (rect.left + rect.right) / 2, y = startY + 1;
       y < Math.min(innerHeight, rect.bottom); y += 5) {
    let dom = view.root.elementFromPoint(x, y)
    if (dom == view.dom || !view.dom.contains(dom)) continue
    let localRect = dom.getBoundingClientRect()
    if (localRect.top >= startY - 20) {
      refDOM = dom
      refTop = localRect.top
      break
    }
  }
  let stack = []
  for (let dom = view.dom; dom; dom = parentNode(dom)) {
    stack.push({dom, top: dom.scrollTop, left: dom.scrollLeft})
    if (dom == document.body) break
  }
  return {refDOM, refTop, stack}
}
exports.storeScrollPos = storeScrollPos

// Reset the scroll position of the editor's parent nodes to that what
// it was before, when storeScrollPos was called.
function resetScrollPos({refDOM, refTop, stack}) {
  let newRefTop = refDOM ? refDOM.getBoundingClientRect().top : 0
  let dTop = newRefTop == 0 ? 0 : newRefTop - refTop
  for (let i = 0; i < stack.length; i++) {
    let {dom, top, left} = stack[i]
    if (dom.scrollTop != top + dTop) dom.scrollTop = top + dTop
    if (dom.scrollLeft != left) dom.scrollLeft = left
  }
}
exports.resetScrollPos = resetScrollPos

function findOffsetInNode(node, coords) {
  let closest, dxClosest = 2e8, coordsClosest, offset = 0
  let rowBot = coords.top, rowTop = coords.top
  for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
    let rects
    if (child.nodeType == 1) rects = child.getClientRects()
    else if (child.nodeType == 3) rects = textRange(child).getClientRects()
    else continue

    for (let i = 0; i < rects.length; i++) {
      let rect = rects[i]
      if (rect.top <= rowBot && rect.bottom >= rowTop) {
        rowBot = Math.max(rect.bottom, rowBot)
        rowTop = Math.min(rect.top, rowTop)
        let dx = rect.left > coords.left ? rect.left - coords.left
            : rect.right < coords.left ? coords.left - rect.right : 0
        if (dx < dxClosest) {
          closest = child
          dxClosest = dx
          coordsClosest = dx && closest.nodeType == 3 ? {left: rect.right < coords.left ? rect.right : rect.left, top: coords.top} : coords
          if (child.nodeType == 1 && dx)
            offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
          continue
        }
      }
      if (!closest && (coords.left >= rect.right && coords.top >= rect.top ||
                       coords.left >= rect.left && coords.top >= rect.bottom))
        offset = childIndex + 1
    }
  }
  if (closest && closest.nodeType == 3) return findOffsetInText(closest, coordsClosest)
  if (!closest || (dxClosest && closest.nodeType == 1)) return {node, offset}
  return findOffsetInNode(closest, coordsClosest)
}

function findOffsetInText(node, coords) {
  let len = node.nodeValue.length
  let range = document.createRange()
  for (let i = 0; i < len; i++) {
    range.setEnd(node, i + 1)
    range.setStart(node, i)
    let rect = singleRect(range, 1)
    if (rect.top == rect.bottom) continue
    if (rect.left - 1 <= coords.left && rect.right + 1 >= coords.left &&
        rect.top - 1 <= coords.top && rect.bottom + 1 >= coords.top)
      return {node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)}
  }
  return {node, offset: 0}
}

function targetKludge(dom, coords) {
  if (/^[uo]l$/i.test(dom.nodeName)) {
    for (let child = dom.firstChild; child; child = child.nextSibling) {
      if (!child.pmViewDesc || !/^li$/i.test(child.nodeName)) continue
      let childBox = child.getBoundingClientRect()
      if (coords.left > childBox.left - 2) break
      if (childBox.top <= coords.top && childBox.bottom >= coords.top) return child
    }
  }
  return dom
}

function posFromElement(view, elt, coords) {
  elt = targetKludge(elt, coords)
  if (!view.dom.contains(elt.nodeType != 1 ? elt.parentNode : elt)) return null

  let {node, offset} = findOffsetInNode(elt, coords), bias = -1
  if (node.nodeType == 1 && !node.firstChild) {
    let rect = node.getBoundingClientRect()
    bias = rect.left != rect.right && coords.left > (rect.left + rect.right) / 2 ? 1 : -1
  }
  return view.docView.posFromDOM(node, offset, bias)
}

function posFromCaret(view, node, offset, coords) {
  // Browser (in caretPosition/RangeFromPoint) will agressively
  // normalize towards nearby inline nodes. Since we are interested in
  // positions between block nodes too, we first walk up the hierarchy
  // of nodes to see if there are block nodes that the coordinates
  // fall outside of. If so, we take the position before/after that
  // block. If not, we call `posFromDOM` on the raw node/offset.
  let outside = -1
  for (let cur = node;;) {
    if (cur == view.dom) break
    let desc = view.docView.nearestDesc(cur, true)
    if (desc.node.isBlock) {
      let rect = desc.dom.getBoundingClientRect()
      if (rect.left > coords.left || rect.top > coords.top) outside = desc.posBefore
      else if (rect.right < coords.left || rect.bottom < coords.top) outside = desc.posAfter
      else break
    }
    cur = desc.dom.parentNode
  }
  return outside > -1 ? outside : view.docView.posFromDOM(node, offset)
}

// Given an x,y position on the editor, get the position in the document.
function posAtCoords(view, coords) {
  let root = view.root, node, offset
  if (root.caretPositionFromPoint) {
    let pos = root.caretPositionFromPoint(coords.left, coords.top)
    if (!pos) return null
    ;({offsetNode: node, offset} = pos)
  } else if (root.caretRangeFromPoint) {
    let range = root.caretRangeFromPoint(coords.left, coords.top)
    if (!range) return null
    ;({startContainer: node, startOffset: offset} = range)
  }

  let elt = root.elementFromPoint(coords.left, coords.top + 1)
  if (!elt) return null
  let pos = node ? posFromCaret(view, node, offset, coords) : posFromElement(view, elt, coords)
  if (pos == null) return null

  let desc = view.docView.nearestDesc(elt, true)
  return {pos, inside: desc ? desc.posAtStart - desc.border : -1}
}
exports.posAtCoords = posAtCoords

function singleRect(object, bias) {
  let rects = object.getClientRects()
  return !rects.length ? object.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1]
}

// : (EditorView, number) → {left: number, top: number, right: number, bottom: number}
// Given a position in the document model, get a bounding box of the
// character at that position, relative to the window.
function coordsAtPos(view, pos) {
  let {node, offset} = view.docView.domFromPos(pos)
  let side, rect
  if (node.nodeType == 3) {
    if (offset < node.nodeValue.length) {
      rect = singleRect(textRange(node, offset, offset + 1), -1)
      side = "left"
    }
    if ((!rect || rect.left == rect.right) && offset) {
      rect = singleRect(textRange(node, offset - 1, offset), 1)
      side = "right"
    }
  } else if (node.firstChild) {
    if (offset < node.childNodes.length) {
      let child = node.childNodes[offset]
      rect = singleRect(child.nodeType == 3 ? textRange(child) : child, -1)
      side = "left"
    }
    if ((!rect || rect.top == rect.bottom) && offset) {
      let child = node.childNodes[offset - 1]
      rect = singleRect(child.nodeType == 3 ? textRange(child) : child, 1)
      side = "right"
    }
  } else {
    rect = node.getBoundingClientRect()
    side = "left"
  }
  let x = rect[side]
  return {top: rect.top, bottom: rect.bottom, left: x, right: x}
}
exports.coordsAtPos = coordsAtPos

function withFlushedState(view, state, f) {
  let viewState = view.state, active = view.root.activeElement
  if (viewState != state || !view.inDOMChange) view.updateState(state)
  if (active != view.dom) view.focus()
  try {
    return f()
  } finally {
    if (viewState != state) view.updateState(viewState)
    if (active != view.dom) active.focus()
  }
}

// : (EditorView, number, number)
// Whether vertical position motion in a given direction
// from a position would leave a text block.
function endOfTextblockVertical(view, state, dir) {
  let $pos = dir == "up" ? state.selection.$from : state.selection.$to
  if (!$pos.depth) return false
  return withFlushedState(view, state, () => {
    let dom = view.docView.domAfterPos($pos.before())
    let coords = coordsAtPos(view, $pos.pos)
    for (let child = dom.firstChild; child; child = child.nextSibling) {
      let boxes
      if (child.nodeType == 1) boxes = child.getClientRects()
      else if (child.nodeType == 3) boxes = textRange(child, 0, child.nodeValue.length).getClientRects()
      else continue
      for (let i = 0; i < boxes.length; i++) {
        let box = boxes[i]
        if (dir == "up" ? box.bottom < coords.top + 1 : box.top > coords.bottom - 1)
          return false
      }
    }
    return true
  })
}

const maybeRTL = /[\u0590-\u08ac]/

function endOfTextblockHorizontal(view, state, dir) {
  let {$head, empty} = state.selection
  if (!empty || !$head.parent.isTextblock || !$head.depth) return false
  let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size
  // If the textblock is all LTR and the cursor isn't at the sides, we don't need to touch the DOM
  if (!atStart && !atEnd && !maybeRTL.test($head.parent.textContent)) return false
  let sel = getSelection()
  // Fall back to a primitive approach if the necessary selection method isn't supported (Edge)
  if (!sel.modify) return dir == "left" || dir == "backward" ? atStart : atEnd

  return withFlushedState(view, state, () => {
    // This is a huge hack, but appears to be the best we can
    // currently do: use `Selection.modify` to move the selection by
    // one character, and see if that moves the cursor out of the
    // textblock (or doesn't move it at all, when at the start/end of
    // the document).
    let oldRange = sel.getRangeAt(0), oldNode = sel.focusNode, oldOff = sel.focusOffset
    sel.modify("move", dir, "character")
    let parentDOM = view.docView.domAfterPos($head.before())
    let result = !parentDOM.contains(sel.focusNode.nodeType == 1 ? sel.focusNode : sel.focusNode.parentNode) ||
        (oldNode == sel.focusNode && oldOff == sel.focusOffset)
    // Restore the previous selection
    sel.removeAllRanges()
    sel.addRange(oldRange)
    return result
  })
}

let cachedState = null, cachedDir = null, cachedResult = false
function endOfTextblock(view, state, dir) {
  if (cachedState == state && cachedDir == dir) return cachedResult
  cachedState = state; cachedDir = dir
  return cachedResult = dir == "up" || dir == "down"
    ? endOfTextblockVertical(view, state, dir)
    : endOfTextblockHorizontal(view, state, dir)
}
exports.endOfTextblock = endOfTextblock
<commit_msg>Avoid costly DOM manipulation in endOfTextblockHorizontal<commit_after>const {textRange, parentNode} = require("./dom")

function windowRect() {
  return {left: 0, right: window.innerWidth,
          top: 0, bottom: window.innerHeight}
}

function scrollRectIntoView(view, rect) {
  let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin")
  if (scrollMargin == null) scrollMargin = 5
  for (let parent = view.dom;; parent = parentNode(parent)) {
    if (!parent) break
    let atBody = parent == document.body
    let bounding = atBody ? windowRect() : parent.getBoundingClientRect()
    let moveX = 0, moveY = 0
    if (rect.top < bounding.top + scrollThreshold)
      moveY = -(bounding.top - rect.top + scrollMargin)
    else if (rect.bottom > bounding.bottom - scrollThreshold)
      moveY = rect.bottom - bounding.bottom + scrollMargin
    if (rect.left < bounding.left + scrollThreshold)
      moveX = -(bounding.left - rect.left + scrollMargin)
    else if (rect.right > bounding.right - scrollThreshold)
      moveX = rect.right - bounding.right + scrollMargin
    if (moveX || moveY) {
      if (atBody) {
        window.scrollBy(moveX, moveY)
      } else {
        if (moveY) parent.scrollTop += moveY
        if (moveX) parent.scrollLeft += moveX
      }
    }
    if (atBody) break
  }
}
exports.scrollRectIntoView = scrollRectIntoView

// Store the scroll position of the editor's parent nodes, along with
// the top position of an element near the top of the editor, which
// will be used to make sure the visible viewport remains stable even
// when the size of the content above changes.
function storeScrollPos(view) {
  let rect = view.dom.getBoundingClientRect(), startY = Math.max(0, rect.top)
  let refDOM, refTop
  for (let x = (rect.left + rect.right) / 2, y = startY + 1;
       y < Math.min(innerHeight, rect.bottom); y += 5) {
    let dom = view.root.elementFromPoint(x, y)
    if (dom == view.dom || !view.dom.contains(dom)) continue
    let localRect = dom.getBoundingClientRect()
    if (localRect.top >= startY - 20) {
      refDOM = dom
      refTop = localRect.top
      break
    }
  }
  let stack = []
  for (let dom = view.dom; dom; dom = parentNode(dom)) {
    stack.push({dom, top: dom.scrollTop, left: dom.scrollLeft})
    if (dom == document.body) break
  }
  return {refDOM, refTop, stack}
}
exports.storeScrollPos = storeScrollPos

// Reset the scroll position of the editor's parent nodes to that what
// it was before, when storeScrollPos was called.
function resetScrollPos({refDOM, refTop, stack}) {
  let newRefTop = refDOM ? refDOM.getBoundingClientRect().top : 0
  let dTop = newRefTop == 0 ? 0 : newRefTop - refTop
  for (let i = 0; i < stack.length; i++) {
    let {dom, top, left} = stack[i]
    if (dom.scrollTop != top + dTop) dom.scrollTop = top + dTop
    if (dom.scrollLeft != left) dom.scrollLeft = left
  }
}
exports.resetScrollPos = resetScrollPos

function findOffsetInNode(node, coords) {
  let closest, dxClosest = 2e8, coordsClosest, offset = 0
  let rowBot = coords.top, rowTop = coords.top
  for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
    let rects
    if (child.nodeType == 1) rects = child.getClientRects()
    else if (child.nodeType == 3) rects = textRange(child).getClientRects()
    else continue

    for (let i = 0; i < rects.length; i++) {
      let rect = rects[i]
      if (rect.top <= rowBot && rect.bottom >= rowTop) {
        rowBot = Math.max(rect.bottom, rowBot)
        rowTop = Math.min(rect.top, rowTop)
        let dx = rect.left > coords.left ? rect.left - coords.left
            : rect.right < coords.left ? coords.left - rect.right : 0
        if (dx < dxClosest) {
          closest = child
          dxClosest = dx
          coordsClosest = dx && closest.nodeType == 3 ? {left: rect.right < coords.left ? rect.right : rect.left, top: coords.top} : coords
          if (child.nodeType == 1 && dx)
            offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
          continue
        }
      }
      if (!closest && (coords.left >= rect.right && coords.top >= rect.top ||
                       coords.left >= rect.left && coords.top >= rect.bottom))
        offset = childIndex + 1
    }
  }
  if (closest && closest.nodeType == 3) return findOffsetInText(closest, coordsClosest)
  if (!closest || (dxClosest && closest.nodeType == 1)) return {node, offset}
  return findOffsetInNode(closest, coordsClosest)
}

function findOffsetInText(node, coords) {
  let len = node.nodeValue.length
  let range = document.createRange()
  for (let i = 0; i < len; i++) {
    range.setEnd(node, i + 1)
    range.setStart(node, i)
    let rect = singleRect(range, 1)
    if (rect.top == rect.bottom) continue
    if (rect.left - 1 <= coords.left && rect.right + 1 >= coords.left &&
        rect.top - 1 <= coords.top && rect.bottom + 1 >= coords.top)
      return {node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)}
  }
  return {node, offset: 0}
}

function targetKludge(dom, coords) {
  if (/^[uo]l$/i.test(dom.nodeName)) {
    for (let child = dom.firstChild; child; child = child.nextSibling) {
      if (!child.pmViewDesc || !/^li$/i.test(child.nodeName)) continue
      let childBox = child.getBoundingClientRect()
      if (coords.left > childBox.left - 2) break
      if (childBox.top <= coords.top && childBox.bottom >= coords.top) return child
    }
  }
  return dom
}

function posFromElement(view, elt, coords) {
  elt = targetKludge(elt, coords)
  if (!view.dom.contains(elt.nodeType != 1 ? elt.parentNode : elt)) return null

  let {node, offset} = findOffsetInNode(elt, coords), bias = -1
  if (node.nodeType == 1 && !node.firstChild) {
    let rect = node.getBoundingClientRect()
    bias = rect.left != rect.right && coords.left > (rect.left + rect.right) / 2 ? 1 : -1
  }
  return view.docView.posFromDOM(node, offset, bias)
}

function posFromCaret(view, node, offset, coords) {
  // Browser (in caretPosition/RangeFromPoint) will agressively
  // normalize towards nearby inline nodes. Since we are interested in
  // positions between block nodes too, we first walk up the hierarchy
  // of nodes to see if there are block nodes that the coordinates
  // fall outside of. If so, we take the position before/after that
  // block. If not, we call `posFromDOM` on the raw node/offset.
  let outside = -1
  for (let cur = node;;) {
    if (cur == view.dom) break
    let desc = view.docView.nearestDesc(cur, true)
    if (desc.node.isBlock) {
      let rect = desc.dom.getBoundingClientRect()
      if (rect.left > coords.left || rect.top > coords.top) outside = desc.posBefore
      else if (rect.right < coords.left || rect.bottom < coords.top) outside = desc.posAfter
      else break
    }
    cur = desc.dom.parentNode
  }
  return outside > -1 ? outside : view.docView.posFromDOM(node, offset)
}

// Given an x,y position on the editor, get the position in the document.
function posAtCoords(view, coords) {
  let root = view.root, node, offset
  if (root.caretPositionFromPoint) {
    let pos = root.caretPositionFromPoint(coords.left, coords.top)
    if (!pos) return null
    ;({offsetNode: node, offset} = pos)
  } else if (root.caretRangeFromPoint) {
    let range = root.caretRangeFromPoint(coords.left, coords.top)
    if (!range) return null
    ;({startContainer: node, startOffset: offset} = range)
  }

  let elt = root.elementFromPoint(coords.left, coords.top + 1)
  if (!elt) return null
  let pos = node ? posFromCaret(view, node, offset, coords) : posFromElement(view, elt, coords)
  if (pos == null) return null

  let desc = view.docView.nearestDesc(elt, true)
  return {pos, inside: desc ? desc.posAtStart - desc.border : -1}
}
exports.posAtCoords = posAtCoords

function singleRect(object, bias) {
  let rects = object.getClientRects()
  return !rects.length ? object.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1]
}

// : (EditorView, number) → {left: number, top: number, right: number, bottom: number}
// Given a position in the document model, get a bounding box of the
// character at that position, relative to the window.
function coordsAtPos(view, pos) {
  let {node, offset} = view.docView.domFromPos(pos)
  let side, rect
  if (node.nodeType == 3) {
    if (offset < node.nodeValue.length) {
      rect = singleRect(textRange(node, offset, offset + 1), -1)
      side = "left"
    }
    if ((!rect || rect.left == rect.right) && offset) {
      rect = singleRect(textRange(node, offset - 1, offset), 1)
      side = "right"
    }
  } else if (node.firstChild) {
    if (offset < node.childNodes.length) {
      let child = node.childNodes[offset]
      rect = singleRect(child.nodeType == 3 ? textRange(child) : child, -1)
      side = "left"
    }
    if ((!rect || rect.top == rect.bottom) && offset) {
      let child = node.childNodes[offset - 1]
      rect = singleRect(child.nodeType == 3 ? textRange(child) : child, 1)
      side = "right"
    }
  } else {
    rect = node.getBoundingClientRect()
    side = "left"
  }
  let x = rect[side]
  return {top: rect.top, bottom: rect.bottom, left: x, right: x}
}
exports.coordsAtPos = coordsAtPos

function withFlushedState(view, state, f) {
  let viewState = view.state, active = view.root.activeElement
  if (viewState != state || !view.inDOMChange) view.updateState(state)
  if (active != view.dom) view.focus()
  try {
    return f()
  } finally {
    if (viewState != state) view.updateState(viewState)
    if (active != view.dom) active.focus()
  }
}

// : (EditorView, number, number)
// Whether vertical position motion in a given direction
// from a position would leave a text block.
function endOfTextblockVertical(view, state, dir) {
  let $pos = dir == "up" ? state.selection.$from : state.selection.$to
  if (!$pos.depth) return false
  return withFlushedState(view, state, () => {
    let dom = view.docView.domAfterPos($pos.before())
    let coords = coordsAtPos(view, $pos.pos)
    for (let child = dom.firstChild; child; child = child.nextSibling) {
      let boxes
      if (child.nodeType == 1) boxes = child.getClientRects()
      else if (child.nodeType == 3) boxes = textRange(child, 0, child.nodeValue.length).getClientRects()
      else continue
      for (let i = 0; i < boxes.length; i++) {
        let box = boxes[i]
        if (dir == "up" ? box.bottom < coords.top + 1 : box.top > coords.bottom - 1)
          return false
      }
    }
    return true
  })
}

const maybeRTL = /[\u0590-\u08ac]/

function endOfTextblockHorizontal(view, state, dir) {
  let {$head, empty} = state.selection
  if (!empty || !$head.parent.isTextblock || !$head.depth) return false
  let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size
  let sel = getSelection()
  // If the textblock is all LTR, or the browser doesn't support
  // Selection.modify (Edge), fall back to a primitive approach
  if (!maybeRTL.test($head.parent.textContent) || !sel.modify)
    return dir == "left" || dir == "backward" ? atStart : atEnd

  return withFlushedState(view, state, () => {
    // This is a huge hack, but appears to be the best we can
    // currently do: use `Selection.modify` to move the selection by
    // one character, and see if that moves the cursor out of the
    // textblock (or doesn't move it at all, when at the start/end of
    // the document).
    let oldRange = sel.getRangeAt(0), oldNode = sel.focusNode, oldOff = sel.focusOffset
    sel.modify("move", dir, "character")
    let parentDOM = view.docView.domAfterPos($head.before())
    let result = !parentDOM.contains(sel.focusNode.nodeType == 1 ? sel.focusNode : sel.focusNode.parentNode) ||
        (oldNode == sel.focusNode && oldOff == sel.focusOffset)
    // Restore the previous selection
    sel.removeAllRanges()
    sel.addRange(oldRange)
    return result
  })
}

let cachedState = null, cachedDir = null, cachedResult = false
function endOfTextblock(view, state, dir) {
  if (cachedState == state && cachedDir == dir) return cachedResult
  cachedState = state; cachedDir = dir
  return cachedResult = dir == "up" || dir == "down"
    ? endOfTextblockVertical(view, state, dir)
    : endOfTextblockHorizontal(view, state, dir)
}
exports.endOfTextblock = endOfTextblock
<|endoftext|>


<commit_before>import * as core from '../../core';

const Container = core.Container;

/**
 * Removes all children.
 *
 * @param {boolean} [destroyChildren=false] - If set to true, all children will have their destroy
 *  method called as well.
 */
Container.prototype.removeAllChildren = function (destroyChildren)
{
    if (destroyChildren)
    {
        const oldChildren = this.children;

        for (let i = 0; i < oldChildren.length; ++i)
        {
            oldChildren[i].destroy(destroyChildren);
        }
    }

    this.children.length = 0;
};

/**
 * Updates the transform of this container.
 *
 * @param {boolean} [includeChildren] - Should we update the transforms of children ?
 */
Container.prototype.updateTransformWithParent = function (includeChildren)
{
    this._boundsID++;

    if (this.parent)
    {
        this.transform.updateTransform(this.parent.transform);
        // TODO: check render flags, how to process stuff here
        this.worldAlpha = this.alpha * this.parent.worldAlpha;
    }
    else
    {
        this.transform.updateTransformAsOrphan();
        this.worldAlpha = this.alpha;
    }

    if (includeChildren === true)
    {
        for (let i = 0, j = this.children.length; i < j; ++i)
        {
            const child = this.children[i];

            if (child.visible)
            {
                child.updateTransformWithParent(true);
            }
        }
    }
};

Container.prototype._removeChildAt = function (index, child)
{
    this.children.splice(index, 1);
    child.parent = null;
    this.onChildrenChange(index);
    child.emit('removed', this);

    return child;
};

Container.prototype.renderWebGLChildren = function (renderer)
{
    let i = 0;
    let len = this.children.length;

    while (i < len)
    {
        const child = this.children[i];

        if (child._toRemove)
        {
            len--;
            this._removeChildAt(i, child);
            continue;
        }
        child.renderWebGL(renderer);
        i++;
    }
};

Container.prototype.renderCanvasChildren = function (renderer)
{
    let i = 0;
    let len = this.children.length;

    while (i < len)
    {
        const child = this.children[i];

        if (child._toRemove)
        {
            len--;
            this._removeChildAt(i, child);
            continue;
        }
        child.renderCanvas(renderer);
        i++;
    }
};
<commit_msg>update the options of removeAllChildren<commit_after>import * as core from '../../core';

const Container = core.Container;

/**
 * A fast way to removes all children directly.
 *
 * @param {object|boolean} [options] - Options parameter.
 *     If the child is a sprite, see `options` of Sprite's destroy method.
 *        @see PIXI.Sprite#destroy
 *     If the child is a container, see `options` of Container's destroy method.
 *        @see PIXI.Container#destroy
 */
Container.prototype.removeAllChildren = function (options)
{
    if (options)
    {
        const oldChildren = this.children;

        for (let i = 0; i < oldChildren.length; ++i)
        {
            oldChildren[i].destroy(options);
        }
    }

    this.children.length = 0;
};

/**
 * Updates the transform of this container.
 *
 * @param {boolean} [includeChildren] - Should we update the transforms of children ?
 */
Container.prototype.updateTransformWithParent = function (includeChildren)
{
    this._boundsID++;

    if (this.parent)
    {
        this.transform.updateTransform(this.parent.transform);
        // TODO: check render flags, how to process stuff here
        this.worldAlpha = this.alpha * this.parent.worldAlpha;
    }
    else
    {
        this.transform.updateTransformAsOrphan();
        this.worldAlpha = this.alpha;
    }

    if (includeChildren === true)
    {
        for (let i = 0, j = this.children.length; i < j; ++i)
        {
            const child = this.children[i];

            if (child.visible)
            {
                child.updateTransformWithParent(true);
            }
        }
    }
};

Container.prototype._removeChildAt = function (index, child)
{
    this.children.splice(index, 1);
    child.parent = null;
    this.onChildrenChange(index);
    child.emit('removed', this);

    return child;
};

Container.prototype.renderWebGLChildren = function (renderer)
{
    let i = 0;
    let len = this.children.length;

    while (i < len)
    {
        const child = this.children[i];

        if (child._toRemove)
        {
            len--;
            this._removeChildAt(i, child);
            continue;
        }
        child.renderWebGL(renderer);
        i++;
    }
};

Container.prototype.renderCanvasChildren = function (renderer)
{
    let i = 0;
    let len = this.children.length;

    while (i < len)
    {
        const child = this.children[i];

        if (child._toRemove)
        {
            len--;
            this._removeChildAt(i, child);
            continue;
        }
        child.renderCanvas(renderer);
        i++;
    }
};
<|endoftext|>