<commit_before>// @flow
import * as React from 'react'
import {globalStyles, globalColors, globalMargins, isMobile} from '../styles'
import {Avatar, Box, ClickableBox, Icon, Text} from '../common-adapters'
import HeaderConnector from './header-container'

type FolderHeaderProps = {
  items: Array<{
    name: string,
    path: string,
  }>,
  isTeamPath: boolean,
  onOpenBreadcrumb: (path: string) => void,
}

const FolderHeader = HeaderConnector(({items, isTeamPath, onOpenBreadcrumb}: FolderHeaderProps) => (
  <Box>
    {items.length === 1 ? (
      <Box style={folderHeaderStyleRoot}>
        <Text type="BodyBig">Keybase Files</Text>
      </Box>
    ) : (
      <Box style={folderHeaderStyleTree}>
        {items.length < 4 &&
          items.map((i, idx) => (
            <Box key={i.path} style={folderBreadcrumbStyle}>
              {idx !== 0 && <Icon type="iconfont-back" style={iconStyle} />}
              {idx === 2 &&
                isTeamPath && <Avatar size={12} teamname={i.name} isTeam={true} style={styleTeamAvatar} />}
              {idx === items.length - 1 ? (
                <Text type="BodyBig">{i.name}</Text>
              ) : (
                <ClickableBox onClick={() => onOpenBreadcrumb(i.path)}>
                  <Text type="BodySmallSemibold" style={styleParentBreadcrumb}>
                    {i.name}
                  </Text>
                </ClickableBox>
              )}
            </Box>
          ))}
      </Box>
    )}
  </Box>
))

const stylesCommonRow = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  borderBottomColor: globalColors.black_05,
  borderBottomWidth: 1,
  minHeight: isMobile ? 64 : 48,
}

const folderHeaderStyleRoot = {
  ...stylesCommonRow,
  alignItems: 'center',
  justifyContent: 'center',
}

const folderHeaderStyleTree = {
  ...stylesCommonRow,
  alignItems: 'center',
  paddingLeft: 16,
  paddingRight: 16,
}

const folderBreadcrumbStyle = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  paddingLeft: 0,
  paddingRight: 0,
}

const styleParentBreadcrumb = {color: globalColors.black_60}

const iconStyle = {
  fontSize: 11,
  marginLeft: globalMargins.xtiny,
  marginRight: globalMargins.xtiny,
}

const styleTeamAvatar = {
  marginRight: globalMargins.xtiny,
}

export default FolderHeader
<commit_msg>fs: Expand breadcrumb limit for testing (#10485)<commit_after>// @flow
import * as React from 'react'
import {globalStyles, globalColors, globalMargins, isMobile} from '../styles'
import {Avatar, Box, ClickableBox, Icon, Text} from '../common-adapters'
import HeaderConnector from './header-container'

type FolderHeaderProps = {
  items: Array<{
    name: string,
    path: string,
  }>,
  isTeamPath: boolean,
  onOpenBreadcrumb: (path: string) => void,
}

const FolderHeader = HeaderConnector(({items, isTeamPath, onOpenBreadcrumb}: FolderHeaderProps) => (
  <Box>
    {items.length === 1 ? (
      <Box style={folderHeaderStyleRoot}>
        <Text type="BodyBig">Keybase Files</Text>
      </Box>
    ) : (
      <Box style={folderHeaderStyleTree}>
        {items.length < 10 &&
          items.map((i, idx) => (
            <Box key={i.path} style={folderBreadcrumbStyle}>
              {idx !== 0 && <Icon type="iconfont-back" style={iconStyle} />}
              {idx === 2 &&
                isTeamPath && <Avatar size={12} teamname={i.name} isTeam={true} style={styleTeamAvatar} />}
              {idx === items.length - 1 ? (
                <Text type="BodyBig">{i.name}</Text>
              ) : (
                <ClickableBox onClick={() => onOpenBreadcrumb(i.path)}>
                  <Text type="BodySmallSemibold" style={styleParentBreadcrumb}>
                    {i.name}
                  </Text>
                </ClickableBox>
              )}
            </Box>
          ))}
      </Box>
    )}
  </Box>
))

const stylesCommonRow = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  borderBottomColor: globalColors.black_05,
  borderBottomWidth: 1,
  minHeight: isMobile ? 64 : 48,
}

const folderHeaderStyleRoot = {
  ...stylesCommonRow,
  alignItems: 'center',
  justifyContent: 'center',
}

const folderHeaderStyleTree = {
  ...stylesCommonRow,
  alignItems: 'center',
  paddingLeft: 16,
  paddingRight: 16,
}

const folderBreadcrumbStyle = {
  ...globalStyles.flexBoxRow,
  alignItems: 'center',
  paddingLeft: 0,
  paddingRight: 0,
}

const styleParentBreadcrumb = {color: globalColors.black_60}

const iconStyle = {
  fontSize: 11,
  marginLeft: globalMargins.xtiny,
  marginRight: globalMargins.xtiny,
}

const styleTeamAvatar = {
  marginRight: globalMargins.xtiny,
}

export default FolderHeader
<|endoftext|>


<commit_before>var FieldType = require('../Type');
var moment = require('moment');
var util = require('util');
var utils = require('keystone-utils');

/**
 * Date FieldType Constructor
 * @extends Field
 * @api public
 */
function datearray (list, path, options) {
	this._nativeType = [Date];
	this._defaultSize = 'medium';
	this._underscoreMethods = ['format'];
	this._properties = ['formatString'];
	this.parseFormatString = options.parseFormat || 'YYYY-MM-DD';
	this.formatString = (options.format === false) ? false : (options.format || 'Do MMM YYYY');
	if (this.formatString && typeof this.formatString !== 'string') {
		throw new Error('FieldType.Date: options.format must be a string.');
	}
	this.separator = options.separator || ' | ';
	datearray.super_.call(this, list, path, options);
}
util.inherits(datearray, FieldType);

/**
 * Formats the field value
 */
datearray.prototype.format = function (item, format, separator) {
	var value = item.get(this.path);
	if (format || this.formatString) {
		value = value.map(function (d) {
			return moment(d).format(format || this._formatString);
		});
	}
	return value.join(separator || this.separator);
};

/**
 * Asynchronously confirms that the provided value is valid
 */
datearray.prototype.validateInput = function (data, callback) {
	var value = this.getValueFromData(data);
	var result = true;
	if (value !== undefined && value !== null && value !== '') {
		if (!Array.isArray(value)) {
			value = [value];
		}
		for (var i = 0; i < value.length; i++) {
			var currentValue;
			// If we pass it an epoch, parse it without the format string
			if (typeof value[i] === 'number') {
				currentValue = moment(value[i]);
			} else {
				currentValue = moment(value[i], this.parseFormatString);
			}
			// If moment does not think it's a valid date, invalidate
			if (!currentValue.isValid()) {
				result = false;
				break;
			}
		}
	}
	utils.defer(callback, result);
};

/**
 * Asynchronously confirms that the a value is present
 */
datearray.prototype.validateRequiredInput = function (item, data, callback) {
	var value = this.getValueFromData(data);
	var result = false;
	// If the field is undefined but has a value saved already, validate
	if (value === undefined) {
		if (item.get(this.path) && item.get(this.path).length) {
			result = true;
		}
	}
	if (typeof value === 'string' || typeof value === 'number') {
		if (moment(value).isValid()) {
			result = true;
		}
	// If it's an array of only dates and/or dateify-able data, validate
	} else if (Array.isArray(value)) {
		var invalidContent = false;
		for (var i = 0; i < value.length; i++) {
			var currentValue;
			// If we pass it an epoch, parse it without the format string
			if (typeof value[i] === 'number') {
				currentValue = moment(value[i]);
			} else {
				currentValue = moment(value[i], this.parseFormatString);
			}
			// If even a single item is not a valid date, invalidate
			if (!currentValue.isValid()) {
				invalidContent = true;
				break;
			}
		}
		if (invalidContent === false) {
			result = true;
		}
	}
	utils.defer(callback, result);
};

/**
 * Checks that a valid array of dates has been provided in a data object
 * An empty value clears the stored value and is considered valid
 *
 * Deprecated
 */
datearray.prototype.inputIsValid = function (data, required, item) {

	var value = this.getValueFromData(data);
	var parseFormatString = this.parseFormatString;

	if (typeof value === 'string') {
		if (!moment(value, parseFormatString).isValid()) {
			return false;
		}
		value = [value];
	}

	if (required) {
		if (value === undefined && item && item.get(this.path) && item.get(this.path).length) {
			return true;
		}
		if (value === undefined || !Array.isArray(value)) {
			return false;
		}
		if (Array.isArray(value) && !value.length) {
			return false;
		}
	}

	if (Array.isArray(value)) {
		// filter out empty fields
		value = value.filter(function (date) {
			return date.trim() !== '';
		});
		// if there are no values left, and requried is true, return false
		if (required && !value.length) {
			return false;
		}
		// if any date in the array is invalid, return false
		if (value.some(function (dateValue) { return !moment(dateValue, parseFormatString).isValid(); })) {
			return false;
		}
	}

	return (value === undefined || Array.isArray(value));

};

