<commit_before>'use strict';
/* global CodeMirror */
/*eslint no-script-url:0 */

var React = require('react');
var ReactDOM = require('react-dom');
var TagTranslator = require('./tag-translator');
var _ = require('../../undash');
var cx = require('classnames');

var { keyCodes } = require('../../utils');

var toString = function (value) {
  if (_.isUndefined(value) || _.isNull(value)) {
    return '';
  }
  return String(value);
};

/*
   Editor for tagged text. Renders text like "hello {{firstName}}"
   with replacement labels rendered in a pill box. Designed to load
   quickly when many separate instances of it are on the same
   page.

   Uses CodeMirror to edit text. To save memory the CodeMirror node is
   instantiated when the user moves the mouse into the edit area.
   Initially a read-only view using a simple div is shown.
 */
module.exports = React.createClass({

  displayName: 'PrettyTextInput',

  mixins: [require('../../mixins/helper')],

  componentDidMount: function() {
    this.createEditor();
  },

  componentDidUpdate: function(prevProps, prevState) {
    if (prevState.codeMirrorMode !== this.state.codeMirrorMode) {
      // Changed from code mirror mode to read only mode or vice versa,
      // so setup the other editor.
      this.createEditor();
    }
    this.updateEditor();
  },

  componentWillUnmount: function() {
    if (this.state.codeMirrorMode) {
      this.removeCodeMirrorEditor();
    }
  },

  getInitialState: function() {
    var selectedChoices = this.props.selectedChoices;
    var replaceChoices = this.props.replaceChoices;
    var translator = TagTranslator(selectedChoices.concat(replaceChoices), this.props.config.humanize);

    return {
      // With number values, onFocus never fires, which means it stays read-only. So convert to string.
      value: toString(this.props.value),
      codeMirrorMode: false,
      isChoicesOpen: false,
      replaceChoices: replaceChoices,
      translator: translator,
      hasChanged: false
    };
  },

  componentWillReceiveProps: function(nextProps) {
    // If we're debouncing a change, then we should just ignore this props change,
    // because there will be another when we hit the trailing edge of the debounce.
    if (this.isDebouncingCodeMirrorChange) {
      return;
    }

    var selectedChoices = nextProps.selectedChoices;
    var replaceChoices = nextProps.replaceChoices;
    var nextState = {
      replaceChoices: replaceChoices,
      translator: TagTranslator(selectedChoices.concat(replaceChoices), this.props.config.humanize)
    };

    // Not sure what the null/undefined checks are here for, but changed from falsey which was breaking.
    if (this.state.value !== nextProps.value && !_.isUndefined(nextProps.value) && nextProps.value !== null) {
      nextState.value = toString(nextProps.value);
      if (this.state.hasChanged === false) {
        nextState.hasChanged = true;
      }
    }

    this.setState(nextState);
  },

  onChange: function (newValue) {
    this.props.onChange(newValue);
  },

  handleChoiceSelection: function (key, event) {
    const selectChoice = () => {
      var pos = this.state.selectedTagPos;
      var tag = '{{' + key + '}}';

      this.isInserting = true;
      if (pos) {
        this.codeMirror.replaceRange(tag, {line: pos.line, ch: pos.start}, {line: pos.line, ch: pos.stop});
      } else {
        this.codeMirror.replaceSelection(tag, 'end');
      }
      this.isInserting = false;
      this.codeMirror.focus();

      this.setState({ isChoicesOpen: false, selectedTagPos: null });
    };
    if (this.state.codeMirrorMode) {
      selectChoice();
    } else if (this.props.readOnly) {
      // hackety hack to stop dropdown choices from toggling
      event.stopPropagation();
      this.isInserting = true;
      this.onChange('{{' + key + '}}');
      this.isInserting = false;
      this.setState({ isChoicesOpen: false });
    } else {
      this.switchToCodeMirror(selectChoice);
    }
  },

  onFocusWrapper: function () {
    this.switchToCodeMirror(() => {
      this.codeMirror.focus();
      this.codeMirror.setCursor(this.codeMirror.lineCount(), 0);
    });
  },

  focus() {
    if (this.codeMirror) {
      this.focusCodeMirror();
    } else {
      this.switchToCodeMirror(() => {
        this.focusCodeMirror();
      });
    }
  },

  focusCodeMirror() {
    if (this.codeMirror) {
      this.codeMirror.focus();
    }
  },

  onFocusCodeMirror() {
    this.setState({ hasFocus: true });
    this.props.onFocus();
  },

  onBlur() {
    const blur = () => {
      this.setState({ hasFocus: false });
      this.props.onBlur();
    };
    if (this.isDebouncingCodeMirrorChange) {
      this.onChangeAndTagCodeMirror();
      this.isDebouncingCodeMirrorChange = false;
      setTimeout(blur, 0);
    } else {
      blur();
    }
  },

  insertBtn: function () {
    if (this.isReadOnly() && !this.hasReadOnlyControls()) {
      return null;
    }

    var onInsertClick = function () {
      this.setState({selectedTagPos: null});
      this.onToggleChoices();
    };

    const props = {
      ref: 'toggle',
      onClick: onInsertClick.bind(this),
      readOnly: this.isReadOnly(),
      field: this.props.field
    };
    return this.props.config.createElement('insert-button', props, 'Insert...');
  },

  choices: function () {
    if (this.isReadOnly()) {
      return null;
    }

    return this.props.config.createElement('choices', {
      ref: 'choices',
      onFocusSelect: this.focusCodeMirror,
      choices: this.state.replaceChoices,
      open: this.state.isChoicesOpen,
      ignoreCloseNodes: this.getCloseIgnoreNodes,
      onSelect: this.handleChoiceSelection,
      onClose: this.onCloseChoices,
      isAccordion: this.props.isAccordion,
      field: this.props.field,
      onChoiceAction: this.onChoiceAction
    });
  },

  onChoiceAction: function (choice) {
    this.setState({
      isChoicesOpen: !!choice.isOpen
    });
    this.onStartAction(choice.action, choice);
  },

  wrapperTabIndex() {
    if (this.props.readOnly || this.state.codeMirrorMode) {
      return null;
    }
    return this.props.tabIndex || 0;
  },

  onKeyDown(event) {
    if (!this.isReadOnly()) {
      if (event.keyCode === keyCodes.ESC) {
        event.preventDefault();
        event.stopPropagation();
        if (this.state.isChoicesOpen) {
          this.onToggleChoices();
          this.focusCodeMirror();
        }
      } else if (!this.state.isChoicesOpen) {
        // TODO: sane shortcut for opening choices
        // Below does not work yet. Ends up dumping { into search input.
        // if (this.codeMirror) {
        //   if (event.keyCode === keyCodes['['] && event.shiftKey) {
        //     const cursor = this.codeMirror.getCursor();
        //     const value = this.codeMirror.getValue();
        //     const lines = value.split('\n');
        //     const line = lines[cursor.line];
        //     if (line) {
        //       const prevChar = line[cursor.ch - 1];
        //       if (prevChar === '{') {
        //         this.onToggleChoices();
        //       }
        //     }
        //   }
        // }
      } else {
        if (this.refs.choices && this.refs.choices.onKeyDown) {
          this.refs.choices.onKeyDown(event);
        }
      }
    }
  },

  render: function() {
    return this.renderWithConfig();
  },

  renderDefault: function () {
    var textBoxClasses = cx(_.extend({}, this.props.classes, {
      'pretty-text-box': true,
      placeholder: this.hasPlaceholder(),
      'has-focus': this.state.hasFocus
    }));

    // Render read-only version.
    return (

        {this.insertBtn()}
        {this.choices()}

    );
  },

  getCloseIgnoreNodes: function () {
    return this.refs.toggle;
  },

  onToggleChoices: function () {
    this.setChoicesOpen(!this.state.isChoicesOpen);
  },

  setChoicesOpen: function (isOpen) {
    var action = isOpen ? 'open-replacements' : 'close-replacements';
    this.onStartAction(action);
    this.setState({ isChoicesOpen: isOpen });
  },

  onCloseChoices: function () {
    if (this.state.isChoicesOpen) {
      this.setChoicesOpen(false);
    }
  },

  createEditor: function () {
    if (this.state.codeMirrorMode) {
      this.createCodeMirrorEditor();
    } else {
      this.createReadonlyEditor();
    }
  },

  updateEditor: function () {
    if (this.state.codeMirrorMode) {
      var codeMirrorValue = this.codeMirror.getValue();
      if (!this.hasPlaceholder() && codeMirrorValue !== this.state.value) {
        // switch back to read-only mode to make it easier to render
        this.removeCodeMirrorEditor();
        this.createReadonlyEditor();
        this.setState({
          codeMirrorMode: false
        });
      }
    } else {
      this.createReadonlyEditor();
    }
  },

  createCodeMirrorEditor: function () {
    var options = {
      tabindex: this.props.tabIndex || 0,
      lineWrapping: true,
      placeholder: toString(this.props.config.fieldPlaceholder(this.props.field)),
      value: toString(this.state.value),
      readOnly: false,
      mode: null,
      extraKeys: {
        Tab: false,
        'Shift-Tab': false
      }
    };

    var textBox = this.refs.textBox;
    textBox.innerHTML = ''; // release any previous read-only content so it can be GC'ed

    this.codeMirror = CodeMirror(textBox, options);
    this.codeMirror.on('change', this.onCodeMirrorChange);
    this.codeMirror.on('focus', this.onFocusCodeMirror);

    this.tagCodeMirror();
  },

  tagCodeMirror: function () {
    var positions = this.state.translator.getTagPositions(this.codeMirror.getValue());
    var self = this;

    var tagOps = function () {
      positions.forEach(function (pos) {
        var node = self.createTagNode(pos);
        self.codeMirror.markText({line: pos.line, ch: pos.start},
                                 {line: pos.line, ch: pos.stop},
                                 {replacedWith: node, handleMouseEvents: true});
      });
    };

    this.codeMirror.operation(tagOps);
  },

  onChangeAndTagCodeMirror() {
    if (!this.codeMirror) {
      // We might have erased our codemirror instance before hitting the trailing
      // end of the debounce. If so, get the value out of state.
      if (this.props.value !== this.state.value) {
        this.onChange(this.state.value);
      }
      return;
    }
    this.onChange(this.codeMirror.getValue());
    this.tagCodeMirror();
  },

  onCodeMirrorChange: function () {
    var newValue = this.codeMirror.getValue();
    this.setState({value: newValue});

    // Immediately change and tag if inserting.
    if (this.isInserting) {
      this.onChangeAndTagCodeMirror();
      return;
    }

    // Otherwise, debounce so CodeMirror doesn't die.
    if (!this.debounceCodeMirrorChange) {
      this.debounceCodeMirrorChange = _.debounce(() => {
        if (this.isDebouncingCodeMirrorChange) {
          this.isDebouncingCodeMirrorChange = false;
          this.onChangeAndTagCodeMirror();
        }
      }, 200);
    }
    this.isDebouncingCodeMirrorChange = true;
    this.debounceCodeMirrorChange();
  },

  /* Return true if we should render the placeholder */
  hasPlaceholder: function () {
    return !this.state.value;
  },

  createReadonlyEditor: function () {
    var textBoxNode = this.refs.textBox;

    if (this.hasPlaceholder()) {
      return ReactDOM.render({this.props.field.placeholder}, textBoxNode);
    }

    var tokens = this.state.translator.tokenize(this.state.value);
    var self = this;
    var nodes = tokens.map(function (part, i) {
      if (part.type === 'tag') {
        var label = self.state.translator.getLabel(part.value);
        var props = {key: i, tag: part.value, replaceChoices: self.state.replaceChoices, field: self.props.field};
        return self.props.config.createElement('pretty-tag', props, label);
      }
      return {part.value};
    });

    ReactDOM.render({nodes}, textBoxNode);
  },

  removeCodeMirrorEditor: function () {
    var textBoxNode = this.refs.textBox;
    var cmNode = textBoxNode.firstChild;
    textBoxNode.removeChild(cmNode);
    this.codeMirror = null;
  },

  switchToCodeMirror: function (cb) {
    if (this.isReadOnly()) {
      return; // never render in code mirror if read-only
    }

    if (!this.state.codeMirrorMode && !this.props.readOnly) {
      this.setState({codeMirrorMode: true}, () => {
        if (this.codeMirror && _.isFunction(cb)) {
          cb();
        }
      });
    }
  },

  onTagClick: function () {
    const cursor = this.codeMirror.getCursor();
    const pos = this.state.translator.getTrueTagPosition(this.state.value, cursor);

    this.setState({selectedTagPos: pos});
    this.onToggleChoices();
  },

  createTagNode: function (pos) {
    var node = document.createElement('span');
    var label = this.state.translator.getLabel(pos.tag);
    var config = this.props.config;

    var props = {onClick: this.onTagClick, field: this.props.field, tag: pos.tag};

    ReactDOM.render(
      config.createElement('pretty-tag', props, label),
      node
    );

    return node;
  }
});
<commit_msg>use setState hook for async instead of setTimeout<commit_after>'use strict';
/* global CodeMirror */
/*eslint no-script-url:0 */