/**
 * Updates the value for this field in the item from a data object
 */
datearray.prototype.updateItem = function (item, data, callback) {

	var value = this.getValueFromData(data);

	if (value !== undefined) {
		if (Array.isArray(value)) {
			// Only save valid dates
			value = value.filter(function (date) {
				return moment(date).isValid();
			});
		}
		if (value === null) {
			value = [];
		}
		if (typeof value === 'string') {
			if (moment(value).isValid()) {
				value = [value];
			}
		}
		if (Array.isArray(value)) {
			item.set(this.path, value);
		}
	}

	process.nextTick(callback);
};

/* Export Field Type */
module.exports = datearray;
<commit_msg>Fix invalid option error message of DateArray type<commit_after>var FieldType = require('../Type');
var moment = require('moment');
var util = require('util');
var utils = require('keystone-utils');

/**
 * Date FieldType Constructor
 * @extends Field
 * @api public
 */
function datearray (list, path, options) {
	this._nativeType = [Date];
	this._defaultSize = 'medium';
	this._underscoreMethods = ['format'];
	this._properties = ['formatString'];
	this.parseFormatString = options.parseFormat || 'YYYY-MM-DD';
	this.formatString = (options.format === false) ? false : (options.format || 'Do MMM YYYY');
	if (this.formatString && typeof this.formatString !== 'string') {
		throw new Error('FieldType.DateArray: options.format must be a string.');
	}
	this.separator = options.separator || ' | ';
	datearray.super_.call(this, list, path, options);
}
util.inherits(datearray, FieldType);

/**
 * Formats the field value
 */
datearray.prototype.format = function (item, format, separator) {
	var value = item.get(this.path);
	if (format || this.formatString) {
		value = value.map(function (d) {
			return moment(d).format(format || this._formatString);
		});
	}
	return value.join(separator || this.separator);
};

/**
 * Asynchronously confirms that the provided value is valid
 */
datearray.prototype.validateInput = function (data, callback) {
	var value = this.getValueFromData(data);
	var result = true;
	if (value !== undefined && value !== null && value !== '') {
		if (!Array.isArray(value)) {
			value = [value];
		}
		for (var i = 0; i < value.length; i++) {
			var currentValue;
			// If we pass it an epoch, parse it without the format string
			if (typeof value[i] === 'number') {
				currentValue = moment(value[i]);
			} else {
				currentValue = moment(value[i], this.parseFormatString);
			}
			// If moment does not think it's a valid date, invalidate
			if (!currentValue.isValid()) {
				result = false;
				break;
			}
		}
	}
	utils.defer(callback, result);
};

/**
 * Asynchronously confirms that the a value is present
 */
datearray.prototype.validateRequiredInput = function (item, data, callback) {
	var value = this.getValueFromData(data);
	var result = false;
	// If the field is undefined but has a value saved already, validate
	if (value === undefined) {
		if (item.get(this.path) && item.get(this.path).length) {
			result = true;
		}
	}
	if (typeof value === 'string' || typeof value === 'number') {
		if (moment(value).isValid()) {
			result = true;
		}
	// If it's an array of only dates and/or dateify-able data, validate
	} else if (Array.isArray(value)) {
		var invalidContent = false;
		for (var i = 0; i < value.length; i++) {
			var currentValue;
			// If we pass it an epoch, parse it without the format string
			if (typeof value[i] === 'number') {
				currentValue = moment(value[i]);
			} else {
				currentValue = moment(value[i], this.parseFormatString);
			}
			// If even a single item is not a valid date, invalidate
			if (!currentValue.isValid()) {
				invalidContent = true;
				break;
			}
		}
		if (invalidContent === false) {
			result = true;
		}
	}
	utils.defer(callback, result);
};

/**
 * Checks that a valid array of dates has been provided in a data object
 * An empty value clears the stored value and is considered valid
 *
 * Deprecated
 */
datearray.prototype.inputIsValid = function (data, required, item) {

	var value = this.getValueFromData(data);
	var parseFormatString = this.parseFormatString;

	if (typeof value === 'string') {
		if (!moment(value, parseFormatString).isValid()) {
			return false;
		}
		value = [value];
	}

	if (required) {
		if (value === undefined && item && item.get(this.path) && item.get(this.path).length) {
			return true;
		}
		if (value === undefined || !Array.isArray(value)) {
			return false;
		}
		if (Array.isArray(value) && !value.length) {
			return false;
		}
	}

	if (Array.isArray(value)) {
		// filter out empty fields
		value = value.filter(function (date) {
			return date.trim() !== '';
		});
		// if there are no values left, and requried is true, return false
		if (required && !value.length) {
			return false;
		}
		// if any date in the array is invalid, return false
		if (value.some(function (dateValue) { return !moment(dateValue, parseFormatString).isValid(); })) {
			return false;
		}
	}

	return (value === undefined || Array.isArray(value));

};

/**
 * Updates the value for this field in the item from a data object
 */
datearray.prototype.updateItem = function (item, data, callback) {

	var value = this.getValueFromData(data);

	if (value !== undefined) {
		if (Array.isArray(value)) {
			// Only save valid dates
			value = value.filter(function (date) {
				return moment(date).isValid();
			});
		}
		if (value === null) {
			value = [];
		}
		if (typeof value === 'string') {
			if (moment(value).isValid()) {
				value = [value];
			}
		}
		if (Array.isArray(value)) {
			item.set(this.path, value);
		}
	}

	process.nextTick(callback);
};

/* Export Field Type */
module.exports = datearray;
<|endoftext|>


<commit_before>module.exports = {
  preview: '@docs-only-styles',
  collated: true,
  context: {
    tag: 'button',
    class: '',
    attributes: 'type="button"',
    text: 'I am a button'
  },
  variants: [
    {
      name: 'icon',
      context: {
        text: '',
        class: 'button--icon',
        iconId: 'arrow-left',
        iconTitle: 'Arrow left',
        iconClass: 'button__icon',
        attributes: 'type="button" aria-label="click to do something"'
      }
    },
    {
      name: 'secondary',
      context: {
        class: 'button--secondary',
        text: 'I am button secondary'
      }
    },
    {
      name: 'link',
      context: {
        tag: 'a',
        class: 'button--link',
        attributes: 'href="#" title="I am a link button"',
        text: 'I am a link button'
      }
    },
    {
      name: 'fluid',
      context: {
        class: 'button--fluid'
      }
    },

  ]
};
<commit_msg>#51777 - Changed wrong button component icon config<commit_after>module.exports = {
  preview: '@docs-only-styles',
  collated: true,
  context: {
    tag: 'button',
    class: '',
    attributes: 'type="button"',
    text: 'I am a button'
  },
  variants: [
    {
      name: 'icon',
      context: {
        text: '',
        class: 'button--icon',
        icon: {
          iconId: 'arrow-left',
          iconTitle: 'Arrow left',
          class: 'button__icon',
        },
        attributes: 'type="button" aria-label="click to do something"'
      }
    },
    {
      name: 'secondary',
      context: {
        class: 'button--secondary',
        text: 'I am button secondary'
      }
    },
    {
      name: 'link',
      context: {
        tag: 'a',
        class: 'button--link',
        attributes: 'href="#" title="I am a link button"',
        text: 'I am a link button'
      }
    },
    {
      name: 'fluid',
      context: {
        class: 'button--fluid'
      }
    },

  ]
};
<|endoftext|>


<commit_before>import EndpointsMixin from './endpoints-mixin';

const events = require('events');
const safeStringify = require('fast-safe-stringify');

import {
  ReceiveEndpoint,
  SendEndpointDefault,
  ReceiveEndpointDefault
} from 'kronos-endpoint';

import {
  defaultLogLevels,
  defineLoggerMethods,
  defineLogLevelProperties,
  LogLevelMixin,
  makeLogEvent
} from 'loglevel-mixin';

import {
  prepareActions,
  defineActionMethods,
  StateTransitionMixin,
  defineStateTransitionProperties
} from 'statetransition-mixin';

import {
  createAttributes,
  getAttribute,
  setAttributes
} from 'model-attributes';

const actions = prepareActions({
  start: {
    stopped: {
      target: 'running',
      during: 'starting',
      rejected: 'failed',
      timeout: 5000
    }
  },
  restart: {
    stopped: {
      target: 'running',
      during: 'starting',
      rejected: 'failed',
      timeout: 5000
    },
    running: {
      target: 'running',
      during: 'restarting',
      timeout: 5000
    }
  },
  stop: {
    running: {
      target: 'stopped',
      during: 'stopping',
      rejected: 'failed',
      timeout: 5000
    },
    starting: {
      target: 'stopped',
      during: 'stopping',
      rejected: 'failed',
      timeout: 5000
    },
    failed: {
      target: 'stopped',
      during: 'stopping',
      rejected: 'failed',
      timeout: 1000
    }
  }
});

/**
 * Meta information for the config attributes.
 * - default optional default value of the attribute
 * - needsRestart optional modification requires a service restart
 * - setter(newValue,attrribute) optional function to be used if simple value assignment is not enough
 * The Service class only defines the logLevel attribute
 */
const _ca = createAttributes({
  logLevel: {
    description: `logging level one of: ${Object.keys(defaultLogLevels)}`,
    default: defaultLogLevels.info,
    type: 'string',
    setter(newValue) {
      if (newValue !== undefined) {
        const l = defaultLogLevels[newValue];
        if (l !== undefined) {
          this.logLevel = l;
          return true;
        }
      }
      return false;
    },
    getter() {
      return this.logLevel.name;
    }
  },
  timeout: {
    attributes: {
      start: {
        description: 'service start timeout',
        type: 'duration',
        default: 5
      }
    }
  }
});

const dummyLogReceiver = new ReceiveEndpoint('logReceiver', {
  endpointIdentifier(ep) {
    return undefined; // prevent target;
  }
});

dummyLogReceiver.receive = entry => {
  if (entry.severity === 'error') {
    console.error(safeStringify(entry));
  } else {
    console.log(safeStringify(entry));
  }
  return Promise.resolve();
};

/**
 * Service
 * The initial state is 'stopped'
 * All services have at least two endpoints:
 * - log out: log events
 * - config in: configuration request
 */
export default class Service extends EndpointsMixin(
  StateTransitionMixin(LogLevelMixin(events), actions, 'stopped')
) {
  static get description() {
    return 'This service is the base class for service implementations';
  }

  static get name() {
    return 'service';
  }

  static get configurationAttributes() {
    return _ca;
  }

  /**
	 * @param {object} config
	 * @param {object} [owner]
	 */
  constructor(config, owner) {
    super();

    if (owner !== undefined) {
      Object.defineProperty(this, 'owner', {
        value: owner
      });
    }

    if (config === undefined) {
      config = {};
    } else {
      if (config.name !== undefined) {
        Object.defineProperty(this, 'name', {
          value: config.name
        });
      }
    }

    Object.defineProperty(this, 'config', {
      value: {}
    });

    defineLogLevelProperties(
      this,
      defaultLogLevels,
      config.logLevel !== undefined
        ? defaultLogLevels[config.logLevel] || defaultLogLevels.info
        : defaultLogLevels.info
    );

    defineStateTransitionProperties(this, actions, 'stopped');

    this.addEndpoint(
      new SendEndpointDefault('log', this)
    ).connected = dummyLogReceiver;
    this.addEndpoint(
      new ReceiveEndpointDefault('config', this)
    ).receive = request => this.configure(request);
    this.addEndpoint(
      new ReceiveEndpointDefault('command', this)
    ).receive = request => this.execute(request);

    this._configure(config);
  }

  get configurationAttributes() {
    return this.constructor.configurationAttributes;
  }

  get type() {
    return this.constructor.name;
  }

  get description() {
    return this.constructor.description;
  }

  async execute(command) {
    // TODO admin query language ?
    // for now we return the config
    const result = {
      name: this.name,
      state: this.state
    };

    const ca = this.configurationAttributes;

    for (const an in ca) {
      getAttribute(this, ca, an);
    }

    return result;
  }

  /**
	 * Called when the service state changes.
	 * Emits a serviceStateChanged event to the owner
	 * @param {string} oldState
	 * @param {string} newState
	 */
  stateChanged(oldState, newState) {
    this.owner.emit('serviceStateChanged', this, oldState, newState);
    this.trace({
      message: 'transitioned',
      from: oldState,
      to: newState
    });
  }

  stateTransitionRejection(rejected, newState) {
    const p = super.stateTransitionRejection(rejected, newState);
    this.error({
      message: 'transition rejected',
      rejected: rejected,
      newState: newState
    });
    return p;
  }

  /**
	 * Called when state transition is not allowed
	 * @param {string} action originating action name
	 * @return {Promise} rejecting with an Error
	 */
  rejectWrongState(action) {
    return Promise.reject(
      new Error(`Can't ${action} ${this} in ${this.state} state`)
    );
  }

  timeoutForTransition(transition) {
    if (transition.name.startsWith('start')) {
      return this.timeout.start * 1000;
    }

    return super.timeoutForTransition(transition);
  }

  /**
	 * default implementation does a _stop() and a _start()
	 * @return {Promise}
	 */
  async _restart() {
    await this._stop();
    return this._start();
  }

  /**
	 * Restarts if in running mode
	 * Otherwise does nothing
	 * @returns {Promise} resolves when restart is done (or immediate if no restart triggered)
	 */
  async restartIfRunning() {
    if (this.state === 'running') {
      return this.restart();
    }
  }

  /**
	 * Returns the string representation of this step
	 * @return {string} human readable name
	 */
  toString() {
    return `${this.name}: ${this.state}`;
  }

  toJSON() {
    return this.toJSONWithOptions({
      includeRuntimeInfo: false,
      includeDefaults: false,
      includeName: true,
      includeConfig: false
    });
  }

  /**
	 * Deliver json representation
	 * @param {object} [options]
	 *  with the following flags:
	 *    includeRuntimeInfo - include runtime informtion like state
	 *    includeDefaults - also include default endpoints
	 *    includeName - name of the service
	 *    includeConfig - also include config attributes
	 * @return {object} json representation
	 */
  toJSONWithOptions(options = {}) {
    const json = {
      type: this.type,
      endpoints: {}
    };

    if (options.includeName) {
      json.name = this.name;
    }

    if (options.includeRuntimeInfo) {
      json.state = this.state;
      json.logLevel = this.logLevel;
    }

    if (options.includeConfig) {
      const ca = this.configurationAttributes;
      Object.keys(ca).forEach(name => (json[name] = this[name]));
    }

    for (const endpointName in this.endpoints) {
      const ep = this.endpoints[endpointName];
      if (ep.isDefault) {
        if (options.includeDefaults) {
          json.endpoints[endpointName] = ep.toJSON();
        }
      } else {
        json.endpoints[endpointName] = ep.toJSON();
      }
    }

    return json;
  }

  /**
	 * defaults to the type
	 */
  get name() {
    return this.type;
  }

  /**
	 * Should we start when beeing registered
	 * @return {boolean} false
	 */
  get autostart() {
    return false;
  }

  /**
	 * Takes attribute values from config parameters
	 * and copies them over to the object.
	 * Copying is done according to configurationAttributes
	 * Which means we loop over all configuration attributes
	 * and then for each attribute decide if we use the default, call a setter function
	 * or simply assign the attribute value
	 * @param {object} config
	 * @return {Set} of modified attributes
	 */
  _configure(config) {
    const modified = new Set();
    setAttributes(
      this,
      this.configurationAttributes,
      config,
      (ca, path, value) => {
        this.trace({
          message: 'config',
          attribute: path,
          value: value
        });
        modified.add(ca);
      }
    );
    return modified;
  }

  /**
	 * Use new configuration.
	 * Internally calls _configure(config) as the constructor does
	 * If attribute with needsRestart are touched the restartIfRunning method
	 * will be called
	 * @param {pbject} new config
	 * @return {Promise} fillfills when config is applied
	 */
  async configure(config) {
    const modified = this._configure(config);

    for (const a of modified) {
      if (a.needsRestart) {
        return this.restartIfRunning();
      }
    }
  }

  /**
	 * Adds service name to the log event
	 * @param {string} level the log level
	 * @param {object} arg log content
	 */
  log(level, arg) {
    this.endpoints.log.receive(
      makeLogEvent(level, arg, {
        service: this.name
      })
    );
  }

  /**
	 * @return {string} separator between service name and endpoint name
	 **/
  get endpointParentSeparator() {
    return ':';
  }
}

defineLoggerMethods(Service.prototype);
defineActionMethods(Service.prototype, actions);
<commit_msg>docs: config timeout<commit_after>import EndpointsMixin from './endpoints-mixin';

const events = require('events');
const safeStringify = require('fast-safe-stringify');

import {
  ReceiveEndpoint,
  SendEndpointDefault,
  ReceiveEndpointDefault
} from 'kronos-endpoint';

import {
  defaultLogLevels,
  defineLoggerMethods,
  defineLogLevelProperties,
  LogLevelMixin,
  makeLogEvent
} from 'loglevel-mixin';

import {
  prepareActions,
  defineActionMethods,
  StateTransitionMixin,
  defineStateTransitionProperties
} from 'statetransition-mixin';