var React = require('react');
var ReactDOM = require('react-dom');
var TagTranslator = require('./tag-translator');
var _ = require('../../undash');
var cx = require('classnames');

var { keyCodes } = require('../../utils');

var toString = function (value) {
  if (_.isUndefined(value) || _.isNull(value)) {
    return '';
  }
  return String(value);
};

/*
   Editor for tagged text. Renders text like "hello {{firstName}}"
   with replacement labels rendered in a pill box. Designed to load
   quickly when many separate instances of it are on the same
   page.

   Uses CodeMirror to edit text. To save memory the CodeMirror node is
   instantiated when the user moves the mouse into the edit area.
   Initially a read-only view using a simple div is shown.
 */
module.exports = React.createClass({

  displayName: 'PrettyTextInput',

  mixins: [require('../../mixins/helper')],

  componentDidMount: function() {
    this.createEditor();
  },

  componentDidUpdate: function(prevProps, prevState) {
    if (prevState.codeMirrorMode !== this.state.codeMirrorMode) {
      // Changed from code mirror mode to read only mode or vice versa,
      // so setup the other editor.
      this.createEditor();
    }
    this.updateEditor();
  },

  componentWillUnmount: function() {
    if (this.state.codeMirrorMode) {
      this.removeCodeMirrorEditor();
    }
  },

  getInitialState: function() {
    var selectedChoices = this.props.selectedChoices;
    var replaceChoices = this.props.replaceChoices;
    var translator = TagTranslator(selectedChoices.concat(replaceChoices), this.props.config.humanize);

    return {
      // With number values, onFocus never fires, which means it stays read-only. So convert to string.
      value: toString(this.props.value),
      codeMirrorMode: false,
      isChoicesOpen: false,
      replaceChoices: replaceChoices,
      translator: translator,
      hasChanged: false
    };
  },

  componentWillReceiveProps: function(nextProps) {
    // If we're debouncing a change, then we should just ignore this props change,
    // because there will be another when we hit the trailing edge of the debounce.
    if (this.isDebouncingCodeMirrorChange) {
      return;
    }

    var selectedChoices = nextProps.selectedChoices;
    var replaceChoices = nextProps.replaceChoices;
    var nextState = {
      replaceChoices: replaceChoices,
      translator: TagTranslator(selectedChoices.concat(replaceChoices), this.props.config.humanize)
    };

    // Not sure what the null/undefined checks are here for, but changed from falsey which was breaking.
    if (this.state.value !== nextProps.value && !_.isUndefined(nextProps.value) && nextProps.value !== null) {
      nextState.value = toString(nextProps.value);
      if (this.state.hasChanged === false) {
        nextState.hasChanged = true;
      }
    }

    this.setState(nextState);
  },

  onChange: function (newValue) {
    this.props.onChange(newValue);
  },

  handleChoiceSelection: function (key, event) {
    const selectChoice = () => {
      var pos = this.state.selectedTagPos;
      var tag = '{{' + key + '}}';

      this.isInserting = true;
      if (pos) {
        this.codeMirror.replaceRange(tag, {line: pos.line, ch: pos.start}, {line: pos.line, ch: pos.stop});
      } else {
        this.codeMirror.replaceSelection(tag, 'end');
      }
      this.isInserting = false;
      this.codeMirror.focus();

      this.setState({ isChoicesOpen: false, selectedTagPos: null });
    };
    if (this.state.codeMirrorMode) {
      selectChoice();
    } else if (this.props.readOnly) {
      // hackety hack to stop dropdown choices from toggling
      event.stopPropagation();
      this.isInserting = true;
      this.onChange('{{' + key + '}}');
      this.isInserting = false;
      this.setState({ isChoicesOpen: false });
    } else {
      this.switchToCodeMirror(selectChoice);
    }
  },

  onFocusWrapper: function () {
    this.switchToCodeMirror(() => {
      this.codeMirror.focus();
      this.codeMirror.setCursor(this.codeMirror.lineCount(), 0);
    });
  },

  focus() {
    if (this.codeMirror) {
      this.focusCodeMirror();
    } else {
      this.switchToCodeMirror(() => {
        this.focusCodeMirror();
      });
    }
  },

  focusCodeMirror() {
    if (this.codeMirror) {
      this.codeMirror.focus();
    }
  },

  onFocusCodeMirror() {
    this.setState({ hasFocus: true });
    this.props.onFocus();
  },

  onBlur() {
    if (this.isDebouncingCodeMirrorChange) {
      this.onChangeAndTagCodeMirror();
      this.isDebouncingCodeMirrorChange = false;
      this.setState({ hasFocus: false }, this.props.onBlur);
    } else {
      this.setState({ hasFocus: false });
      this.props.onBlur();
    }
  },

  insertBtn: function () {
    if (this.isReadOnly() && !this.hasReadOnlyControls()) {
      return null;
    }

    var onInsertClick = function () {
      this.setState({selectedTagPos: null});
      this.onToggleChoices();
    };

    const props = {
      ref: 'toggle',
      onClick: onInsertClick.bind(this),
      readOnly: this.isReadOnly(),
      field: this.props.field
    };
    return this.props.config.createElement('insert-button', props, 'Insert...');
  },

  choices: function () {
    if (this.isReadOnly()) {
      return null;
    }

    return this.props.config.createElement('choices', {
      ref: 'choices',
      onFocusSelect: this.focusCodeMirror,
      choices: this.state.replaceChoices,
      open: this.state.isChoicesOpen,
      ignoreCloseNodes: this.getCloseIgnoreNodes,
      onSelect: this.handleChoiceSelection,
      onClose: this.onCloseChoices,
      isAccordion: this.props.isAccordion,
      field: this.props.field,
      onChoiceAction: this.onChoiceAction
    });
  },

  onChoiceAction: function (choice) {
    this.setState({
      isChoicesOpen: !!choice.isOpen
    });
    this.onStartAction(choice.action, choice);
  },

  wrapperTabIndex() {
    if (this.props.readOnly || this.state.codeMirrorMode) {
      return null;
    }
    return this.props.tabIndex || 0;
  },

  onKeyDown(event) {
    if (!this.isReadOnly()) {
      if (event.keyCode === keyCodes.ESC) {
        event.preventDefault();
        event.stopPropagation();
        if (this.state.isChoicesOpen) {
          this.onToggleChoices();
          this.focusCodeMirror();
        }
      } else if (!this.state.isChoicesOpen) {
        // TODO: sane shortcut for opening choices
        // Below does not work yet. Ends up dumping { into search input.
        // if (this.codeMirror) {
        //   if (event.keyCode === keyCodes['['] && event.shiftKey) {
        //     const cursor = this.codeMirror.getCursor();
        //     const value = this.codeMirror.getValue();
        //     const lines = value.split('\n');
        //     const line = lines[cursor.line];
        //     if (line) {
        //       const prevChar = line[cursor.ch - 1];
        //       if (prevChar === '{') {
        //         this.onToggleChoices();
        //       }
        //     }
        //   }
        // }
      } else {
        if (this.refs.choices && this.refs.choices.onKeyDown) {
          this.refs.choices.onKeyDown(event);
        }
      }
    }
  },

  render: function() {
    return this.renderWithConfig();
  },

  renderDefault: function () {
    var textBoxClasses = cx(_.extend({}, this.props.classes, {
      'pretty-text-box': true,
      placeholder: this.hasPlaceholder(),
      'has-focus': this.state.hasFocus
    }));

    // Render read-only version.
    return (

        {this.insertBtn()}
        {this.choices()}

    );
  },

  getCloseIgnoreNodes: function () {
    return this.refs.toggle;
  },

  onToggleChoices: function () {
    this.setChoicesOpen(!this.state.isChoicesOpen);
  },

  setChoicesOpen: function (isOpen) {
    var action = isOpen ? 'open-replacements' : 'close-replacements';
    this.onStartAction(action);
    this.setState({ isChoicesOpen: isOpen });
  },

  onCloseChoices: function () {
    if (this.state.isChoicesOpen) {
      this.setChoicesOpen(false);
    }
  },

  createEditor: function () {
    if (this.state.codeMirrorMode) {
      this.createCodeMirrorEditor();
    } else {
      this.createReadonlyEditor();
    }
  },

  updateEditor: function () {
    if (this.state.codeMirrorMode) {
      var codeMirrorValue = this.codeMirror.getValue();
      if (!this.hasPlaceholder() && codeMirrorValue !== this.state.value) {
        // switch back to read-only mode to make it easier to render
        this.removeCodeMirrorEditor();
        this.createReadonlyEditor();
        this.setState({
          codeMirrorMode: false
        });
      }
    } else {
      this.createReadonlyEditor();
    }
  },

  createCodeMirrorEditor: function () {
    var options = {
      tabindex: this.props.tabIndex || 0,
      lineWrapping: true,
      placeholder: toString(this.props.config.fieldPlaceholder(this.props.field)),
      value: toString(this.state.value),
      readOnly: false,
      mode: null,
      extraKeys: {
        Tab: false,
        'Shift-Tab': false
      }
    };

    var textBox = this.refs.textBox;
    textBox.innerHTML = ''; // release any previous read-only content so it can be GC'ed

    this.codeMirror = CodeMirror(textBox, options);
    this.codeMirror.on('change', this.onCodeMirrorChange);
    this.codeMirror.on('focus', this.onFocusCodeMirror);

    this.tagCodeMirror();
  },

  tagCodeMirror: function () {
    var positions = this.state.translator.getTagPositions(this.codeMirror.getValue());
    var self = this;

    var tagOps = function () {
      positions.forEach(function (pos) {
        var node = self.createTagNode(pos);
        self.codeMirror.markText({line: pos.line, ch: pos.start},
                                 {line: pos.line, ch: pos.stop},
                                 {replacedWith: node, handleMouseEvents: true});
      });
    };

    this.codeMirror.operation(tagOps);
  },

  onChangeAndTagCodeMirror() {
    if (!this.codeMirror) {
      // We might have erased our codemirror instance before hitting the trailing
      // end of the debounce. If so, get the value out of state.
      if (this.props.value !== this.state.value) {
        this.onChange(this.state.value);
      }
      return;
    }
    this.onChange(this.codeMirror.getValue());
    this.tagCodeMirror();
  },

  onCodeMirrorChange: function () {
    var newValue = this.codeMirror.getValue();
    this.setState({value: newValue});

    // Immediately change and tag if inserting.
    if (this.isInserting) {
      this.onChangeAndTagCodeMirror();
      return;
    }

    // Otherwise, debounce so CodeMirror doesn't die.
    if (!this.debounceCodeMirrorChange) {
      this.debounceCodeMirrorChange = _.debounce(() => {
        if (this.isDebouncingCodeMirrorChange) {
          this.isDebouncingCodeMirrorChange = false;
          this.onChangeAndTagCodeMirror();
        }
      }, 200);
    }
    this.isDebouncingCodeMirrorChange = true;
    this.debounceCodeMirrorChange();
  },

  /* Return true if we should render the placeholder */
  hasPlaceholder: function () {
    return !this.state.value;
  },

  createReadonlyEditor: function () {
    var textBoxNode = this.refs.textBox;

    if (this.hasPlaceholder()) {
      return ReactDOM.render({this.props.field.placeholder}, textBoxNode);
    }

    var tokens = this.state.translator.tokenize(this.state.value);
    var self = this;
    var nodes = tokens.map(function (part, i) {
      if (part.type === 'tag') {
        var label = self.state.translator.getLabel(part.value);
        var props = {key: i, tag: part.value, replaceChoices: self.state.replaceChoices, field: self.props.field};
        return self.props.config.createElement('pretty-tag', props, label);
      }
      return {part.value};
    });

    ReactDOM.render({nodes}, textBoxNode);
  },

  removeCodeMirrorEditor: function () {
    var textBoxNode = this.refs.textBox;
    var cmNode = textBoxNode.firstChild;
    textBoxNode.removeChild(cmNode);
    this.codeMirror = null;
  },

  switchToCodeMirror: function (cb) {
    if (this.isReadOnly()) {
      return; // never render in code mirror if read-only
    }

    if (!this.state.codeMirrorMode && !this.props.readOnly) {
      this.setState({codeMirrorMode: true}, () => {
        if (this.codeMirror && _.isFunction(cb)) {
          cb();
        }
      });
    }
  },

  onTagClick: function () {
    const cursor = this.codeMirror.getCursor();
    const pos = this.state.translator.getTrueTagPosition(this.state.value, cursor);

    this.setState({selectedTagPos: pos});
    this.onToggleChoices();
  },

  createTagNode: function (pos) {
    var node = document.createElement('span');
    var label = this.state.translator.getLabel(pos.tag);
    var config = this.props.config;

    var props = {onClick: this.onTagClick, field: this.props.field, tag: pos.tag};

    ReactDOM.render(
      config.createElement('pretty-tag', props, label),
      node
    );

    return node;
  }
});
<|endoftext|>


<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
var ModuleDependency = require("./ModuleDependency");

function HarmonyImportDependency(request, importedVar, range) {
	ModuleDependency.call(this, request);
	this.range = range;
	this.importedVar = importedVar;
}
module.exports = HarmonyImportDependency;

HarmonyImportDependency.prototype = Object.create(ModuleDependency.prototype);
HarmonyImportDependency.prototype.constructor = HarmonyImportDependency;
HarmonyImportDependency.prototype.type = "harmony import";

HarmonyImportDependency.prototype.getReference = function() {
	if(!this.module) return null;
	return {
		module: this.module,
		importedNames: false
	};
};

HarmonyImportDependency.prototype.updateHash = function updateHash(hash) {
	ModuleDependency.prototype.updateHash.call(this, hash);
	hash.update((dep.module && dep.module.meta && dep.module.meta.harmonyModule) + "");
};

HarmonyImportDependency.makeStatement = function(declare, dep, outputOptions, requestShortener) {
	var comment = "";
	if(outputOptions.pathinfo) comment = "/*! " + requestShortener.shorten(dep.request) + " */ ";
	var declaration = declare ? "var " : "";
	var content;
	if(!dep.module) {
		content = "throw new Error(" + JSON.stringify("Cannot find module \"" + dep.request + "\"") + ");\n";
	} else if(dep.importedVar) {
		content = "/* harmony import */ " + declaration + dep.importedVar + " = __webpack_require__(" + comment + JSON.stringify(dep.module.id) + ");\n";
		if(!(dep.module.meta && dep.module.meta.harmonyModule)) {
			content += "/* harmony import */ " + declaration + dep.importedVar + "_default = " + dep.importedVar + " && " + dep.importedVar + ".__esModule ? function() { return " + dep.importedVar + "['default'] } : function() { return " + dep.importedVar + "; }\n";
			content += "/* harmony import */ Object.defineProperty(" + dep.importedVar + "_default, 'a', { get: " + dep.importedVar + "_default });\n";
		}
	} else {
		content = "";
	}
	return content;
};

HarmonyImportDependency.Template = function HarmonyImportDependencyTemplate() {};

HarmonyImportDependency.Template.prototype.apply = function(dep, source, outputOptions, requestShortener) {
	var content = HarmonyImportDependency.makeStatement(true, dep, outputOptions, requestShortener);
	source.replace(dep.range[0], dep.range[1] - 1, "");
	source.insert(0, content);
};
<commit_msg>fixed reference<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
var ModuleDependency = require("./ModuleDependency");

function HarmonyImportDependency(request, importedVar, range) {
	ModuleDependency.call(this, request);
	this.range = range;
	this.importedVar = importedVar;
}
module.exports = HarmonyImportDependency;

HarmonyImportDependency.prototype = Object.create(ModuleDependency.prototype);
HarmonyImportDependency.prototype.constructor = HarmonyImportDependency;
HarmonyImportDependency.prototype.type = "harmony import";

HarmonyImportDependency.prototype.getReference = function() {
	if(!this.module) return null;
	return {
		module: this.module,
		importedNames: false
	};
};

HarmonyImportDependency.prototype.updateHash = function updateHash(hash) {
	ModuleDependency.prototype.updateHash.call(this, hash);
	hash.update((this.module && this.module.meta && this.module.meta.harmonyModule) + "");
};

HarmonyImportDependency.makeStatement = function(declare, dep, outputOptions, requestShortener) {
	var comment = "";
	if(outputOptions.pathinfo) comment = "/*! " + requestShortener.shorten(dep.request) + " */ ";
	var declaration = declare ? "var " : "";
	var content;
	if(!dep.module) {
		content = "throw new Error(" + JSON.stringify("Cannot find module \"" + dep.request + "\"") + ");\n";
	} else if(dep.importedVar) {
		content = "/* harmony import */ " + declaration + dep.importedVar + " = __webpack_require__(" + comment + JSON.stringify(dep.module.id) + ");\n";
		if(!(dep.module.meta && dep.module.meta.harmonyModule)) {
			content += "/* harmony import */ " + declaration + dep.importedVar + "_default = " + dep.importedVar + " && " + dep.importedVar + ".__esModule ? function() { return " + dep.importedVar + "['default'] } : function() { return " + dep.importedVar + "; }\n";
			content += "/* harmony import */ Object.defineProperty(" + dep.importedVar + "_default, 'a', { get: " + dep.importedVar + "_default });\n";
		}
	} else {
		content = "";
	}
	return content;
};

HarmonyImportDependency.Template = function HarmonyImportDependencyTemplate() {};

HarmonyImportDependency.Template.prototype.apply = function(dep, source, outputOptions, requestShortener) {
	var content = HarmonyImportDependency.makeStatement(true, dep, outputOptions, requestShortener);
	source.replace(dep.range[0], dep.range[1] - 1, "");
	source.insert(0, content);
};
<|endoftext|>


<commit_before>"use strict";
var AbstractConnectionManager = require('../abstract/connection-manager')
  , ConnectionManager
  , Utils = require('../../utils')
  , Promise = require('../../promise');

ConnectionManager = function(dialect, sequelize) {
  AbstractConnectionManager.call(this, dialect, sequelize);

  this.sequelize = sequelize;
  this.sequelize.config.port = this.sequelize.config.port || 5432;
  try {
    this.lib = sequelize.config.native ? require(sequelize.config.dialectModulePath || 'pg').native : require(sequelize.config.dialectModulePath || 'pg');
  } catch (err) {
    throw new Error('Please install postgres package manually');
  }
};

Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);