import {
  createAttributes,
  getAttribute,
  setAttributes
} from 'model-attributes';

const actions = prepareActions({
  start: {
    stopped: {
      target: 'running',
      during: 'starting',
      rejected: 'failed',
      timeout: 5000
    }
  },
  restart: {
    stopped: {
      target: 'running',
      during: 'starting',
      rejected: 'failed',
      timeout: 5000
    },
    running: {
      target: 'running',
      during: 'restarting',
      timeout: 5000
    }
  },
  stop: {
    running: {
      target: 'stopped',
      during: 'stopping',
      rejected: 'failed',
      timeout: 5000
    },
    starting: {
      target: 'stopped',
      during: 'stopping',
      rejected: 'failed',
      timeout: 5000
    },
    failed: {
      target: 'stopped',
      during: 'stopping',
      rejected: 'failed',
      timeout: 1000
    }
  }
});

/**
 * Meta information for the config attributes.
 * - default optional default value of the attribute
 * - needsRestart optional modification requires a service restart
 * - setter(newValue,attrribute) optional function to be used if simple value assignment is not enough
 * The Service class only defines the logLevel, ans start timeout attribute
 */
const _ca = createAttributes({
  logLevel: {
    description: `logging level one of: ${Object.keys(defaultLogLevels)}`,
    default: defaultLogLevels.info,
    type: 'string',
    setter(newValue) {
      if (newValue !== undefined) {
        const l = defaultLogLevels[newValue];
        if (l !== undefined) {
          this.logLevel = l;
          return true;
        }
      }
      return false;
    },
    getter() {
      return this.logLevel.name;
    }
  },
  timeout: {
    attributes: {
      start: {
        description: 'service start timeout',
        type: 'duration',
        default: 5
      }
    }
  }
});

const dummyLogReceiver = new ReceiveEndpoint('logReceiver', {
  endpointIdentifier(ep) {
    return undefined; // prevent target;
  }
});

dummyLogReceiver.receive = entry => {
  if (entry.severity === 'error') {
    console.error(safeStringify(entry));
  } else {
    console.log(safeStringify(entry));
  }
  return Promise.resolve();
};

/**
 * Service
 * The initial state is 'stopped'
 * All services have at least two endpoints:
 * - log out: log events
 * - config in: configuration request
 */
export default class Service extends EndpointsMixin(
  StateTransitionMixin(LogLevelMixin(events), actions, 'stopped')
) {
  static get description() {
    return 'This service is the base class for service implementations';
  }

  static get name() {
    return 'service';
  }

  static get configurationAttributes() {
    return _ca;
  }

  /**
	 * @param {object} config
	 * @param {object} [owner]
	 */
  constructor(config, owner) {
    super();

    if (owner !== undefined) {
      Object.defineProperty(this, 'owner', {
        value: owner
      });
    }

    if (config === undefined) {
      config = {};
    } else {
      if (config.name !== undefined) {
        Object.defineProperty(this, 'name', {
          value: config.name
        });
      }
    }

    Object.defineProperty(this, 'config', {
      value: {}
    });

    defineLogLevelProperties(
      this,
      defaultLogLevels,
      config.logLevel !== undefined
        ? defaultLogLevels[config.logLevel] || defaultLogLevels.info
        : defaultLogLevels.info
    );

    defineStateTransitionProperties(this, actions, 'stopped');

    this.addEndpoint(
      new SendEndpointDefault('log', this)
    ).connected = dummyLogReceiver;
    this.addEndpoint(
      new ReceiveEndpointDefault('config', this)
    ).receive = request => this.configure(request);
    this.addEndpoint(
      new ReceiveEndpointDefault('command', this)
    ).receive = request => this.execute(request);

    this._configure(config);
  }

  get configurationAttributes() {
    return this.constructor.configurationAttributes;
  }

  get type() {
    return this.constructor.name;
  }

  get description() {
    return this.constructor.description;
  }

  async execute(command) {
    // TODO admin query language ?
    // for now we return the config
    const result = {
      name: this.name,
      state: this.state
    };

    const ca = this.configurationAttributes;

    for (const an in ca) {
      getAttribute(this, ca, an);
    }

    return result;
  }

  /**
	 * Called when the service state changes.
	 * Emits a serviceStateChanged event to the owner
	 * @param {string} oldState
	 * @param {string} newState
	 */
  stateChanged(oldState, newState) {
    this.owner.emit('serviceStateChanged', this, oldState, newState);
    this.trace({
      message: 'transitioned',
      from: oldState,
      to: newState
    });
  }

  stateTransitionRejection(rejected, newState) {
    const p = super.stateTransitionRejection(rejected, newState);
    this.error({
      message: 'transition rejected',
      rejected: rejected,
      newState: newState
    });
    return p;
  }

  /**
	 * Called when state transition is not allowed
	 * @param {string} action originating action name
	 * @return {Promise} rejecting with an Error
	 */
  rejectWrongState(action) {
    return Promise.reject(
      new Error(`Can't ${action} ${this} in ${this.state} state`)
    );
  }

  timeoutForTransition(transition) {
    if (transition.name.startsWith('start')) {
      return this.timeout.start * 1000;
    }

    return super.timeoutForTransition(transition);
  }

  /**
	 * default implementation does a _stop() and a _start()
	 * @return {Promise}
	 */
  async _restart() {
    await this._stop();
    return this._start();
  }

  /**
	 * Restarts if in running mode
	 * Otherwise does nothing
	 * @returns {Promise} resolves when restart is done (or immediate if no restart triggered)
	 */
  async restartIfRunning() {
    if (this.state === 'running') {
      return this.restart();
    }
  }

  /**
	 * Returns the string representation of this step
	 * @return {string} human readable name
	 */
  toString() {
    return `${this.name}: ${this.state}`;
  }

  toJSON() {
    return this.toJSONWithOptions({
      includeRuntimeInfo: false,
      includeDefaults: false,
      includeName: true,
      includeConfig: false
    });
  }

  /**
	 * Deliver json representation
	 * @param {object} [options]
	 *  with the following flags:
	 *    includeRuntimeInfo - include runtime informtion like state
	 *    includeDefaults - also include default endpoints
	 *    includeName - name of the service
	 *    includeConfig - also include config attributes
	 * @return {object} json representation
	 */
  toJSONWithOptions(options = {}) {
    const json = {
      type: this.type,
      endpoints: {}
    };

    if (options.includeName) {
      json.name = this.name;
    }

    if (options.includeRuntimeInfo) {
      json.state = this.state;
      json.logLevel = this.logLevel;
    }

    if (options.includeConfig) {
      const ca = this.configurationAttributes;
      Object.keys(ca).forEach(name => (json[name] = this[name]));
    }

    for (const endpointName in this.endpoints) {
      const ep = this.endpoints[endpointName];
      if (ep.isDefault) {
        if (options.includeDefaults) {
          json.endpoints[endpointName] = ep.toJSON();
        }
      } else {
        json.endpoints[endpointName] = ep.toJSON();
      }
    }

    return json;
  }

  /**
	 * defaults to the type
	 */
  get name() {
    return this.type;
  }

  /**
	 * Should we start when beeing registered
	 * @return {boolean} false
	 */
  get autostart() {
    return false;
  }

  /**
	 * Takes attribute values from config parameters
	 * and copies them over to the object.
	 * Copying is done according to configurationAttributes
	 * Which means we loop over all configuration attributes
	 * and then for each attribute decide if we use the default, call a setter function
	 * or simply assign the attribute value
	 * @param {object} config
	 * @return {Set} of modified attributes
	 */
  _configure(config) {
    const modified = new Set();
    setAttributes(
      this,
      this.configurationAttributes,
      config,
      (ca, path, value) => {
        this.trace({
          message: 'config',
          attribute: path,
          value: value
        });
        modified.add(ca);
      }
    );
    return modified;
  }

  /**
	 * Use new configuration.
	 * Internally calls _configure(config) as the constructor does
	 * If attribute with needsRestart are touched the restartIfRunning method
	 * will be called
	 * @param {pbject} new config
	 * @return {Promise} fillfills when config is applied
	 */
  async configure(config) {
    const modified = this._configure(config);

    for (const a of modified) {
      if (a.needsRestart) {
        return this.restartIfRunning();
      }
    }
  }

  /**
	 * Adds service name to the log event
	 * @param {string} level the log level
	 * @param {object} arg log content
	 */
  log(level, arg) {
    this.endpoints.log.receive(
      makeLogEvent(level, arg, {
        service: this.name
      })
    );
  }

  /**
	 * @return {string} separator between service name and endpoint name
	 **/
  get endpointParentSeparator() {
    return ':';
  }
}

defineLoggerMethods(Service.prototype);
defineActionMethods(Service.prototype, actions);
<|endoftext|>