ConnectionManager.prototype.connect = function(config) {
  var self = this
    , clientConfig = {};

  config.user = config.username;

  if (config.dialectOptions) {
    clientConfig = Utils._.pick(config.dialectOptions, [
      // see [http://www.postgresql.org/docs/9.3/static/runtime-config-logging.html#GUC-APPLICATION-NAME]
      'application_name',
      // choose the SSL mode with the PGSSLMODE environment variable
      // object format: [https://github.com/brianc/node-postgres/blob/master/lib/connection.js#L79]
      // see also [http://www.postgresql.org/docs/9.3/static/libpq-ssl.html]
      'ssl',
      // In addition to the values accepted by the corresponding server,
      // you can use "auto" to determine the right encoding from the
      // current locale in the client (LC_CTYPE environment variable on Unix systems)
      'client_encoding',
      // !! DONT SET THIS TO TRUE !!
      // (unless you know what you're doing)
      // see [http://www.postgresql.org/message-id/flat/bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com#bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com]
      'binary'
    ]);
  }

  return new Promise(function (resolve, reject) {
    var connection = new self.lib.Client(clientConfig)
      , responded = false;

    connection.connect(function(err) {
      if (err) {
        if (err.code) {
          switch (err.code) {
          case 'ECONNREFUSED':
            reject(new Error('Failed to authenticate for PostgresSQL. Please double check your settings.'));
            break;
          case 'ENOTFOUND':
          case 'EHOSTUNREACH':
          case 'EINVAL':
            reject(new Error('Failed to find PostgresSQL server. Please double check your settings.'));
            break;
          default:
            reject(err);
            break;
          }
        } else {
          reject(new Error(err.message));
        }
        return;
      }
      responded = true;
      resolve(connection);
    });

    // If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted
    connection.on('end', function () {
      if (!responded) {
        reject(new Error('Connection timed out'));
      }
    });
  }).tap(function (connection) {
    if (self.sequelize.config.keepDefaultTimezone) return;
    return new Promise(function (resolve, reject) {
      connection.query("SET TIME ZONE INTERVAL '" + self.sequelize.options.timezone + "' HOUR TO MINUTE").on('error', function (err) {
        reject(err);
      }).on('end', function () {
        resolve();
      });
    });
  });
};
ConnectionManager.prototype.disconnect = function(connection) {
  return new Promise(function (resolve, reject) {
    connection.end();
    resolve();
  });
};

module.exports = ConnectionManager;
<commit_msg>pg: remove useless connection string<commit_after>"use strict";
var AbstractConnectionManager = require('../abstract/connection-manager')
  , ConnectionManager
  , Utils = require('../../utils')
  , Promise = require('../../promise');

ConnectionManager = function(dialect, sequelize) {
  AbstractConnectionManager.call(this, dialect, sequelize);

  this.sequelize = sequelize;
  this.sequelize.config.port = this.sequelize.config.port || 5432;
  try {
    this.lib = sequelize.config.native ? require(sequelize.config.dialectModulePath || 'pg').native : require(sequelize.config.dialectModulePath || 'pg');
  } catch (err) {
    throw new Error('Please install postgres package manually');
  }
};

Utils._.extend(ConnectionManager.prototype, AbstractConnectionManager.prototype);

ConnectionManager.prototype.connect = function(config) {
  var self = this
    , connectionConfig = {};

  config.user = config.username;
  connectionConfig = Utils._.pick(config, [
    'user', 'password', 'host', 'database', 'port'
  ]);

  if (config.dialectOptions) {
    Utils._.merge(connectionConfig,
                  Utils._.pick(config.dialectOptions, [
      // see [http://www.postgresql.org/docs/9.3/static/runtime-config-logging.html#GUC-APPLICATION-NAME]
      'application_name',
      // choose the SSL mode with the PGSSLMODE environment variable
      // object format: [https://github.com/brianc/node-postgres/blob/master/lib/connection.js#L79]
      // see also [http://www.postgresql.org/docs/9.3/static/libpq-ssl.html]
      'ssl',
      // In addition to the values accepted by the corresponding server,
      // you can use "auto" to determine the right encoding from the
      // current locale in the client (LC_CTYPE environment variable on Unix systems)
      'client_encoding',
      // !! DONT SET THIS TO TRUE !!
      // (unless you know what you're doing)
      // see [http://www.postgresql.org/message-id/flat/bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com#bc9549a50706040852u27633f41ib1e6b09f8339d845@mail.gmail.com]
      'binary'
    ]));
  }

  return new Promise(function (resolve, reject) {
    var connection = new self.lib.Client(connectionConfig)
      , responded = false;

    connection.connect(function(err) {
      if (err) {
        if (err.code) {
          switch (err.code) {
          case 'ECONNREFUSED':
            reject(new Error('Failed to authenticate for PostgresSQL. Please double check your settings.'));
            break;
          case 'ENOTFOUND':
          case 'EHOSTUNREACH':
          case 'EINVAL':
            reject(new Error('Failed to find PostgresSQL server. Please double check your settings.'));
            break;
          default:
            reject(err);
            break;
          }
        } else {
          reject(new Error(err.message));
        }
        return;
      }
      responded = true;
      resolve(connection);
    });

    // If we didn't ever hear from the client.connect() callback the connection timeout, node-postgres does not treat this as an error since no active query was ever emitted
    connection.on('end', function () {
      if (!responded) {
        reject(new Error('Connection timed out'));
      }
    });
  }).tap(function (connection) {
    if (self.sequelize.config.keepDefaultTimezone) return;
    return new Promise(function (resolve, reject) {
      connection.query("SET TIME ZONE INTERVAL '" + self.sequelize.options.timezone + "' HOUR TO MINUTE").on('error', function (err) {
        reject(err);
      }).on('end', function () {
        resolve();
      });
    });
  });
};
ConnectionManager.prototype.disconnect = function(connection) {
  return new Promise(function (resolve, reject) {
    connection.end();
    resolve();
  });
};

module.exports = ConnectionManager;
<|endoftext|>


<commit_before>var _ = require('@sailshq/lodash');

module.exports = function(self, options) {
  self.addHelpers({

    // `apos.singleton` renders a single widget of a fixed type, standing alone
    // in the page. The `_options` object is passed to the widget.
    //
    // A singleton is just a special case of an area, so you can change your
    // mind later and call `apos.area` with the same `name`.
    //
    // The `name` property distinguishes this singleton from other areas in
    // the same `doc`.
    //
    // If `_options.addLabel` is set, that text appears on the button to
    // initially populate the singleton. If `_options.editLabel` is set, that
    // text appears on the button edit an existing widget in the singleton.
    //
    // If `_options.controls.movable` is false, the widget may not be dragged out
    // of the singleton.
    //
    // If `_options.controls.removable` is false, the widget
    // may not be removed entirely.
    //
    // If `_options.controls.position` is set to `top-left`, `top-right`,
    // `bottom-left` or `bottom-right`, the widget controls (edit, drag
    // and remove) are positioned accordingly.
    //
    // If `_options` is not specified, Apostrophe falls back to the options
    // configured for the given field `name` in the schema for this type of
    // `doc`. For ordinary pages there usually won't be any, but this is
    // very convenient when working with `apostrophe-pieces`.
    //
    // Alternate syntax: `{ area: doc.areaname, type: type, ... more options }`

    singleton: function(doc, name, type, _options) {
      var area;
      var options;

      if (arguments.length === 1) {
        options = doc;
        area = options.area;
      } else {
        if (!name.match(/^\w+$/)) {
          self.apos.utils.warn(name + ' in ' + doc.slug + ': area names should be made up only of letters, underscores and digits. Otherwise they may not save properly.');
        }
        options = { type: type };
        if (_.has(doc, name)) {
          area = doc[name];
        } else {
          area = {
            // If the "doc" is actually a widget, continue the path
            _docId: doc.__docId || doc._id,
            _dotPath: (doc.__dotPath ? (doc.__dotPath + '.') : '') + name,
            _edit: doc._edit
          };
        }
      }

      if (!options.widgets) {
        // Need to make the widget definition options configured the same
        // way as in areas to support drag n drop.
        options.widgets = {};
        options.widgets[options.type] = _options || self.getSchemaOptions(doc, name);
      }

      // Don't fill the session with blessings of option sets unless
      // this user actually can edit this singleton
      if (area._edit) {
        _.each(options.widgets || {}, function(options, type) {
          self.apos.utils.bless(self.apos.templates.contextReq, options, 'widget', type);
        });
      }

      options.limit = 1;

      return self.renderArea(area, options);
    },

    // `apos.area` renders an area: a column of widgets of one or more types.
    //
    // If present The `_options` object must contain a `widgets` property, an object
    // which must at least contain a property by the name of each allowed widget. The
    // corresponding value should be an object, and is passed on as options to
    // widgets of that type appearing in this area.
    //
    // If `blockLevelControls: true` is present, the controls for this area are given
    // extra vertical space and rendered in a distinct color. These are affordances
    // to ensure the user can clearly distinguish the controls of an area used for layout, i.e.
    // an area that contains "two column" and "three column" widgets containing areas
    // of their own.
    //
    // The `name` property distinguishes this area from other areas in
    // the same `doc`.
    //
    // The `limit` option may be used to limit the number of widgets allowed.
    //
    // If `_options.controls.movable` is false, the widget may not be dragged out
    // of the singleton.
    //
    // If `_options.controls.removable` is false, the widget
    // may not be removed entirely.
    //
    // If `_options.controls.position` is set to `top-left`, `top-right`,
    // `bottom-left` or `bottom-right`, the widget controls (edit, drag
    // and remove) are positioned accordingly.
    //
    // If `_options` is not specified, Apostrophe falls back to the options
    // configured for the given field `name` in the schema for this type of
    // `doc`. For ordinary pages there usually won't be any, but this is
    // very convenient when working with `apostrophe-pieces`.
    //
    // Alternate syntax: `{ area: doc.areaname, ... more options }`

    area: function(doc, name, _options) {
      var area;
      var options;

      if (arguments.length === 1) {
        options = doc;
        area = options.area;
      } else {
        if (!name.match(/^\w+$/)) {
          self.apos.utils.warn(name + ' in ' + doc.slug + ': area names should be made up only of letters, underscores and digits. Otherwise they may not save properly.');
        }
        options = _options || self.getSchemaOptions(doc, name);
        if (_.has(doc, name)) {
          area = doc[name];
        } else {
          area = {
            // If the "doc" is actually a widget, continue the path
            _docId: doc.__docId || doc._id,
            _dotPath: (doc.__dotPath ? (doc.__dotPath + '.') : '') + name,
            _edit: doc._edit
          };
        }
      }

      options.hint = options.hint || self.apos.templates.contextReq.__('Use the Add Content button to get started.');

      // Don't fill the session with blessings of option sets unless
      // this user actually can edit this area
      if (area._edit) {
        _.each(options.widgets || {}, function(options, type) {
          self.apos.utils.bless(self.apos.templates.contextReq, options, 'widget', type);
        });
      }

      return self.renderArea(area, options);
    },

    // apos.areas.widget renders one widget. Invoked by both `apos.area` and
    // `apos.singleton`. Not
    // often called directly, but see `area.html` if you are interested in
    // doing so.

    widget: function(widget, options) {
      var manager = self.getWidgetManager(widget.type);
      if (!manager) {
        // Not configured in this project
        self.warnMissingWidgetType(widget.type);
        return '';
      }
      var partial;
      if (!manager.options.wrapperTemplate) {
        partial = _.partial(self.partial, 'widget');
      } else {
        partial = _.partial(manager.partial, manager.options.wrapperTemplate);
      }
      return partial({
        // The widget minus any properties that don't
        // absolutely need to be crammed into a JSON
        // attribute for the editor's benefit. Good filtering
        // here prevents megabytes of markup. -Tom and Sam
        dataFiltered: manager.filterForDataAttribute(widget),
        manager: manager,
        widget: widget,
        options: options,
        output: function() {
          return manager.output(widget, options);
        }
      });
    },

    // Returns the rich text markup of all `apostrophe-rich-text` widgets
    // within the provided doc or area, concatenated as a single string. In future this method
    // may improve to return the content of other widgets that consider themselves primarily
    // providers of rich text, such as subclasses of `apostrophe-rich-text`,
    // which will **not** be regarded as a bc break. However it will never return images, videos, etc.
    //
    // By default the rich text contents of the widgets are joined with
    // a newline between. You may pass your own `options.delimiter` string if
    // you wish a different delimiter or the empty string. You may also pass
    // an HTML element name like `div` via `options.wrapper` to wrap each
    // one in a `<div>...</div>` block. Of course, there may already be a div
    // in the rich txt (but then again there may not).
    //
    // Content will be retrieved from any widget type that supplies a
    // `getRichText` method.

    richText: function(within, options) {
      // Use the safe filter so that the markup doesn't get double-escaped by nunjucks
      return self.apos.templates.safe(self.richText(within, options));
    },

    // Returns the plaintext contents  of all rich text widgets
    // within the provided doc or area, concatenated as a single string.
    //
    // By default the rich text contents of the various widgets are joined with
    // a newline between. You may pass your own `options.delimiter` string if
    // you wish a different delimiter or the empty string.
    //
    // Pass `options.limit` to limit the number of characters. This method will
    // return fewer characters in order to avoid cutting off in mid-word.
    //
    // By default, three periods (`...`) follow a truncated string. If you prefer,
    // set `options.ellipsis` to a different suffix, which may be the empty string
    // if you wish.
    //
    // Content will be retrieved from any widget type that supplies a
    // `getRichText` method.

    plaintext: function(within, options) {
      return self.plaintext(within, options);
    },

    // Output the widget controls. The `addWidgetControlGroups` option can
    // be used to append additional control groups. See the
    // `widgetControlGroups` method for the format. That method can also
    // be extended via the `super` pattern to make the decision dynamically.

    widgetControlGroups: function(widget, options) {
      return self.widgetControlGroups(self.apos.templates.contextReq, widget, options);
    },

    // Returns true if the named area in the given `doc` is empty.
    //
    // Alternate syntax: `{ area: doc.areaname, ... more options }`
    //
    // An area is empty if it has no widgets in it, or when
    // all of the widgets in it return true when their
    // `isEmpty()` methods are interrogated. For instance,
    // if an area only contains a rich text widget and that
    // widget. A widget with no `isEmpty()` method is never empty.

    isEmpty: function(doc, name) {
      var area;
      if (!name) {
        area = doc.area;
      } else {
        area = doc[name];
      }
      return self.isEmpty({ area: area });
    }

  });

  // These are the most frequently used helpers in A2,
  // so we allow their use without typing .areas first
  self.addHelperShortcut('area');
  self.addHelperShortcut('singleton');
};
<commit_msg>Updated module docs to account for apos.area now respecting addLabel option<commit_after>var _ = require('@sailshq/lodash');