<commit_before>(function () {
    'use strict';

    const factory = function (Validator, utils) {
        // TODO rename all validators (add postfix "Validator")
        class Num extends Validator {

            constructor(data) {
                super(data);

                /**
                 * @type {RegExp}
                 */
                const numberReg = /[0-9]/;
                /**
                 * @type {HTMLInputElement}
                 */
                const input = this.$input.get(0);

                /**
                 * @param {string} char
                 * @param {boolean} hasDot
                 * @return {boolean}
                 */
                const checkChar = (char, hasDot) => {
                    if (char === '.') {
                        return !hasDot;
                    } else {
                        return numberReg.test(char);
                    }
                };

                /**
                 * @type {boolean}
                 */
                const isRequired = input.hasAttribute('required');
                /**
                 * @type {boolean}
                 */
                const isInteger = input.hasAttribute('integer');
                /**
                 * @type {boolean}
                 */
                const hasMax = input.hasAttribute('max');
                /**
                 * @type {boolean}
                 */
                const hasMin = input.hasAttribute('min');
                /**
                 * @type {boolean}
                 */
                const includeRangeMin = input.hasAttribute('include-range-min');
                /**
                 * @type {boolean}
                 */
                const includeRangeMax = input.hasAttribute('include-range-max');
                /**
                 * @type {boolean}
                 */
                const hasPrecision = input.hasAttribute('precision');

                if (isInteger) {
                    this.registerValidator('integer', (modalValue) => {
                        return !modalValue || modalValue.round(0).eq(modalValue);
                    });
                } else if (hasPrecision) {
                    let precision;
                    this.registerValidator('precision', (modalValue) => {
                        return !modalValue || precision == null || modalValue.eq(modalValue.round(precision));
                    });
                    this.$scope.$watch(this.$attrs.precision, (value) => {
                        precision = Number(value) || 0;
                        this.validateByName('precision');
                    });
                }

                if (hasMin) {
                    let min;
                    if (includeRangeMin) {
                        this.registerValidator('min', (modalValue) => !min || !modalValue || modalValue.gte(min));
                    } else {
                        this.registerValidator('min', (modalValue) => !min || !modalValue || modalValue.gt(min));
                    }

                    this.$scope.$watch(this.$attrs.min, (value) => {
                        min = Num._toBigNumber(value);
                        this.validateByName('min');
                    });
                }

                if (hasMax) {
                    let max;
                    if (includeRangeMax) {
                        this.registerValidator('max', (modalValue) => !max || !modalValue || modalValue.lte(max));
                    } else {
                        this.registerValidator('max', (modalValue) => !max || !modalValue || modalValue.lt(max));
                    }

                    this.$scope.$watch(this.$attrs.max, (value) => {
                        max = Num._toBigNumber(value);
                        this.validateByName('max');
                    });
                }

                this.registerValidator('required', (modelValue) => {
                    return !isRequired || !!modelValue;
                });

                // TODO fix for ff backspace
                this.$input.on('keypress', (event) => {
                    function getChar(event) {
                        if (event.which === null) { // IE
                            if (event.keyCode < 32) return null; // спец. символ
                            return String.fromCharCode(event.keyCode);
                        }

                        if (event.which !== 0 && event.charCode !== 0) { // все кроме IE
                            if (event.which < 32) return null; // спец. символ
                            return String.fromCharCode(event.which); // остальные
                        }

                        return null; // спец. символ
                    }

                    const char = getChar(event);
                    if (!char || !checkChar(char, this.$input.val().indexOf('.') !== -1)) {
                        event.preventDefault();
                    }
                });

            }

            getParser() {
                return utils.parseNiceNumber;
            }

            static _toBigNumber(value) {
                if (!value) {
                    return new BigNumber(0);
                } else if (value instanceof BigNumber) {
                    return value;
                } else {
                    return new BigNumber(value);
                }
            }

        }

        return Num;
    };

    factory.$inject = ['Validator', 'utils'];

    angular.module('app.utils').factory('Num', factory);
})();
<commit_msg>CLIENT-132: fix ff numbers<commit_after>(function () {
    'use strict';

    const factory = function (Validator, utils) {
        // TODO rename all validators (add postfix "Validator")
        class Num extends Validator {

            constructor(data) {
                super(data);

                /**
                 * @type {RegExp}
                 */
                const numberReg = /[0-9]/;
                /**
                 * @type {HTMLInputElement}
                 */
                const input = this.$input.get(0);

                /**
                 * @param {string} char
                 * @param {boolean} hasDot
                 * @return {boolean}
                 */
                const checkChar = (char, hasDot) => {
                    if (char === '.') {
                        return !hasDot;
                    } else {
                        return numberReg.test(char);
                    }
                };

                /**
                 * @type {boolean}
                 */
                const isRequired = input.hasAttribute('required');
                /**
                 * @type {boolean}
                 */
                const isInteger = input.hasAttribute('integer');
                /**
                 * @type {boolean}
                 */
                const hasMax = input.hasAttribute('max');
                /**
                 * @type {boolean}
                 */
                const hasMin = input.hasAttribute('min');
                /**
                 * @type {boolean}
                 */
                const includeRangeMin = input.hasAttribute('include-range-min');
                /**
                 * @type {boolean}
                 */
                const includeRangeMax = input.hasAttribute('include-range-max');
                /**
                 * @type {boolean}
                 */
                const hasPrecision = input.hasAttribute('precision');

                if (isInteger) {
                    this.registerValidator('integer', (modalValue) => {
                        return !modalValue || modalValue.round(0).eq(modalValue);
                    });
                } else if (hasPrecision) {
                    let precision;
                    this.registerValidator('precision', (modalValue) => {
                        return !modalValue || precision == null || modalValue.eq(modalValue.round(precision));
                    });
                    this.$scope.$watch(this.$attrs.precision, (value) => {
                        precision = Number(value) || 0;
                        this.validateByName('precision');
                    });
                }

                if (hasMin) {
                    let min;
                    if (includeRangeMin) {
                        this.registerValidator('min', (modalValue) => !min || !modalValue || modalValue.gte(min));
                    } else {
                        this.registerValidator('min', (modalValue) => !min || !modalValue || modalValue.gt(min));
                    }

                    this.$scope.$watch(this.$attrs.min, (value) => {
                        min = Num._toBigNumber(value);
                        this.validateByName('min');
                    });
                }

                if (hasMax) {
                    let max;
                    if (includeRangeMax) {
                        this.registerValidator('max', (modalValue) => !max || !modalValue || modalValue.lte(max));
                    } else {
                        this.registerValidator('max', (modalValue) => !max || !modalValue || modalValue.lt(max));
                    }

                    this.$scope.$watch(this.$attrs.max, (value) => {
                        max = Num._toBigNumber(value);
                        this.validateByName('max');
                    });
                }

                this.registerValidator('required', (modelValue) => {
                    return !isRequired || !!modelValue;
                });

                this.$input.on('keypress', (event) => {
                    function getChar(event) {
                        if (event.which === null) {
                            if (event.keyCode < 32) return null;
                            return String.fromCharCode(event.keyCode);
                        }

                        if (event.which !== 0 && event.charCode !== 0) {
                            if (event.which < 32) return null;
                            return String.fromCharCode(event.which);
                        }

                        return null;
                    }

                    const char = getChar(event);
                    if (event.keyCode !== 8) {
                        if (!char || !checkChar(char, this.$input.val().indexOf('.') !== -1)) {
                            event.preventDefault();
                        }
                    }
                });

            }

            getParser() {
                return utils.parseNiceNumber;
            }

            static _toBigNumber(value) {
                if (!value) {
                    return new BigNumber(0);
                } else if (value instanceof BigNumber) {
                    return value;
                } else {
                    return new BigNumber(value);
                }
            }

        }

        return Num;
    };

    factory.$inject = ['Validator', 'utils'];

    angular.module('app.utils').factory('Num', factory);
})();
<|endoftext|>