module.exports = function(self, options) {
  self.addHelpers({

    // `apos.singleton` renders a single widget of a fixed type, standing alone
    // in the page. The `_options` object is passed to the widget.
    //
    // A singleton is just a special case of an area, so you can change your
    // mind later and call `apos.area` with the same `name`.
    //
    // The `name` property distinguishes this singleton from other areas in
    // the same `doc`.
    //
    // If `_options.addLabel` is set, that text appears on the button to
    // initially populate the singleton. If `_options.editLabel` is set, that
    // text appears on the button edit an existing widget in the singleton.
    //
    // If `_options.controls.movable` is false, the widget may not be dragged out
    // of the singleton.
    //
    // If `_options.controls.removable` is false, the widget
    // may not be removed entirely.
    //
    // If `_options.controls.position` is set to `top-left`, `top-right`,
    // `bottom-left` or `bottom-right`, the widget controls (edit, drag
    // and remove) are positioned accordingly.
    //
    // If `_options` is not specified, Apostrophe falls back to the options
    // configured for the given field `name` in the schema for this type of
    // `doc`. For ordinary pages there usually won't be any, but this is
    // very convenient when working with `apostrophe-pieces`.
    //
    // Alternate syntax: `{ area: doc.areaname, type: type, ... more options }`

    singleton: function(doc, name, type, _options) {
      var area;
      var options;

      if (arguments.length === 1) {
        options = doc;
        area = options.area;
      } else {
        if (!name.match(/^\w+$/)) {
          self.apos.utils.warn(name + ' in ' + doc.slug + ': area names should be made up only of letters, underscores and digits. Otherwise they may not save properly.');
        }
        options = { type: type };
        if (_.has(doc, name)) {
          area = doc[name];
        } else {
          area = {
            // If the "doc" is actually a widget, continue the path
            _docId: doc.__docId || doc._id,
            _dotPath: (doc.__dotPath ? (doc.__dotPath + '.') : '') + name,
            _edit: doc._edit
          };
        }
      }

      if (!options.widgets) {
        // Need to make the widget definition options configured the same
        // way as in areas to support drag n drop.
        options.widgets = {};
        options.widgets[options.type] = _options || self.getSchemaOptions(doc, name);
      }

      // Don't fill the session with blessings of option sets unless
      // this user actually can edit this singleton
      if (area._edit) {
        _.each(options.widgets || {}, function(options, type) {
          self.apos.utils.bless(self.apos.templates.contextReq, options, 'widget', type);
        });
      }

      options.limit = 1;

      return self.renderArea(area, options);
    },

    // `apos.area` renders an area: a column of widgets of one or more types.
    //
    // If present The `_options` object must contain a `widgets` property, an object
    // which must at least contain a property by the name of each allowed widget. The
    // corresponding value should be an object, and is passed on as options to
    // widgets of that type appearing in this area.
    //
    // If `blockLevelControls: true` is present, the controls for this area are given
    // extra vertical space and rendered in a distinct color. These are affordances
    // to ensure the user can clearly distinguish the controls of an area used for layout, i.e.
    // an area that contains "two column" and "three column" widgets containing areas
    // of their own.
    //
    // The `name` property distinguishes this area from other areas in
    // the same `doc`.
    //
    // The `limit` option may be used to limit the number of widgets allowed.
    //
    // For every widget in `_options.widgets`, you can pass the same options as
    // in `apos.singleton`. See the documentation above for `addLabel`,
    // `controls.movable`, `controls.removable` and `controls.position`. Note
    // that `addLabel` normally does not actually begin with `Add ` in areas, as
    // opposed to in singletons.
    //
    // If `_options` is not specified, Apostrophe falls back to the options
    // configured for the given field `name` in the schema for this type of
    // `doc`. For ordinary pages there usually won't be any, but this is
    // very convenient when working with `apostrophe-pieces`.
    //
    // Alternate syntax: `{ area: doc.areaname, ... more options }`

    area: function(doc, name, _options) {
      var area;
      var options;

      if (arguments.length === 1) {
        options = doc;
        area = options.area;
      } else {
        if (!name.match(/^\w+$/)) {
          self.apos.utils.warn(name + ' in ' + doc.slug + ': area names should be made up only of letters, underscores and digits. Otherwise they may not save properly.');
        }
        options = _options || self.getSchemaOptions(doc, name);
        if (_.has(doc, name)) {
          area = doc[name];
        } else {
          area = {
            // If the "doc" is actually a widget, continue the path
            _docId: doc.__docId || doc._id,
            _dotPath: (doc.__dotPath ? (doc.__dotPath + '.') : '') + name,
            _edit: doc._edit
          };
        }
      }

      options.hint = options.hint || self.apos.templates.contextReq.__('Use the Add Content button to get started.');

      // Don't fill the session with blessings of option sets unless
      // this user actually can edit this area
      if (area._edit) {
        _.each(options.widgets || {}, function(options, type) {
          self.apos.utils.bless(self.apos.templates.contextReq, options, 'widget', type);
        });
      }

      return self.renderArea(area, options);
    },

    // apos.areas.widget renders one widget. Invoked by both `apos.area` and
    // `apos.singleton`. Not
    // often called directly, but see `area.html` if you are interested in
    // doing so.

    widget: function(widget, options) {
      var manager = self.getWidgetManager(widget.type);
      if (!manager) {
        // Not configured in this project
        self.warnMissingWidgetType(widget.type);
        return '';
      }
      var partial;
      if (!manager.options.wrapperTemplate) {
        partial = _.partial(self.partial, 'widget');
      } else {
        partial = _.partial(manager.partial, manager.options.wrapperTemplate);
      }
      return partial({
        // The widget minus any properties that don't
        // absolutely need to be crammed into a JSON
        // attribute for the editor's benefit. Good filtering
        // here prevents megabytes of markup. -Tom and Sam
        dataFiltered: manager.filterForDataAttribute(widget),
        manager: manager,
        widget: widget,
        options: options,
        output: function() {
          return manager.output(widget, options);
        }
      });
    },

    // Returns the rich text markup of all `apostrophe-rich-text` widgets
    // within the provided doc or area, concatenated as a single string. In future this method
    // may improve to return the content of other widgets that consider themselves primarily
    // providers of rich text, such as subclasses of `apostrophe-rich-text`,
    // which will **not** be regarded as a bc break. However it will never return images, videos, etc.
    //
    // By default the rich text contents of the widgets are joined with
    // a newline between. You may pass your own `options.delimiter` string if
    // you wish a different delimiter or the empty string. You may also pass
    // an HTML element name like `div` via `options.wrapper` to wrap each
    // one in a `<div>...</div>` block. Of course, there may already be a div
    // in the rich txt (but then again there may not).
    //
    // Content will be retrieved from any widget type that supplies a
    // `getRichText` method.

    richText: function(within, options) {
      // Use the safe filter so that the markup doesn't get double-escaped by nunjucks
      return self.apos.templates.safe(self.richText(within, options));
    },

    // Returns the plaintext contents  of all rich text widgets
    // within the provided doc or area, concatenated as a single string.
    //
    // By default the rich text contents of the various widgets are joined with
    // a newline between. You may pass your own `options.delimiter` string if
    // you wish a different delimiter or the empty string.
    //
    // Pass `options.limit` to limit the number of characters. This method will
    // return fewer characters in order to avoid cutting off in mid-word.
    //
    // By default, three periods (`...`) follow a truncated string. If you prefer,
    // set `options.ellipsis` to a different suffix, which may be the empty string
    // if you wish.
    //
    // Content will be retrieved from any widget type that supplies a
    // `getRichText` method.

    plaintext: function(within, options) {
      return self.plaintext(within, options);
    },

    // Output the widget controls. The `addWidgetControlGroups` option can
    // be used to append additional control groups. See the
    // `widgetControlGroups` method for the format. That method can also
    // be extended via the `super` pattern to make the decision dynamically.

    widgetControlGroups: function(widget, options) {
      return self.widgetControlGroups(self.apos.templates.contextReq, widget, options);
    },

    // Returns true if the named area in the given `doc` is empty.
    //
    // Alternate syntax: `{ area: doc.areaname, ... more options }`
    //
    // An area is empty if it has no widgets in it, or when
    // all of the widgets in it return true when their
    // `isEmpty()` methods are interrogated. For instance,
    // if an area only contains a rich text widget and that
    // widget. A widget with no `isEmpty()` method is never empty.

    isEmpty: function(doc, name) {
      var area;
      if (!name) {
        area = doc.area;
      } else {
        area = doc[name];
      }
      return self.isEmpty({ area: area });
    }

  });

  // These are the most frequently used helpers in A2,
  // so we allow their use without typing .areas first
  self.addHelperShortcut('area');
  self.addHelperShortcut('singleton');
};
<|endoftext|>


<commit_before>window.currentVolumeObject = "";
window.currentVolumeKey = "";
window.currentChapter = -1;

window.loadVolumes = function() {
	$("#navigationholder").empty();

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString = "
  Scriptures  /
";
	$("#breadcrumb").append(crumbString);

	$.each(window.volumesObjects, function(key, value) {
		keyString = '"'+key+'"';
		string = ""
				+ value.volume_title + "
";
		$("#navigationholder").append(string);
	});
}

window.loadBooks = function(volumeKey) {
	$("#navigationholder").empty();

	currentVolumeObject = window.volumesObjects[volumeKey];
	currentVolumeKey = volumeKey;

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString1 = "
  Scriptures
";
	keyString = '"'+currentVolumeKey+'"';
	crumbString2 = "
  "
			+ currentVolumeObject.volume_title + "
";
	$("#breadcrumb").append(crumbString1);
	$("#breadcrumb").append(crumbString2);

	// the json array of books for the selected volume
	$.each(currentVolumeObject.books, function(key, value) {
		volKeyString = '"'+currentVolumeKey+'"';
		bookKeyString = '"'+key+'"';
		string = ""
				+ value.book_title + "
";
		$("#navigationholder").append(string);
	});

}

window.loadChapters = function(volumeKey, bookKey) {
	$("#navigationholder").empty();
	currentVolumeObject = window.volumesObjects[volumeKey];
	currentVolumeKey = volumeKey;

	currentBookObject = currentVolumeObject.books[bookKey];
	currentBookKey = bookKey;

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString1 = "
  Scriptures
";
	keyString = '"'+currentVolumeKey+'"';
	crumbString2 = "
  "
		+ currentVolumeObject.volume_title + "
";

	volKeyString = '"'+currentVolumeKey+'"';
	bookKeyString = '"'+currentBookKey+'"';
	crumbString3 = "
  " + currentBookObject.book_title + "
";
	$("#breadcrumb").append(crumbString1);
	$("#breadcrumb").append(crumbString2);
	$("#breadcrumb").append(crumbString3);

	chapterString = "";
	for ( var i = 0; i < currentBookObject.num_chapters; i++) {
		displayNum = i + 1;
		string = " " + displayNum
				+ "   -";
		chapterString += string;
	}
	chapterString = chapterString.slice(0, -1);
	$("#navigationholder").append(chapterString);

}

window.loadVerses = function(volumeKey, bookKey, chapterNum) {
	$("#navigationholder").empty();
	currentVolumeObject = window.volumesObjects[volumeKey];
	currentVolumeKey = volumeKey;

	currentBookObject = currentVolumeObject.books[bookKey];
	currentBookKey = bookKey;

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString1 = "
  Scriptures
";
	keyString = '"'+currentVolumeKey+'"';
	crumbString2 = "
  "
		+ currentVolumeObject.volume_title + "
";

	volKeyString = '"'+currentVolumeKey+'"';
	bookKeyString = '"'+currentBookKey+'"';
	crumbString3 = "
  "
		+ currentBookObject.book_title
		+ "
";
	crumbString4 = "
 " + eval(chapterNum+1) + "
";
	$("#breadcrumb").append(crumbString1);
	$("#breadcrumb").append(crumbString2);
	$("#breadcrumb").append(crumbString3);
	$("#breadcrumb").append(crumbString4);

	$.getJSON($SCRIPT_ROOT + '/get_verses', {
		book : currentBookObject.book_id,
		chapter_id : chapterNum + 1
	}, function(data) {
		$("#navigationholder").empty();
		$.each(data, function() {
			// alert('data ' +
			// this.verse_scripture);
			scriptureString = "
" + this.verse + ". "
					+ this.verse_scripture + "

"
			$("#navigationholder").append(scriptureString);
		});
	});
}

window.verseClick = function(div) {
	alert('mouse over ' + div);
	div.style.backgroundColor = '#cccccc';
}

window.verseMouseOver = function(div) {
	// alert('mouse over ' + div);
	div.style.backgroundColor = '#cccccc';
	div.style.cursor = "pointer"
}

window.verseMouseOut = function(div) {
	// alert('mouse over ' + div);
	div.style.backgroundColor = 'white';
}

// the on ready function
$(document).ready(function() {
	if (window.chapter_num != "None") {
		alert("load chapters " + window.chapter_num);
	} else if (window.book_name != "None") {
		alert("got a book, load by book " + window.book_name);
		window.loadChapters(window.book_name);
	}else if (window.volume_name != "None") {
		window.loadBooks(window.volumeKeyByName[window.volume_name]);
	} else {
		loadVolumes();
	}
});
<commit_msg>got the urls working with scripture navigation<commit_after>window.currentVolumeObject = "";
window.currentVolumeKey = "";
window.currentChapter = -1;

window.loadVolumes = function() {
	$("#navigationholder").empty();

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString = "
  Scriptures  /
";
	$("#breadcrumb").append(crumbString);

	$.each(window.volumesObjects, function(key, value) {
		keyString = '"'+key+'"';
		string = ""
				+ value.volume_title + "
";
		$("#navigationholder").append(string);
	});
}

window.loadBooks = function(volumeKey) {
	$("#navigationholder").empty();

	currentVolumeObject = window.volumesObjects[volumeKey];
	currentVolumeKey = volumeKey;

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString1 = "
  Scriptures
";
	keyString = '"'+currentVolumeKey+'"';
	crumbString2 = "
  "
			+ currentVolumeObject.volume_title + "
";
	$("#breadcrumb").append(crumbString1);
	$("#breadcrumb").append(crumbString2);

	// the json array of books for the selected volume
	$.each(currentVolumeObject.books, function(key, value) {
		volKeyString = '"'+currentVolumeKey+'"';
		bookKeyString = '"'+key+'"';
		string = ""
				+ value.book_title + "
";
		$("#navigationholder").append(string);
	});

}

window.loadChapters = function(volumeKey, bookKey) {
	$("#navigationholder").empty();
	currentVolumeObject = window.volumesObjects[volumeKey];
	currentVolumeKey = volumeKey;

	currentBookObject = currentVolumeObject.books[bookKey];
	currentBookKey = bookKey;

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString1 = "
  Scriptures
";
	keyString = '"'+currentVolumeKey+'"';
	crumbString2 = "
  "
		+ currentVolumeObject.volume_title + "
";

	volKeyString = '"'+currentVolumeKey+'"';
	bookKeyString = '"'+currentBookKey+'"';
	crumbString3 = "
  " + currentBookObject.book_title + "
";
	$("#breadcrumb").append(crumbString1);
	$("#breadcrumb").append(crumbString2);
	$("#breadcrumb").append(crumbString3);

	chapterString = "";
	for ( var i = 0; i < currentBookObject.num_chapters; i++) {
		displayNum = i + 1;
		string = " " + displayNum
				+ "   -";
		chapterString += string;
	}
	chapterString = chapterString.slice(0, -1);
	$("#navigationholder").append(chapterString);

}

window.loadVerses = function(volumeKey, bookKey, chapterNum) {
	$("#navigationholder").empty();
	currentVolumeObject = window.volumesObjects[volumeKey];
	currentVolumeKey = volumeKey;

	currentBookObject = currentVolumeObject.books[bookKey];
	currentBookKey = bookKey;

	// deal with the breadcrumbs
	$("#breadcrumb").empty();
	crumbString1 = "
  Scriptures
";
	keyString = '"'+currentVolumeKey+'"';
	crumbString2 = "
  "
		+ currentVolumeObject.volume_title + "
";

	volKeyString = '"'+currentVolumeKey+'"';
	bookKeyString = '"'+currentBookKey+'"';
	crumbString3 = "
  "
		+ currentBookObject.book_title
		+ "
";
	chapterInt = parseInt(chapterNum)+1
	crumbString4 = "
 " + chapterInt + "
";
	$("#breadcrumb").append(crumbString1);
	$("#breadcrumb").append(crumbString2);
	$("#breadcrumb").append(crumbString3);
	$("#breadcrumb").append(crumbString4);

	$.getJSON($SCRIPT_ROOT + '/get_verses', {
		book : currentBookObject.book_id,
		chapter_id : parseInt(chapterNum) + 1
	}, function(data) {
		$("#navigationholder").empty();
		$.each(data, function() {
			// alert('data ' +
			// this.verse_scripture);
			scriptureString = "
" + this.verse + ". "
					+ this.verse_scripture + "

"
			$("#navigationholder").append(scriptureString);
		});
	});
}

window.verseClick = function(div) {
	alert('mouse over ' + div);
	div.style.backgroundColor = '#cccccc';
}

window.verseMouseOver = function(div) {
	// alert('mouse over ' + div);
	div.style.backgroundColor = '#cccccc';
	div.style.cursor = "pointer"
}

window.verseMouseOut = function(div) {
	// alert('mouse over ' + div);
	div.style.backgroundColor = 'white';
}

// the on ready function
$(document).ready(function() {
	if (chapter_num != "None" && book_name != "None" && volume_name != "None") {
		chapterInt = parseInt(chapter_num)-1;
		loadVerses(volume_name, book_name, chapterInt);
	} else if (book_name != "None" && volume_name != "None") {
		loadChapters(volume_name, book_name);
	}else if (volume_name != "None") {
		loadBooks(volume_name);
	} else {
		loadVolumes();
	}
});
<|endoftext|>


<commit_before>/**
 * This file is covered by the AGPLv3 license, which can be found at the LICENSE file in the root of this project.
 * @copyright 2021 subtitulamos.tv
 */

import "../css/user.scss";
import timeago from "timeago.js";
import { $getAllEle, $getEle, $getById, easyFetch, showOverlayFromTpl, onDomReady } from "./utils";