<commit_before>frappe.ui.form.on("Communication", {
	onload: function(frm) {
		if(frm.doc.content) {
			frm.doc.content = frappe.dom.remove_script_and_style(frm.doc.content);
		}
		frm.set_query("reference_doctype", function() {
			return {
				filters: {
					"issingle": 0,
					"istable": 0
				}
			}
		});
	},
	refresh: function(frm) {
		if(frm.is_new()) return;

		frm.convert_to_click && frm.set_convert_button();
		frm.subject_field = "subject";

		if(frm.doc.reference_doctype && frm.doc.reference_name) {
			frm.add_custom_button(__(frm.doc.reference_name), function() {
				frappe.set_route("Form", frm.doc.reference_doctype, frm.doc.reference_name);
			});
		} else {
			// if an unlinked communication, set email field
			if (frm.doc.sent_or_received==="Received") {
				frm.email_field = "sender";
			} else {
				frm.email_field = "recipients";
			}
		}

		if(frm.doc.status==="Open") {
			frm.add_custom_button(__("Close"), function() {
				frm.set_value("status", "Closed");
				frm.save();
			});
		} else if (frm.doc.status !== "Linked") {
			frm.add_custom_button(__("Reopen"), function() {
				frm.set_value("status", "Open");
				frm.save();
			});
		}

		frm.add_custom_button(__("Relink"), function() {
			frm.trigger('show_relink_dialog');
		});

		if(frm.doc.communication_type=="Communication"
			&& frm.doc.communication_medium == "Email"
			&& frm.doc.sent_or_received == "Received") {

			frm.add_custom_button(__("Reply"), function() {
				frm.trigger('reply');
			});

			frm.add_custom_button(__("Reply All"), function() {
				frm.trigger('reply_all');
			}, "Actions");

			frm.add_custom_button(__("Forward"), function() {
				frm.trigger('forward_mail');
			}, "Actions");

			frm.add_custom_button(__("Mark as {0}", [frm.doc.seen? "Unread": "Read"]), function() {
				frm.trigger('mark_as_read_unread');
			}, "Actions");

			frm.add_custom_button(__("Add Contact"), function() {
				frm.trigger('add_to_contact');
			}, "Actions");

			if(frm.doc.email_status != "Spam")
				frm.add_custom_button(__("Mark as Spam"), function() {
					frm.trigger('mark_as_spam');
				}, "Actions");

			if(frm.doc.email_status != "Trash") {
				frm.add_custom_button(__("Move To Trash"), function() {
					frm.trigger('move_to_trash');
				}, "Actions");
			}
		}

		if(frm.doc.communication_type=="Communication"
			&& frm.doc.communication_medium == "Phone"
			&& frm.doc.sent_or_received == "Received"){

			frm.add_custom_button(__("Add Contact"), function() {
				frm.trigger('add_to_contact');
			}, "Actions");
		}
	},

	show_relink_dialog: function(frm){
		var lib = "frappe.email";
		var d = new frappe.ui.Dialog ({
			title: __("Relink Communication"),
			fields: [{
				"fieldtype": "Link",
				"options": "DocType",
				"label": __("Reference Doctype"),
				"fieldname": "reference_doctype",
				"get_query": function() {return {"query": "frappe.email.get_communication_doctype"}}
			},
			{
				"fieldtype": "Dynamic Link",
				"options": "reference_doctype",
				"label": __("Reference Name"),
				"fieldname": "reference_name"
			}]
		});
		d.set_value("reference_doctype", frm.doc.reference_doctype);
		d.set_value("reference_name", frm.doc.reference_name);
		d.set_primary_action(__("Relink"), function () {
			var values = d.get_values();
			if (values) {
				frappe.confirm(
					__('Are you sure you want to relink this communication to {0}?', [values["reference_name"]]),
					function () {
						d.hide();
						frappe.call({
							method: "frappe.email.relink",
							args: {
								"name": frm.doc.name,
								"reference_doctype": values["reference_doctype"],
								"reference_name": values["reference_name"]
							},
							callback: function () {
								frm.refresh();
							}
						});
					},
					function () {
						frappe.show_alert('Document not Relinked')
					}
				);
			}
		});
		d.show();
	},

	mark_as_read_unread: function(frm) {
		var action = frm.doc.seen? "Unread": "Read";
		var flag = "(\\SEEN)";

		return frappe.call({
			method: "frappe.email.inbox.create_email_flag_queue",
			args: {
				'names': [frm.doc.name],
				'action': action,
				'flag': flag
			},
			freeze: true
		});
	},

	reply: function(frm) {
		var args = frm.events.get_mail_args(frm);
		$.extend(args, {
			subject: __("Re: {0}", [frm.doc.subject]),
			recipients: frm.doc.sender
		})

		new frappe.views.CommunicationComposer(args);
	},

	reply_all: function(frm) {
		var args = frm.events.get_mail_args(frm)
		$.extend(args, {
			subject: __("Res: {0}", [frm.doc.subject]),
			recipients: frm.doc.sender,
			cc: frm.doc.cc
		})
		new frappe.views.CommunicationComposer(args);
	},

	forward_mail: function(frm) {
		var args = frm.events.get_mail_args(frm)
		$.extend(args, {
			forward: true,
			subject: __("Fw: {0}", [frm.doc.subject]),
		})

		new frappe.views.CommunicationComposer(args);
	},

	get_mail_args: function(frm) {
		var sender_email_id = ""
		$.each(frappe.boot.email_accounts, function(idx, account) {
			if(account.email_account == frm.doc.email_account) {
				sender_email_id = account.email_id
				return
			}
		});

		return {
			frm: frm,
			doc: frm.doc,
			last_email: frm.doc,
			sender: sender_email_id,
			attachments: frm.doc.attachments
		}
	},

	add_to_contact: function(frm) {
		var me = this;
		var fullname = frm.doc.sender_full_name || ""

		var names = fullname.split(" ")
		var first_name = names[0]
		var last_name = names.length >= 2? names[names.length - 1]: ""

		frappe.route_options = {
			"email_id": frm.doc.sender || "",
			"first_name": first_name,
			"last_name": last_name,
			"mobile_no": frm.doc.phone_no || ""
		}
		frappe.new_doc("Contact")
	},

	mark_as_spam: function(frm) {
		frappe.call({
			method: "frappe.email.inbox.mark_as_spam",
			args: {
				communication: frm.doc.name,
				sender: frm.doc.sender
			},
			freeze: true,
			callback: function(r) {
				frappe.msgprint(__("Email has been marked as spam"))
			}
		})
	},

	move_to_trash: function(frm) {
		frappe.call({
			method: "frappe.email.inbox.mark_as_trash",
			args: {
				communication: frm.doc.name
			},
			freeze: true,
			callback: function(r) {
				frappe.msgprint(__("Email has been moved to trash"))
			}
		})
	}
});
<commit_msg>fix(Communication): Set content as read only<commit_after>frappe.ui.form.on("Communication", {
	onload: function(frm) {
		if(frm.doc.content) {
			frm.doc.content = frappe.dom.remove_script_and_style(frm.doc.content);
		}
		frm.set_query("reference_doctype", function() {
			return {
				filters: {
					"issingle": 0,
					"istable": 0
				}
			}
		});
	},
	refresh: function(frm) {
		if(frm.is_new()) return;

		frm.convert_to_click && frm.set_convert_button();
		frm.subject_field = "subject";

		// content field contains weird table html that does not render well in Quill
		// this field is not to be edited directly anyway, so setting it as read only
		frm.set_df_property('content', 'read_only', 1);

		if(frm.doc.reference_doctype && frm.doc.reference_name) {
			frm.add_custom_button(__(frm.doc.reference_name), function() {
				frappe.set_route("Form", frm.doc.reference_doctype, frm.doc.reference_name);
			});
		} else {
			// if an unlinked communication, set email field
			if (frm.doc.sent_or_received==="Received") {
				frm.email_field = "sender";
			} else {
				frm.email_field = "recipients";
			}
		}

		if(frm.doc.status==="Open") {
			frm.add_custom_button(__("Close"), function() {
				frm.set_value("status", "Closed");
				frm.save();
			});
		} else if (frm.doc.status !== "Linked") {
			frm.add_custom_button(__("Reopen"), function() {
				frm.set_value("status", "Open");
				frm.save();
			});
		}

		frm.add_custom_button(__("Relink"), function() {
			frm.trigger('show_relink_dialog');
		});

		if(frm.doc.communication_type=="Communication"
			&& frm.doc.communication_medium == "Email"
			&& frm.doc.sent_or_received == "Received") {

			frm.add_custom_button(__("Reply"), function() {
				frm.trigger('reply');
			});

			frm.add_custom_button(__("Reply All"), function() {
				frm.trigger('reply_all');
			}, "Actions");

			frm.add_custom_button(__("Forward"), function() {
				frm.trigger('forward_mail');
			}, "Actions");

			frm.add_custom_button(__("Mark as {0}", [frm.doc.seen? "Unread": "Read"]), function() {
				frm.trigger('mark_as_read_unread');
			}, "Actions");

			frm.add_custom_button(__("Add Contact"), function() {
				frm.trigger('add_to_contact');
			}, "Actions");

			if(frm.doc.email_status != "Spam")
				frm.add_custom_button(__("Mark as Spam"), function() {
					frm.trigger('mark_as_spam');
				}, "Actions");

			if(frm.doc.email_status != "Trash") {
				frm.add_custom_button(__("Move To Trash"), function() {
					frm.trigger('move_to_trash');
				}, "Actions");
			}
		}

		if(frm.doc.communication_type=="Communication"
			&& frm.doc.communication_medium == "Phone"
			&& frm.doc.sent_or_received == "Received"){

			frm.add_custom_button(__("Add Contact"), function() {
				frm.trigger('add_to_contact');
			}, "Actions");
		}
	},

	show_relink_dialog: function(frm){
		var lib = "frappe.email";
		var d = new frappe.ui.Dialog ({
			title: __("Relink Communication"),
			fields: [{
				"fieldtype": "Link",
				"options": "DocType",
				"label": __("Reference Doctype"),
				"fieldname": "reference_doctype",
				"get_query": function() {return {"query": "frappe.email.get_communication_doctype"}}
			},
			{
				"fieldtype": "Dynamic Link",
				"options": "reference_doctype",
				"label": __("Reference Name"),
				"fieldname": "reference_name"
			}]
		});
		d.set_value("reference_doctype", frm.doc.reference_doctype);
		d.set_value("reference_name", frm.doc.reference_name);
		d.set_primary_action(__("Relink"), function () {
			var values = d.get_values();
			if (values) {
				frappe.confirm(
					__('Are you sure you want to relink this communication to {0}?', [values["reference_name"]]),
					function () {
						d.hide();
						frappe.call({
							method: "frappe.email.relink",
							args: {
								"name": frm.doc.name,
								"reference_doctype": values["reference_doctype"],
								"reference_name": values["reference_name"]
							},
							callback: function () {
								frm.refresh();
							}
						});
					},
					function () {
						frappe.show_alert('Document not Relinked')
					}
				);
			}
		});
		d.show();
	},

	mark_as_read_unread: function(frm) {
		var action = frm.doc.seen? "Unread": "Read";
		var flag = "(\\SEEN)";

		return frappe.call({
			method: "frappe.email.inbox.create_email_flag_queue",
			args: {
				'names': [frm.doc.name],
				'action': action,
				'flag': flag
			},
			freeze: true
		});
	},

	reply: function(frm) {
		var args = frm.events.get_mail_args(frm);
		$.extend(args, {
			subject: __("Re: {0}", [frm.doc.subject]),
			recipients: frm.doc.sender
		})

		new frappe.views.CommunicationComposer(args);
	},

	reply_all: function(frm) {
		var args = frm.events.get_mail_args(frm)
		$.extend(args, {
			subject: __("Res: {0}", [frm.doc.subject]),
			recipients: frm.doc.sender,
			cc: frm.doc.cc
		})
		new frappe.views.CommunicationComposer(args);
	},

	forward_mail: function(frm) {
		var args = frm.events.get_mail_args(frm)
		$.extend(args, {
			forward: true,
			subject: __("Fw: {0}", [frm.doc.subject]),
		})

		new frappe.views.CommunicationComposer(args);
	},

	get_mail_args: function(frm) {
		var sender_email_id = ""
		$.each(frappe.boot.email_accounts, function(idx, account) {
			if(account.email_account == frm.doc.email_account) {
				sender_email_id = account.email_id
				return
			}
		});

		return {
			frm: frm,
			doc: frm.doc,
			last_email: frm.doc,
			sender: sender_email_id,
			attachments: frm.doc.attachments
		}
	},

	add_to_contact: function(frm) {
		var me = this;
		var fullname = frm.doc.sender_full_name || ""

		var names = fullname.split(" ")
		var first_name = names[0]
		var last_name = names.length >= 2? names[names.length - 1]: ""

		frappe.route_options = {
			"email_id": frm.doc.sender || "",
			"first_name": first_name,
			"last_name": last_name,
			"mobile_no": frm.doc.phone_no || ""
		}
		frappe.new_doc("Contact")
	},

	mark_as_spam: function(frm) {
		frappe.call({
			method: "frappe.email.inbox.mark_as_spam",
			args: {
				communication: frm.doc.name,
				sender: frm.doc.sender
			},
			freeze: true,
			callback: function(r) {
				frappe.msgprint(__("Email has been marked as spam"))
			}
		})
	},

	move_to_trash: function(frm) {
		frappe.call({
			method: "frappe.email.inbox.mark_as_trash",
			args: {
				communication: frm.doc.name
			},
			freeze: true,
			callback: function(r) {
				frappe.msgprint(__("Email has been moved to trash"))
			}
		})
	}
});
<|endoftext|>


<commit_before>import { Feature } from 'toolkit/extension/features/feature';
import { isCurrentRouteBudgetPage } from 'toolkit/extension/utils/ynab';
import { getEmberView } from 'toolkit/extension/utils/ember';
import { formatCurrency } from 'toolkit/extension/utils/currency';

export class DisplayUpcomingAmount extends Feature {
  injectCSS() { return require('./index.css'); }

  shouldInvoke() {
    return isCurrentRouteBudgetPage();
  }

  invoke() {
    $('.budget-table-row.is-sub-category').each((_, element) => {
      const { monthlySubCategoryBudgetCalculation, subCategory } = getEmberView(element.id, 'category');

      if (monthlySubCategoryBudgetCalculation && monthlySubCategoryBudgetCalculation.upcomingTransactions) {
        $('.budget-table-cell-activity', element)
          .addClass('toolkit-activity-upcoming')
          .prepend($('<div>', {
            class: 'toolkit-activity-upcoming-amount currency',
            title: `Total upcoming transaction amount in this month for ${subCategory.get('name')}`,
            text: formatCurrency(monthlySubCategoryBudgetCalculation.upcomingTransactions)
          }));
      }
    });
  }

  onRouteChanged() {
    if (!this.shouldInvoke()) return;
    this.invoke();
  }
}
<commit_msg>Clear "upcoming amount" elements when re-rendering (#1468) (#1505)<commit_after>import { Feature } from 'toolkit/extension/features/feature';
import { isCurrentRouteBudgetPage } from 'toolkit/extension/utils/ynab';
import { getEmberView } from 'toolkit/extension/utils/ember';
import { formatCurrency } from 'toolkit/extension/utils/currency';

export class DisplayUpcomingAmount extends Feature {
  injectCSS() { return require('./index.css'); }

  shouldInvoke() {
    return isCurrentRouteBudgetPage();
  }

  invoke() {
    $('.toolkit-activity-upcoming').removeClass('.toolkit-activity-upcoming');
    $('.toolkit-activity-upcoming-amount').remove();

    $('.budget-table-row.is-sub-category').each((_, element) => {
      const { monthlySubCategoryBudgetCalculation, subCategory } = getEmberView(element.id, 'category');

      if (monthlySubCategoryBudgetCalculation && monthlySubCategoryBudgetCalculation.upcomingTransactions) {
        $('.budget-table-cell-activity', element)
          .addClass('toolkit-activity-upcoming')
          .prepend($('<div>', {
            class: 'toolkit-activity-upcoming-amount currency',
            title: `Total upcoming transaction amount in this month for ${subCategory.get('name')}`,
            text: formatCurrency(monthlySubCategoryBudgetCalculation.upcomingTransactions)
          }));
      }
    });
  }

  onRouteChanged() {
    if (!this.shouldInvoke()) return;
    this.invoke();
  }
}
<|endoftext|>


<commit_before>// https://github.com/lasercar/konamijs
var Konami = (function() {
  'use strict';

  function arraysEqual(a, b) {
    if (a === b) return true;
    if (a == null || b == null) return false;
    if (a.length != b.length) return false;

    for (var i = 0; i < a.length; ++i) {
      if (a[i] !== b[i]) return false;
    }
    return true;
  }

  //arbitrary functions to be run when code is entered correctly
  var callbacks = [];

  function activate() {
    var deleted = [];
    //run all the callback functions
    callbacks.forEach(function(item, position) {
      item.callback();
      //track once-only functions for deletion
      if (item.once) {
        deleted.push(position);
      }
    });
    deleted.forEach(function(item) {
      //remove all once-only functions
      callbacks.splice(item, 1);
    });
  }

  //up up down down left right left right b a
  var code = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];

  //current correct streak of keys user has pressed
  var streak = [];

  //test if streak is shadowing code correctly
  function okay() {
    return arraysEqual(streak, code.slice(0, streak.length));
  }

  function check(event) {
    var key = event.keyCode;
    //add key to streak
    streak.push(key);
    console.log('start: ' + streak);
    //check if streak is still correct
    if (okay()) {
      //check if streak is complete
      if (streak.length === code.length) {
        activate();
        //reset streak
        streak = [];
      }
    } else {
      //until streak is correct or gone
      while (!okay() && streak.length !== 0) {
        //shift streak back by one
        console.log('while1: ' + streak);
        streak.splice(0, 1);
        console.log('while2: ' + streak);
      }
    }
    console.log('end: ' + streak);
  }

  window.addEventListener('keydown', check);

  function Konami(callback, once) {
    callbacks.push({callback: callback, once: once});
  };

  return Konami;
})();