const $roleChangeForm = $getEle("#reset-user-pwd");
if ($roleChangeForm) {
  let manualSubmit = false;
  $roleChangeForm.addEventListener("submit", function (e) {
    if (manualSubmit) {
      return;
    }

    e.preventDefault(); // Don't submit the form

    Swal.fire({
      type: "warning",
      confirmButtonText: "Reiniciar",
      cancelButtonText: "Cancelar",
      showCancelButton: true,
      html:
        "Estás a punto de reiniciar la contraseña de este usuario. Se generará una nueva contraseña, y su contraseña actual dejará de valer para acceder." +
        "

¿Estás seguro de querer continuar?",
    }).then((result) => {
      if (result.value) {
        manualSubmit = true;
        $roleChangeForm.submit();
      }
    });
  });
}

const $banButton = $getEle("#ban");
if ($banButton) {
  $banButton.addEventListener("click", () => {
    showOverlayFromTpl("ban-dialog");

    $getAllEle("input[type='radio']").forEach((radio) => {
      radio.addEventListener("change", () => {
        $getEle("#detailed-duration").classList.toggle("hidden", radio.value === "permanent");
      });
    });
  });
}

function setSeeMoreVisibility($targetList) {
  $targetList.parentElement
    .querySelector(".spoiler-name")
    .classList.toggle("hidden", $targetList.offsetHeight < 480);
}

const $epTemplate = $getById("subtitle-card");
function addEpisodes(target, startIdx, count) {
  let $subtitleCardsWrap;

  switch (target) {
    case "upload":
      $subtitleCardsWrap = $getById(`${target}-list`);
    case "collab":
      $subtitleCardsWrap = $getById(`${target}-list`);
  }

  $subtitleCardsWrap.innerHTML = "";

  for (let i = startIdx; i < startIdx + count; ++i) {
    const $node = document.importNode($epTemplate.content, true);
    const $targetDiv = $node.children[0];
    $targetDiv.dataset.idx = i;
    subsByTab[target].$episodes[i] = $targetDiv;
    $subtitleCardsWrap.appendChild($node);
  }
}

let subsByTab = {
  collab: {
    $episodes: [],
  },
  upload: {
    $episodes: [],
  },
};
const loadList = (target, msgs) => {
  easyFetch(`/users/${targetUserId}/${target}-list`, {
    method: "get",
  })
    .then((reply) => reply.json())
    .then((data) => {
      const count = data.length;
      subsByTab[target].loading = true;
      addEpisodes(target, 0, count);
      const $targetList = $getById(`${target}-list`);

      $getEle(`#${target}-count`).innerHTML = count;
      if (count > 0) {
        for (let idx = 0; idx < count; idx++) {
          const ep = data[idx];

          const $card = subsByTab[target].$episodes[idx];
          $card.querySelector(".episode-time-ago").classList.toggle("hidden", true);
          $card.innerHTML = $card.innerHTML.replace("{ep_show}", ep.show);
          $card.innerHTML = $card.innerHTML.replace("{ep_season}", ep.season);
          $card.innerHTML = $card.innerHTML.replace("{ep_num}", ep.episode_number);
          $card.innerHTML = $card.innerHTML.replace("{ep_name}", ep.name);
          $card.innerHTML = $card.innerHTML.replace("{ep_url}", ep.url);
          $card.querySelector(".metadata").classList.toggle("hidden", false);
          $card.querySelector(".loading").classList.toggle("hidden", true);
        }
      } else {
        $targetList.innerHTML = msgs.noResults;
      }
      setSeeMoreVisibility($targetList);
    })
    .catch((err) => {
      console.log(err, target);
      Toasts.error.fire(msgs.error);
    });
};

onDomReady(() => {
  loadList("upload", {
    noResults: "El usuario no ha colaborado en ningún capítulo",
    error: "Ha ocurrido un error al cargar los capítulos en los que ha colaborado",
  });
  loadList("collab", {
    noResults: "El usuario no ha subido ningún capítulo",
    error: "Ha ocurrido un error al cargar los capítulos subidos",
  });

  $getAllEle(".option").forEach((option) => {
    option.addEventListener("click", (e) => {
      const $option = e.currentTarget;
      const $parent = $option.closest(".option-wrapper");
      const $toggleTargetsClass = $option.dataset.toggleTargets;
      const $enableElementId = $option.dataset.enable;

      $parent
        .querySelectorAll(".option.selected")
        .forEach((element) => element.classList.toggle("selected", false));
      $option.classList.toggle("selected");

      $getAllEle("." + $toggleTargetsClass).forEach((element) =>
        element.classList.toggle("hidden", true)
      );

      const $spoilerWrapper = $getById($enableElementId);
      $spoilerWrapper.classList.toggle("hidden", false);
      setSeeMoreVisibility($spoilerWrapper.querySelector(".spoiler-content"));
    });
  });

  for (const $spoilerName of $getAllEle(".spoiler-name")) {
    $spoilerName.addEventListener("click", function () {
      const $spoilerWrapper = this.closest(".spoiler-wrapper");
      const $spoiler = $spoilerWrapper.querySelector(".spoiler-content");

      if ($spoilerName.innerHTML.includes("MÁS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MÁS", "MENOS");
      } else if ($spoilerName.innerHTML.includes("MENOS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MENOS", "MÁS");
      }

      const $icon = this.querySelector(".spoiler-name i");
      $icon.classList.toggle("fa-chevron-down");
      $icon.classList.toggle("fa-chevron-up");

      $spoiler.classList.toggle("expanded");
    });
  }

  const $settingsForm = $getEle("#settings-form");
  const checkPwdValidity = () => {
    let err = "";
    const $pwdConfirm = $settingsForm.querySelector("[name=password-confirmation]");
    const $pwd = $settingsForm.querySelector("[name=password-new]");
    if ($pwdConfirm.value && $pwdConfirm.value != $pwd.value) {
      err = "Las contraseñas no coinciden";
    }

    $pwdConfirm.setCustomValidity(err);
  };

  if ($settingsForm) {
    $settingsForm.querySelectorAll("[name^=password]").forEach(($ele) => {
      $ele.addEventListener("keyup", checkPwdValidity);
      $ele.addEventListener("blur", checkPwdValidity);
    });
  }

  const $fontFamilyPreview = $getById("font-family-preview");
  if ($fontFamilyPreview) {
    $getById("font-family").addEventListener("change", (e) => {
      $fontFamilyPreview.classList.value = $fontFamilyPreview.classList.value.replace(
        new RegExp("font-family-([a-z]|-)*"),
        "font-family-" + e.target.value
      );
    });
  }

  const $fontThicknessPreview = $getById("font-thickness-preview");
  if ($fontThicknessPreview) {
    $getById("font-thickness").addEventListener("change", (e) => {
      $fontThicknessPreview.classList.value = $fontThicknessPreview.classList.value.replace(
        new RegExp("font-thickness-([a-z]|-)*"),
        "font-thickness-" + e.target.value
      );
    });
  }

  const $colorSwatchPreview = $getById("color-swatch-preview");
  if ($colorSwatchPreview) {
    $getById("color-swatch").addEventListener("change", (e) => {
      $colorSwatchPreview.classList.value = $colorSwatchPreview.classList.value.replace(
        new RegExp("color-swatch-([a-z]|-)*"),
        "color-swatch-" + e.target.value
      );
    });
  }
});
<commit_msg>Fix no results messages when looking at user profile<commit_after>/**
 * This file is covered by the AGPLv3 license, which can be found at the LICENSE file in the root of this project.
 * @copyright 2021 subtitulamos.tv
 */

import "../css/user.scss";
import timeago from "timeago.js";
import { $getAllEle, $getEle, $getById, easyFetch, showOverlayFromTpl, onDomReady } from "./utils";

const $roleChangeForm = $getEle("#reset-user-pwd");
if ($roleChangeForm) {
  let manualSubmit = false;
  $roleChangeForm.addEventListener("submit", function (e) {
    if (manualSubmit) {
      return;
    }

    e.preventDefault(); // Don't submit the form

    Swal.fire({
      type: "warning",
      confirmButtonText: "Reiniciar",
      cancelButtonText: "Cancelar",
      showCancelButton: true,
      html:
        "Estás a punto de reiniciar la contraseña de este usuario. Se generará una nueva contraseña, y su contraseña actual dejará de valer para acceder." +
        "

¿Estás seguro de querer continuar?",
    }).then((result) => {
      if (result.value) {
        manualSubmit = true;
        $roleChangeForm.submit();
      }
    });
  });
}

const $banButton = $getEle("#ban");
if ($banButton) {
  $banButton.addEventListener("click", () => {
    showOverlayFromTpl("ban-dialog");

    $getAllEle("input[type='radio']").forEach((radio) => {
      radio.addEventListener("change", () => {
        $getEle("#detailed-duration").classList.toggle("hidden", radio.value === "permanent");
      });
    });
  });
}

function setSeeMoreVisibility($targetList) {
  $targetList.parentElement
    .querySelector(".spoiler-name")
    .classList.toggle("hidden", $targetList.offsetHeight < 480);
}

const $epTemplate = $getById("subtitle-card");
function addEpisodes(target, startIdx, count) {
  let $subtitleCardsWrap;

  switch (target) {
    case "upload":
      $subtitleCardsWrap = $getById(`${target}-list`);
    case "collab":
      $subtitleCardsWrap = $getById(`${target}-list`);
  }

  $subtitleCardsWrap.innerHTML = "";

  for (let i = startIdx; i < startIdx + count; ++i) {
    const $node = document.importNode($epTemplate.content, true);
    const $targetDiv = $node.children[0];
    $targetDiv.dataset.idx = i;
    subsByTab[target].$episodes[i] = $targetDiv;
    $subtitleCardsWrap.appendChild($node);
  }
}

let subsByTab = {
  collab: {
    $episodes: [],
  },
  upload: {
    $episodes: [],
  },
};
const loadList = (target, msgs) => {
  easyFetch(`/users/${targetUserId}/${target}-list`, {
    method: "get",
  })
    .then((reply) => reply.json())
    .then((data) => {
      const count = data.length;
      subsByTab[target].loading = true;
      addEpisodes(target, 0, count);
      const $targetList = $getById(`${target}-list`);

      $getEle(`#${target}-count`).innerHTML = count;
      if (count > 0) {
        for (let idx = 0; idx < count; idx++) {
          const ep = data[idx];

          const $card = subsByTab[target].$episodes[idx];
          $card.querySelector(".episode-time-ago").classList.toggle("hidden", true);
          $card.innerHTML = $card.innerHTML.replace("{ep_show}", ep.show);
          $card.innerHTML = $card.innerHTML.replace("{ep_season}", ep.season);
          $card.innerHTML = $card.innerHTML.replace("{ep_num}", ep.episode_number);
          $card.innerHTML = $card.innerHTML.replace("{ep_name}", ep.name);
          $card.innerHTML = $card.innerHTML.replace("{ep_url}", ep.url);
          $card.querySelector(".metadata").classList.toggle("hidden", false);
          $card.querySelector(".loading").classList.toggle("hidden", true);
        }
      } else {
        $targetList.innerHTML = msgs.noResults;
      }
      setSeeMoreVisibility($targetList);
    })
    .catch((err) => {
      console.log(err, target);
      Toasts.error.fire(msgs.error);
    });
};