//run myFunction each time konami code is entered: Komani(myFunction);
//run myFunction only first time code is entered: Konami(myFunction, true);
<commit_msg>remove debug code<commit_after>// https://github.com/lasercar/konamijs
var Konami = (function() {
  'use strict';

  function arraysEqual(a, b) {
    if (a === b) return true;
    if (a == null || b == null) return false;
    if (a.length != b.length) return false;

    for (var i = 0; i < a.length; ++i) {
      if (a[i] !== b[i]) return false;
    }
    return true;
  }

  //arbitrary functions to be run when code is entered correctly
  var callbacks = [];

  function activate() {
    var deleted = [];
    //run all the callback functions
    callbacks.forEach(function(item, position) {
      item.callback();
      //track once-only functions for deletion
      if (item.once) {
        deleted.push(position);
      }
    });
    deleted.forEach(function(item) {
      //remove all once-only functions
      callbacks.splice(item, 1);
    });
  }

  //up up down down left right left right b a
  var code = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];

  //current correct streak of keys user has pressed
  var streak = [];

  //test if streak is shadowing code correctly
  function okay() {
    return arraysEqual(streak, code.slice(0, streak.length));
  }

  function check(event) {
    var key = event.keyCode;
    //add key to streak
    streak.push(key);
    //check if streak is still correct
    if (okay()) {
      //check if streak is complete
      if (streak.length === code.length) {
        activate();
        //reset streak
        streak = [];
      }
    } else {
      //until streak is correct or gone
      while (!okay() && streak.length !== 0) {
        //shift streak back by one
        streak.splice(0, 1);
      }
    }
  }

  window.addEventListener('keydown', check);

  function Konami(callback, once) {
    callbacks.push({callback: callback, once: once});
  };

  return Konami;
})();

//run myFunction each time konami code is entered: Komani(myFunction);
//run myFunction only first time code is entered: Konami(myFunction, true);
<|endoftext|>


<commit_before>OC.L10N.register(
    "audioplayer",
    {
	"Welcome to" : "歡迎",
	"Audio Player" : "Audio Player",
	"Add new tracks to library" : "搜尋音樂",
	"Scan for new audio files" : "搜尋新的音樂",
	"New or updated audio files available" : "New or updated audio files available",
	"Help" : "Help",
	"Sort playlist" : "播放清單排序",
	"Rename playlist" : "重新命名播放清單",
	"Delete playlist" : "刪除播放清單",
	"Add new tracks to playlist by drag and drop" : "Add new tracks to playlist by drag and drop",
	"No playlist selected!" : "請選擇播放清單",
	"Sort modus active" : "排序",
	"Playlist successfully deleted!" : "成功刪除播放清單！",
	"Start scanning…" : "開始搜尋…",
	"Scanning finished! New Audios found!" : "搜尋完成，找到新的音樂！",
	"Yes" : "是",
	"No" : "否",
	"Error" : "錯誤",
	"Are you sure?" : "請確認",
	"Disc" : "片數",
	"Track" : "曲目",
	"File" : "檔案名",
	"Title" : "曲名",
	"Subtitle" : "副標題",
	"Artist" : "演出者",
	"Album Artist" : "Album Artist",
	"Year" : "年份",
	"Album" : "專輯",
	"Albums" : "專輯",
	"Composer" : "作曲者",
	"Edit metadata" : "編輯中繼資料",
	"Remove" : "移除",
	"Options" : "選項",
	"MIME type" : "MIME類型",
	"MIME type not supported by browser" : "瀏覽器不支援此樂曲格式",
	"Missing permission to edit metadata of track!" : "缺少修改曲目中繼資料的權限！",
	"Existing Albums" : "現有的專輯",
	"New Album" : " 新專輯",
	"Genre" : "曲風",
	"Add as Album Cover" : "設定為專輯封面",
	"of" : "之",
	"Total" : "全數",
	"Select from cloud" : "由雲端選擇",
	"Edit" : "編輯",
	"Delete" : "刪除",
	"Upload" : "上傳",
	"Drop picture" : "捨棄圖片",
	"Select picture" : "選取圖片",
	"Picture generating, please wait…" : "新稍候，正在生成圖片…",
	"Drag picture here!" : "將圖片拖曳到此！",
	"Settings" : "Settings",
	"Select a single folder with audio files" : "Select a single folder with audio files",
	"Invalid path!" : "Invalid path!",
	"saved" : "saved",
	"Reset library" : "Reset library",
	"Start resetting library…" : "Start resetting library…",
	"Resetting finished!" : "重設完成！",
	"All library entries will be deleted!" : "All library entries will be deleted!",
	"Close" : "關閉",
	"Play" : "播放",
	"Save" : "儲存",
	"Existing Artists" : "已存在的藝人",
	"New Artist" : " 新藝人",
	"Existing Genres" : "現有的曲風",
	"New Genre" : " 新曲風",
	"Selected" : "選擇",
	"Selected Album" : "選擇的專輯",
	"Selected Artist" : "選擇的藝人",
        "Selected Album Artist": "Selected Album Artist",
	"Selected Composer" : "選擇的作曲者",
	"Selected Folder" : "選擇的資料夾",
	"Selected Genre" : "選擇的曲風",
	"Selected Playlist" : "選擇的播放清單",
	"Selected Year" : "選擇的年份",
	"Length" : "長度"
},
"");
<commit_msg>Editor and Playlist added<commit_after>OC.L10N.register(
    "audioplayer",
    {
	"Welcome to" : "歡迎",
	"Audio Player" : "Audio Player",
	"Add new tracks to library" : "搜尋音樂",
	"Scan for new audio files" : "搜尋新的音樂",
	"New or updated audio files available" : "New or updated audio files available",
	"Help" : "Help",
	"Sort playlist" : "播放清單排序",
	"Rename playlist" : "重新命名播放清單",
	"Delete playlist" : "刪除播放清單",
	"Add new tracks to playlist by drag and drop" : "Add new tracks to playlist by drag and drop",
	"No playlist selected!" : "請選擇播放清單",
	"Sort modus active" : "排序",
	"Playlist successfully deleted!" : "成功刪除播放清單！",
	"Start scanning…" : "開始搜尋…",
	"Scanning finished! New Audios found!" : "搜尋完成，找到新的音樂！",
	"Yes" : "是",
	"No" : "否",
	"Error" : "錯誤",
	"Are you sure?" : "請確認",
	"Disc" : "片數",
	"Track" : "曲目",
	"File" : "檔案名",
	"Title" : "曲名",
	"Subtitle" : "副標題",
	"Artist" : "演出者",
	"Album Artist" : "Album Artist",
	"Year" : "年份",
	"Album" : "專輯",
	"Albums" : "專輯",
	"Composer" : "作曲者",
	"Edit metadata" : "編輯中繼資料",
	"No ID3 editor installed" : "No ID3 editor installed",
	"No playlist entry" : "No playlist entry",
	"Remove" : "移除",
	"Options" : "選項",
	"MIME type" : "MIME類型",
	"MIME type not supported by browser" : "瀏覽器不支援此樂曲格式",
	"Missing permission to edit metadata of track!" : "缺少修改曲目中繼資料的權限！",
	"Existing Albums" : "現有的專輯",
	"New Album" : " 新專輯",
	"Genre" : "曲風",
	"Add as Album Cover" : "設定為專輯封面",
	"of" : "之",
	"Total" : "全數",
	"Select from cloud" : "由雲端選擇",
	"Edit" : "編輯",
	"Delete" : "刪除",
	"Upload" : "上傳",
	"Drop picture" : "捨棄圖片",
	"Select picture" : "選取圖片",
	"Picture generating, please wait…" : "新稍候，正在生成圖片…",
	"Drag picture here!" : "將圖片拖曳到此！",
	"Settings" : "Settings",
	"Select a single folder with audio files" : "Select a single folder with audio files",
	"Invalid path!" : "Invalid path!",
	"saved" : "saved",
	"Reset library" : "Reset library",
	"Start resetting library…" : "Start resetting library…",
	"Resetting finished!" : "重設完成！",
	"All library entries will be deleted!" : "All library entries will be deleted!",
	"Close" : "關閉",
	"Play" : "播放",
	"Save" : "儲存",
	"Existing Artists" : "已存在的藝人",
	"New Artist" : " 新藝人",
	"Existing Genres" : "現有的曲風",
	"New Genre" : " 新曲風",
	"Selected" : "選擇",
	"Selected Album" : "選擇的專輯",
	"Selected Artist" : "選擇的藝人",
	"Selected Album Artist" : "Selected Album Artist",
	"Selected Composer" : "選擇的作曲者",
	"Selected Folder" : "選擇的資料夾",
	"Selected Genre" : "選擇的曲風",
	"Selected Playlist" : "選擇的播放清單",
	"Selected Year" : "選擇的年份",
	"Length" : "長度"
},
"");
<|endoftext|>