onDomReady(() => {
  loadList("upload", {
    noResults: "El usuario no ha subido ningún capítulo",
    error: "Ha ocurrido un error al cargar los capítulos subidos",
  });
  loadList("collab", {
    noResults: "El usuario no ha colaborado en ningún capítulo",
    error: "Ha ocurrido un error al cargar los capítulos en los que ha colaborado",
  });

  $getAllEle(".option").forEach((option) => {
    option.addEventListener("click", (e) => {
      const $option = e.currentTarget;
      const $parent = $option.closest(".option-wrapper");
      const $toggleTargetsClass = $option.dataset.toggleTargets;
      const $enableElementId = $option.dataset.enable;

      $parent
        .querySelectorAll(".option.selected")
        .forEach((element) => element.classList.toggle("selected", false));
      $option.classList.toggle("selected");

      $getAllEle("." + $toggleTargetsClass).forEach((element) =>
        element.classList.toggle("hidden", true)
      );

      const $spoilerWrapper = $getById($enableElementId);
      $spoilerWrapper.classList.toggle("hidden", false);
      setSeeMoreVisibility($spoilerWrapper.querySelector(".spoiler-content"));
    });
  });

  for (const $spoilerName of $getAllEle(".spoiler-name")) {
    $spoilerName.addEventListener("click", function () {
      const $spoilerWrapper = this.closest(".spoiler-wrapper");
      const $spoiler = $spoilerWrapper.querySelector(".spoiler-content");

      if ($spoilerName.innerHTML.includes("MÁS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MÁS", "MENOS");
      } else if ($spoilerName.innerHTML.includes("MENOS")) {
        $spoilerName.innerHTML = $spoilerName.innerHTML.replace("MENOS", "MÁS");
      }

      const $icon = this.querySelector(".spoiler-name i");
      $icon.classList.toggle("fa-chevron-down");
      $icon.classList.toggle("fa-chevron-up");

      $spoiler.classList.toggle("expanded");
    });
  }

  const $settingsForm = $getEle("#settings-form");
  const checkPwdValidity = () => {
    let err = "";
    const $pwdConfirm = $settingsForm.querySelector("[name=password-confirmation]");
    const $pwd = $settingsForm.querySelector("[name=password-new]");
    if ($pwdConfirm.value && $pwdConfirm.value != $pwd.value) {
      err = "Las contraseñas no coinciden";
    }

    $pwdConfirm.setCustomValidity(err);
  };

  if ($settingsForm) {
    $settingsForm.querySelectorAll("[name^=password]").forEach(($ele) => {
      $ele.addEventListener("keyup", checkPwdValidity);
      $ele.addEventListener("blur", checkPwdValidity);
    });
  }

  const $fontFamilyPreview = $getById("font-family-preview");
  if ($fontFamilyPreview) {
    $getById("font-family").addEventListener("change", (e) => {
      $fontFamilyPreview.classList.value = $fontFamilyPreview.classList.value.replace(
        new RegExp("font-family-([a-z]|-)*"),
        "font-family-" + e.target.value
      );
    });
  }

  const $fontThicknessPreview = $getById("font-thickness-preview");
  if ($fontThicknessPreview) {
    $getById("font-thickness").addEventListener("change", (e) => {
      $fontThicknessPreview.classList.value = $fontThicknessPreview.classList.value.replace(
        new RegExp("font-thickness-([a-z]|-)*"),
        "font-thickness-" + e.target.value
      );
    });
  }

  const $colorSwatchPreview = $getById("color-swatch-preview");
  if ($colorSwatchPreview) {
    $getById("color-swatch").addEventListener("change", (e) => {
      $colorSwatchPreview.classList.value = $colorSwatchPreview.classList.value.replace(
        new RegExp("color-swatch-([a-z]|-)*"),
        "color-swatch-" + e.target.value
      );
    });
  }
});
<|endoftext|>


<commit_before>define(['require', 'module', 'jquery', 'underscore', 'backbone', './manifest_item'],
    function (require, module, $, _, Backbone, ManifestItem) {

        var SpineItem = ManifestItem.extend({

            defaults: {
                "pageSpreadClass": ""
            },

            initialize: function () {

                // if (this.isFixedLayout()) {
                //     this.on("change:content", this.parseMetaTags, this);
                //     this.loadContent();
                // }
            },

            // REFACTORING CANDIDATE: The meta tags thing has to be worked out
            // toJSON : function () {

            // var json = {};
            // json.width = this.get("meta_width") || 0;
            // json.height = this.get("meta_height") || 0;
            // json.uri = this.resolveUri(this.get('href'));
            // json.page_class = this.getPageSpreadClass();
            // return json;
            // },

            // REFACTORING CANDIDATE: This needs to change

            isFixedLayout: function () {

                // if it an svg or image then it is fixed layout
                if (this.isSvg() || this.isImage()) {
                    return true;
                }

                // if there is a fixed_flow property, then it takes precedence
                if (typeof this.get("fixed_flow") !== 'undefined') {
                    return this.get("fixed_flow");
                }

                // nothing special about this spine item, fall back to the books settings
                return false;
            },

            // Description: Determines if the first page of the content document should be offset in a synthetic layout
            firstPageOffset: function () {

                // Get book properties
                var notFixedLayout = !this.isFixedLayout();
                var pageProgDirIsRTL = this.get("page_prog_dir") === "rtl" ? true : false;
                var pageSpreadLeft = this.get("page_spread") === "left" ? true : false;
                var pageSpreadRight = this.get("page_spread") === "right" ? true : false;

                // Default to no page spread specified if they are both set on the spine item
                if (pageSpreadRight && pageSpreadLeft) {
                    pageSpreadRight = false;
                    pageSpreadLeft = false;
                }

                if (notFixedLayout) {

                    if (pageProgDirIsRTL) {

                        if (pageSpreadLeft) {
                            return true;
                        }
                    } else {

                        if (pageSpreadRight) {
                            return true;
                        }
                    }
                }

                return false;
            },

            // NOTE: Media overlays have been disabled for the time being, which is why these methods are commented out.

            // hasMediaOverlay : function() {
            //     return !!this.get("media_overlay") && !!this.getMediaOverlay();
            // },

            // getMediaOverlay : function() {
            //     return this.collection.getMediaOverlay(this.get("media_overlay"));
            // }
        });
        return SpineItem;
    });
<commit_msg>Remove unused methods from spine item Backbone model.<commit_after>define(['require', 'module', 'jquery', 'underscore', 'backbone', './manifest_item'],
    function (require, module, $, _, Backbone, ManifestItem) {

        var SpineItem = ManifestItem.extend({

            defaults: {
                "pageSpreadClass": ""
            },

            initialize: function () {
            }

        });
        return SpineItem;
    });
<|endoftext|>


<commit_before><commit_msg>improvise query (#11878)<commit_after><|endoftext|>


<commit_before>const Parser = require('../../parser/parser.js')
const logParser = new Parser()

const SEVERITY = {
  stderr: 'err',
  stdout: 'info'
}

function jsonParse (text) {
  try {
    return JSON.parse(text)
  } catch (err) {
    return null
  }
}

function extractJson (line, source) {
  if (/^\[{0,1}\{.*\}]{0,1}$/.test(line)) {
    let parsed = {}
    parsed = jsonParse(line)
    if (!parsed) {
      return null
    }
    return parsed
  }
  return null
}

function parseJsonLog (log) {
  const jsonLog = extractJson(log.message)
  if (!jsonLog) {
    return null
  }

  if (jsonLog && jsonLog.message) {
    delete log.message
  }
  return {
    ...jsonLog,
    ...log
  }
}

function formatAwsEcs (context, config, eventEmitter, log, callback) {
  try {

    log.severity = SEVERITY[log.source]
    log.source = log.sourceName
    delete log.sourceName

    // check if log.message is in JSON format, return parsed log object or null
    const parsedLog = parseJsonLog(log)
    if (parsedLog) {
      // if not null, the log is parsed, structured and sent down the pipeline
      return callback(null, parsedLog)
    }

    //
    //
    //
    //

    // optional parsing of message field with logagent patterns
    if (log.message !== undefined) {
      logParser.parseLine(log.message, log.source, function (err, data) {
        if (err) {
          return callback(err, log)
        }

        if (data && data._type) {
          // console.log('\n')
          // console.log('\n')
          // console.log(data)
          // console.log('\n')
          // console.log('\n')

          const type = `${data._type}`
          delete data['@timestamp']
          delete data.logSource
          delete data._type
          log.type = type
          log[type] = {}
          Object.assign(log[type], data)

          console.log('\n')
          console.log('\n')
          console.log(log)
          console.log('\n')
          console.log('\n')
        }
        return callback(null, log)
      })
    }

    return callback(null, log)
  } catch (err) {
    console.log(err)
    return callback(err, log)
  }
}

module.exports = formatAwsEcs
<commit_msg>cleanup<commit_after>const Parser = require('../../parser/parser.js')
const logParser = new Parser()

const SEVERITY = {
  stderr: 'err',
  stdout: 'info'
}

function jsonParse (text) {
  try {
    return JSON.parse(text)
  } catch (err) {
    return null
  }
}

function extractJson (line, source) {
  if (/^\[{0,1}\{.*\}]{0,1}$/.test(line)) {
    let parsed = {}
    parsed = jsonParse(line)
    if (!parsed) {
      return null
    }
    return parsed
  }
  return null
}

function parseJsonLog (log) {
  const jsonLog = extractJson(log.message)
  if (!jsonLog) {
    return null
  }

  if (jsonLog && jsonLog.message) {
    delete log.message
  }
  return {
    ...jsonLog,
    ...log
  }
}

function formatAwsEcs (context, config, eventEmitter, log, callback) {
  try {
    log.severity = SEVERITY[log.source]
    log.source = log.sourceName
    delete log.sourceName

    // check if log.message is in JSON format, return parsed log object or null
    const parsedLog = parseJsonLog(log)
    if (parsedLog) {
      // if not null, the log is parsed, structured and sent down the pipeline
      return callback(null, parsedLog)
    }

    // if the log.message is not in JSON format then
    // we parse the log.message field with logagent patterns
    // using the log.source as the pattern sourceName (check patterns.yml)
    // for for more info
    if (log.message !== undefined) {
      logParser.parseLine(log.message, log.source, function (err, data) {
        if (err) {
          return callback(err, log)
        }

        if (data && data._type) {
          const type = `${data._type}`
          delete data['@timestamp']
          delete data.logSource
          delete data._type
          log.type = type
          log[type] = {}
          Object.assign(log[type], data)
        }
        return callback(null, log)
      })
    }

    return callback(null, log)
  } catch (err) {
    console.log(err)
    return callback(err, log)
  }
}

module.exports = formatAwsEcs
<|endoftext|>