<commit_before>import $ from 'jquery';
import _ from 'lodash-node';
import invariant from 'invariant';
import twitter from 'twitter-text';
import { query, remove, resetQuery, select } from 'mention/actions/mentionActions';
import { prevCharIsSpace } from 'mention/utils/tinyMCEUtils';

const Keys = {
  BACKSPACE: 8,
  SPACE: 32,
  TAB: 9
};

export function initializePlugin(store, dataSource, delimiter = '@') {

  invariant(store,
    'Plugin must be initialized with a Redux store.'
  );

  invariant(dataSource,
    'Plugin must be initialized with a dataSource.  Datasource can be an array or promise.'
  );

  return new Promise((resolve, reject) => {

    if (_.isUndefined(window.tinymce)) {
      return reject('Error initializing Mention plugin: `tinymce` is undefined.');
    }

    window.tinymce.create('tinymce.plugins.Mention', {

      /**
       * Callback when the Editor has been registered and is ready
       * to accept plugin initialization.
       * @param  {Object} editor The editor
       */
      init(editor) {
        const mentionPlugin = new MentionPlugin(editor, store, delimiter);
        const resolveInit = () => resolve(editor, mentionPlugin);

        // Check if we're using a promise the dataSource or a
        // raw array.  If promise, wait for it to resolve before
        // resolving the outer promise and initializing the app.
        if (_.isFunction(dataSource.then)) {

          // TODO: Implement promise-based lookup
          resolveInit();
        } else {
          resolveInit();
        }

        // Add persistent top-level listener for delegating events related
        // to binding and unbinding events related to the UI / querying.
        editor.on('keypress', function(event) {
          const character = String.fromCharCode(event.which || event.keyCode);
          const delimiterIndex = _.indexOf(delimiter, character);

          // User has typed `@`; begin tracking
          if (delimiterIndex > -1 && prevCharIsSpace(editor)) {
            if (!mentionPlugin.isFocused) {
              mentionPlugin.initialize();
            }

          // User has exited mentions, stop tracking
          } else if (prevCharIsSpace(editor) || character === ' ') {
            if (mentionPlugin.isFocused) {
              mentionPlugin.cleanup();
            }
          }
        });

        editor.on('keyup', mentionPlugin.handleBackspaceKey.bind(mentionPlugin));
      }
    });

    window.tinymce.PluginManager.add('mention', window.tinymce.plugins.Mention);
  });
}

export class MentionPlugin {

  /**
   * Increments to truthy for detecting if we're inside of a word.
   * @type {Number}
   */
  insideWord = -1;

  /**
   * Checks if we're currently focused on @mention lookup.
   * @type {Boolean}
   */
  isFocued = false;

  /**
   * The Redux store for handling lookups, mentions and tracking.
   * @type {Object}
   */
  store = null;

  /**
   * Reference to the TinyMCE editor.
   * @type {Object}
   */
  editor = null;

  /**
   * The delimiter we're using to trigger @mentions. Defaults to @.
   * @type {String}
   */
  delimiter = '@';

  constructor(editor, store, delimiter) {

    invariant(editor,
      'Error initializing MentionPlugin: `editor` cannot be undefined.'
    );

    invariant(store,
      'Error initializing MentionPlugin: `store` cannot be undefined.'
    );

    this.isFocused = false;
    this.store = store;
    this.editor = editor;
    this.delimiter = delimiter;

    // FIXME: Remove helper refs
    window.editor = editor;
    window.$ = $;
    window.mentionPlugin = this;

    return this;
  }

  /**
   * Initializes the MentionPlugin once a user has typed the delimiter.
   *
   * @return {MentionPlugin}
   */
  initialize() {
    if (!this.isFocued) {
      this.isFocused = true;
      this.addEventListeners();
    }
  }

  /**
   * Cleans up all event listeners and de-initializes plugin. Triggered
   * when outer listener detects a literal ' ' in entry, signifying
   * that we've exited the @mention lookup.
   */
  cleanup() {
    if (this.isFocused) {
      this.isFocused = false;
      this.store.dispatch(resetQuery());
      this.removeEventListeners();
    }
  }

  addEventListeners() {
    this.editor.on('keydown', this.keyPressProxy = $.proxy(this.handleKeyPress, this));
  }

  removeEventListeners() {
    this.editor.off('keydown', this.keyPressProxy);
  }

  /**
   * Handler for internal key-presses. Parses the input and dispatches
   * queries back to the store for list view and selection.
   *
   * @param  {jQuery.Event}
   */
  handleKeyPress(event) {
    const keyCode = event.which || event.keyCode;

    // Autocomplete current suggestion and prevent
    // unnecessary lookup.
    if (keyCode === Keys.TAB) {
      event.preventDefault();
      return this.store.dispatch(select());
    }

    _.defer(() => {
      const content = this.editor.getContent({
        format: 'text'
      });

      const re = /@\w+\b(?! *.)/;
      const match = re.exec(content);

      if (match) {
        const mentions = _.last(twitter.extractMentionsWithIndices(content));

        if (mentions && this.isFocused) {
          this.store.dispatch(query(mentions.screenName));
        }
      }
    });
  }

  /**
   * Handler for backspace presses. Dispatches back to store with request
   * to reset the current query and matches.
   *
   * @param  {jQuery.Event}
   */
  handleBackspaceKey(event) {
    const keyCode = event.which || event.keyCode;

    if (keyCode === Keys.BACKSPACE) {

      // Check to see if the surrounding area contains an @ and remove.
      const $node = $(this.editor.selection.getNode()).closest('.mention');

      if ($node.length) {
        const mention = $node
          .first()
          .text()
          .replace(/(?:@|_)/g, ' ')
          .trim();

        this.store.dispatch(remove(mention));

        // Remove @mention node from editor
        $node.remove();
      }
    }
  }
}
<commit_msg>Clean up bind<commit_after>import $ from 'jquery';
import _ from 'lodash-node';
import invariant from 'invariant';
import twitter from 'twitter-text';
import { query, remove, resetQuery, select } from 'mention/actions/mentionActions';
import { prevCharIsSpace } from 'mention/utils/tinyMCEUtils';

const Keys = {
  BACKSPACE: 8,
  SPACE: 32,
  TAB: 9
};

export function initializePlugin(store, dataSource, delimiter = '@') {

  invariant(store,
    'Plugin must be initialized with a Redux store.'
  );

  invariant(dataSource,
    'Plugin must be initialized with a dataSource.  Datasource can be an array or promise.'
  );

  return new Promise((resolve, reject) => {

    if (_.isUndefined(window.tinymce)) {
      return reject('Error initializing Mention plugin: `tinymce` is undefined.');
    }

    window.tinymce.create('tinymce.plugins.Mention', {

      /**
       * Callback when the Editor has been registered and is ready
       * to accept plugin initialization.
       * @param  {Object} editor The editor
       */
      init(editor) {
        const mentionPlugin = new MentionPlugin(editor, store, delimiter);
        const resolveInit = () => resolve(editor, mentionPlugin);

        // Check if we're using a promise the dataSource or a
        // raw array.  If promise, wait for it to resolve before
        // resolving the outer promise and initializing the app.
        if (_.isFunction(dataSource.then)) {

          // TODO: Implement promise-based lookup
          resolveInit();
        } else {
          resolveInit();
        }

        // Add persistent top-level listener for delegating events related
        // to binding and unbinding events related to the UI / querying.
        editor.on('keypress', function(event) {
          const character = String.fromCharCode(event.which || event.keyCode);
          const delimiterIndex = _.indexOf(delimiter, character);

          // User has typed `@`; begin tracking
          if (delimiterIndex > -1 && prevCharIsSpace(editor)) {
            if (!mentionPlugin.isFocused) {
              mentionPlugin.initialize();
            }

          // User has exited mentions, stop tracking
          } else if (prevCharIsSpace(editor) || character === ' ') {
            if (mentionPlugin.isFocused) {
              mentionPlugin.cleanup();
            }
          }
        });

        editor.on('keyup', mentionPlugin.handleBackspaceKey.bind(mentionPlugin));
      }
    });

    window.tinymce.PluginManager.add('mention', window.tinymce.plugins.Mention);
  });
}

export class MentionPlugin {

  /**
   * Checks if we're currently focused on @mention lookup.
   * @type {Boolean}
   */
  isFocued = false;

  /**
   * The Redux store for handling lookups, mentions and tracking.
   * @type {Object}
   */
  store = null;

  /**
   * Reference to the TinyMCE editor.
   * @type {Object}
   */
  editor = null;

  /**
   * The delimiter we're using to trigger @mentions. Defaults to @.
   * @type {String}
   */
  delimiter = '@';

  constructor(editor, store, delimiter) {

    invariant(editor,
      'Error initializing MentionPlugin: `editor` cannot be undefined.'
    );

    invariant(store,
      'Error initializing MentionPlugin: `store` cannot be undefined.'
    );

    this.isFocused = false;
    this.store = store;
    this.editor = editor;
    this.delimiter = delimiter;

    // FIXME: Remove helper refs
    window.editor = editor;
    window.$ = $;
    window.mentionPlugin = this;

    return this;
  }

  /**
   * Initializes the MentionPlugin once a user has typed the delimiter.
   *
   * @return {MentionPlugin}
   */
  initialize() {
    if (!this.isFocued) {
      this.isFocused = true;
      this.addEventListeners();
    }
  }

  /**
   * Cleans up all event listeners and de-initializes plugin. Triggered
   * when outer listener detects a literal ' ' in entry, signifying
   * that we've exited the @mention lookup.
   */
  cleanup() {
    if (this.isFocused) {
      this.isFocused = false;
      this.store.dispatch(resetQuery());
      this.removeEventListeners();
    }
  }

  addEventListeners() {
    this.editor.on('keydown', this.keyPressProxy = this.handleKeyPress.bind(this));
  }

  removeEventListeners() {
    this.editor.off('keydown', this.keyPressProxy);
  }

  /**
   * Handler for internal key-presses. Parses the input and dispatches
   * queries back to the store for list view and selection.
   *
   * @param  {jQuery.Event}
   */
  handleKeyPress(event) {
    const keyCode = event.which || event.keyCode;

    // Autocomplete current suggestion and prevent
    // unnecessary lookup.
    if (keyCode === Keys.TAB) {
      event.preventDefault();
      return this.store.dispatch(select());
    }

    _.defer(() => {
      const content = this.editor.getContent({
        format: 'text'
      });

      const re = /@\w+\b(?! *.)/;
      const match = re.exec(content);

      if (match) {
        const mentions = _.last(twitter.extractMentionsWithIndices(content));

        if (mentions && this.isFocused) {
          this.store.dispatch(query(mentions.screenName));
        }
      }
    });
  }

  /**
   * Handler for backspace presses. Dispatches back to store with request
   * to reset the current query and matches.
   *
   * @param  {jQuery.Event}
   */
  handleBackspaceKey(event) {
    const keyCode = event.which || event.keyCode;

    if (keyCode === Keys.BACKSPACE) {

      // Check to see if the surrounding area contains an @ and remove.
      const $node = $(this.editor.selection.getNode()).closest('.mention');

      if ($node.length) {
        const mention = $node
          .first()
          .text()
          .replace(/(?:@|_)/g, ' ')
          .trim();

        this.store.dispatch(remove(mention));

        // Remove @mention node from editor
        $node.remove();
      }
    }
  }
}
<|endoftext|>


<commit_before>/**
 * Use cursor position code from https://github.com/yields/editable (MIT license)
 */

define(['react', 'rangy'], function(React, rangy) {

function isDefined(arg) {
  if (typeof arg != 'undefined')
    return true;
  return false;
}

var LexerDomainClassname = {
  1: "operators",
  2: "constants",
  3: "string",
  4: "keywords",
  5: "comments",
  6: "variable_name",
  7: "other",
  8: "whitespace",
  9: "invalid",
  10: "preprocessor",
}

var Editor = React.createClass({

  propTypes: {
    linesVisible: React.PropTypes.number,
    columnsVisible: React.PropTypes.number,

    text: React.PropTypes.instanceOf(Rope).isRequired,
    onChange: React.PropTypes.func.isRequired,
    autoFocus: React.PropTypes.bool
  },

  getDefaultProps: function() {
    return {
      autoFocus: true,
      linesVisible: 40,
      columnsVisible: 80,
    };
  },

  getInitialState: function(){
    return {
      firstLinePos: 1,
      firstColumnPos: 0,
      virtualCursor: {line: 1, column: 0},
      keyEvent: null,
      cursorHandled: false,
    };
  },

  componentDidMount: function(){
    if (this.props.autoFocus) {
      this.autofocus();
    }
  },

  componentDidUpdate: function(prevProps, prevState){

    // set cursor
    //cursorShowedPosition = this._getCursorShowPosition()
    // 1. Get cursor position relative to showed buffer
    // 2.a Get the
 node belongs to the line
    // 2.b Find the nested  tag belongs to the relative position in the line
    // 3. Set cursor to the specified selection
    this._showCursor();

    this.state.willBeRerendered = false;
  },

  autofocus: function(){
    this.getDOMNode().focus();
    this._showCursor();
  },

  render: function() {
    var content = [];
    var id, lexems, codeLine;
    for (var iLine = this.state.firstLinePos; iLine < this.state.firstLinePos + this.props.linesVisible; iLine += 1) {
      codeLine = [];
      lexems = this.props.text.getLexems(
            RopePosition(iLine, this.state.firstColumnPos),
            RopePosition(iLine, this.state.firstColumnPos + this.props.columnsVisible)
          )

      // assume at least one (newline symbol) is presented
      for (var i = 0; i < lexems.length; i += 1) {
        codeLine.push(
          {lexems[i].string}
        )
      }
      if (lexems.length == 0)
        codeLine = " ";
      id = 'codeLine_' + iLine;
      content.push(

{codeLine}

      )
    }

    return (

        {content}

{this.state.virtualCursor.line} - {this.state.virtualCursor.column}/{this.state.firstColumnPos}

    )
  },

  onMouseDown: function(e) {

  },

  onSelect: function(e) {
    // 1. Determine the line when cursor caret is located
    // 2. Determine real string column
    // 3. Update the virtual cursor

    if (this.state.cursorHandled) {
      // handle manually by other methods
      this.state.cursorHandled = false;
      return;
    }

    var lineNode = this._getLineNodeFromNestedNode(rangy.getSelection().getRangeAt(0).startContainer);
    this.state.virtualCursor.line = this._extractLineNumber(lineNode);
    this.state.virtualCursor.column = this._getRealCursorColumnFromRelative(this._getCursorPosition(lineNode));

    // For DEBUG
    this.forceUpdate();
  },

  onPaste: function(e){
    // handle paste manually to ensure we unset our placeholder
    e.preventDefault();

    var data = e.clipboardData.getData('text/plain');

    // prevent text longer then our max-length from being
    // added
    this.setText(data);
  },

  onKeyDown: function(e) {
    var key = e.key;
    var cursorReal = this._getCursorOnRealLine();
    this.state.keyEvent = e;

    if (! (key == 'ArrowUp' || key == 'ArrowDown' || key == 'PageUp' || key == 'PageDown'))
      this.state.virtualCursor = cursorReal;

    if (key == 'ArrowLeft') {
      if (this._cursorOnLineStart()) {
        if (this._previousLineExist())
          this._moveCursorToLineEnd(cursorReal.line - 1)
      } else // not on real line start
        this._moveCursorLeft()

    } else if (key == 'ArrowRight') {
      if (this._cursorOnLineEnd()) {
        if (this._nextLineExist())
          this._moveCursorToLineStart(cursorReal.line + 1)
      } else // not on real line end
        this._moveCursorRight()

    } else if (key == 'ArrowUp') {
      if (this._previousLineExist())
        this._moveCursorUp()
      else // prev line does not exist
        this._moveCursorToLineStart(cursorReal.line)

    } else if (key == 'ArrowDown') {
      if (this._nextLineExist())
        this._moveCursorDown()
      else // next line does not exist
        this._moveCursorToLineEnd(cursorReal.line)

    } else if (key == 'Home') {
      this._moveCursorToLineStart(cursorReal.line)

    } else if (key == 'End') {
      this._moveCursorToLineEnd(cursorReal.line)

    } else if (key == 'PageUp') {
      if (this._previousLineExist())
        this._moveCursorUp(this.props.linesVisible)
      else
        this._moveCursorToLineStart(cursorReal.line)

    } else if (key == 'PageDown') {
      if (this._nextLineExist())
        this._moveCursorDown(this.props.linesVisible)
      else
        this._moveCursorToLineEnd(cursorReal.line)

    } else if (key == 'Backspace') {
      if (this._cursorOnLineStart()) {
        if (this._previousLineExist()) {
          var previousLine = cursorReal.line - 1,
              previousLineLength = this.props.text.getLineLength(previousLine);
          this._moveCursorToLineEnd(previousLine);
          this.props.text.remove(
            RopePosition(previousLine, previousLineLength),
            RopePosition(previousLine, previousLineLength));
        }
      } else { // not on real line start
        var symbolPosition = RopePosition(cursorReal.line, cursorReal.column - 1);
        this.props.text.remove(symbolPosition, symbolPosition);
        this._moveCursorLeft();
      }
      this._preventDefaultEventAction(e)
      this.forceUpdate();

    } else if (key == 'Delete') {
      if (this._cursorOnLineEnd()) {
        if (this._nextLineExist()) {
          var symbolPosition = RopePosition(cursorReal.line, this.props.text.getLineLength(cursorReal.line))
          this.props.text.remove(symbolPosition, symbolPosition);
        }
      } else { // not on real line end
        var symbolPosition = RopePosition(cursorReal.line, cursorReal.column);
        this.props.text.remove(symbolPosition, symbolPosition);
      }
      this._preventDefaultEventAction(e)
      this.forceUpdate();

    } else if (key == 'Tab') {

    }

    if (!this.state.willBeRerendered && (key == 'ArrowUp' || key == 'ArrowDown' || key == 'PageUp' || key == 'PageDown')) {
      // show cursor at end of line if virtual cursor > line length
      this._preventDefaultEventAction(e)
      this._showCursor();
    }

  },

  onKeyPress: function(e){
    var key = e.key;
    var cursorReal = this._getCursorOnRealLine();
    this.state.keyEvent = e;

    if (key == 'Enter')
      key = "\n";

    this.props.text.insert(RopePosition(cursorReal.line, cursorReal.column), key)
    if (key == "\n")
      this._moveCursorToLineStart(cursorReal.line + 1);
    else
      this._moveCursorRight();
    this._preventDefaultEventAction(e)
    this.forceUpdate();
  },

  _cursorOnLineEnd: function() {
    return this.state.virtualCursor.column === this.props.text.getLineLength(this.state.virtualCursor.line)
  },

  _cursorOnLineStart: function() {
    return this.state.virtualCursor.column === 0;
  },

  _extractLineNumber: function(node) {
    return Number(node.dataset.reactid.split('$')[1].split('_')[1]);
  },

  _getCursorOnRealLine: function() {
    var line = this.state.virtualCursor.line;
    return {
      line: line,
      column: Math.min(this.state.virtualCursor.column, this.props.text.getLineLength(line))
    }
  },

  _getLineNodeFromNestedNode: function(node) {
    while (!node.classList || !node.classList.length || node.classList[0] != 'codeLine')
      node = node.parentNode;
    return node
  },

  _getRealCursorColumnFromRelative: function(relativeColumn) {
    return this.state.firstColumnPos + relativeColumn;
  },

  _moveCursorDown: function(lines) {
    this.state.cursorHandled = true;
    if (!isDefined(lines)) lines = 1;
    var nextLine = Math.min(this.state.virtualCursor.line + lines, this.props.text.getLinesCount());
    var toRight = this._getCursorOnRealLine().column <= this.props.text.getLineLength(nextLine);
    this.state.virtualCursor.line = nextLine;
    this._updateShowBuffer({toUp: 0, toRight: toRight})
  },

  _moveCursorLeft: function() {
    this.state.cursorHandled = true;
    this.state.virtualCursor.column -= 1;
    this._updateShowBuffer({toUp: 1, toRight: 0});
  },

  _moveCursorRight: function() {
    this.state.cursorHandled = true;
    this.state.virtualCursor.column += 1;
    this._updateShowBuffer({toUp: 0, toRight: 1});
  },

  _moveCursorToLineEnd: function(line) {
    this.state.cursorHandled = true;
    var toUp = this.state.virtualCursor.line > line;
    var length = this.props.text.getLineLength(line)
    var toRight = this.state.virtualCursor.column < length
    this.state.virtualCursor = {line: line, column: length};
    this._updateShowBuffer({toUp: toUp, toRight: toRight})
  },

  _moveCursorToLineStart: function(line) {
    this.state.cursorHandled = true;
    var toUp = this.state.virtualCursor.line > line;
    this.state.virtualCursor = {line: line, column: 0};
    this._updateShowBuffer({toUp: toUp, toRight: 0})
  },

  _moveCursorUp: function (lines) {
    this.state.cursorHandled = true;
    if (!isDefined(lines)) lines = 1;
    var previousLine = Math.max(this.state.virtualCursor.line - lines, 1);
    var toRight = this._getCursorOnRealLine().column <= this.props.text.getLineLength(previousLine);
    this.state.virtualCursor.line = previousLine;
    this._updateShowBuffer({toUp: 1, toRight: toRight})
  },

  _nextLineExist: function() {
    return this.state.virtualCursor.line + 1 <= this.props.text.getLinesCount()
  },

  _preventDefaultEventAction: function(event) {
    event.preventDefault();
    event.stopPropagation();
  },

  _previousLineExist: function() {
    return this.state.virtualCursor.line > 1;
  },

  /**
  * Set / get caret position with `el`.
  *
  * @param {Element} el
  * @param {Number} at
  * @return {Number}
  * @api private
  */

  _getCursorPosition: function(node) {
    var range = window.getSelection().getRangeAt(0);
    var clone = range.cloneRange();
    clone.selectNodeContents(node);
    clone.setEnd(range.endContainer, range.endOffset);
    return clone.toString().length;
  },

  _setCursorPosition: function(node, pos) {
    var length = 0, abort;

    this._visitNodes(node, function(node){
      if (3 != node.nodeType) return;
      length += node.textContent.length;
      if (length >= pos) {
        if (abort) return;
        abort = true;
        var sel = document.getSelection();
        var range = document.createRange();
        var sub = length - node.textContent.length;
        range.setStart(node, pos - sub);
        range.setEnd(node, pos - sub);
        sel.removeAllRanges();
        sel.addRange(range);
        return true;
      }
    });
  },

  /**
   * Walk all text nodes of `node`.
   *
   * @param {Element|Node} node
   * @param {Function} fn
   * @api private
   */

  _visitNodes: function (node, fn){
    var nodes = node.childNodes;
    for (var i = 0; i < nodes.length; ++i) {
      if (fn(nodes[i])) break;
      this._visitNodes(nodes[i], fn);
    }
  },

  _showCursor: function(){
    if (!rangy.initialized)
      rangy.init();
    // 1. Get cursor position relative to showed buffer
    // 2.a Get the
 node belongs to the line
    // 2.b Find the nested  tag belongs to the relative position in the line
    // 3. Set cursor to the specified selection

    var relativeColumn = Math.min(this.state.virtualCursor.column, this.props.text.getLineLength(this.state.virtualCursor.line)) - this.state.firstColumnPos;
    var node = React.findDOMNode(this.refs['codeLine_' + this.state.virtualCursor.line]);

    node.focus();
    this._setCursorPosition(node, relativeColumn);
  },

  // Virtual cursor represents maximum columned cursor used while movements
  // This cursor not represented real shown cursor and the positions isn't real RopePosition
  //  because it not in real lines bounds. But it represents the absolute position in line, not
  //  relative to shown in GUI.
  // This info is used while real cursor and selection showing, dynamically changes before
  _updateVirtualCursor: function(prevPosition, nextPosition) {

  },

  _updateShowBuffer: function(args) {
    // update based on virtual cursor info and real strings
    var cursor = this._getCursorOnRealLine(this.state.virtualCursor);
    var needUpdate = false;

    if (!(this.state.firstLinePos <= cursor.line && cursor.line <= this.state.firstLinePos + this.props.linesVisible - 1)) {
      if (args.toUp)
        this.state.firstLinePos = cursor.line
      else // toDown
        this.state.firstLinePos = Math.max(cursor.line - this.props.linesVisible + 1, 0)
      needUpdate = true;
    }

    if (!(this.state.firstColumnPos <= cursor.column && cursor.column <= this.state.firstColumnPos + this.props.columnsVisible)) {
      if (args.toRight)
          this.state.firstColumnPos = Math.max(cursor.column - this.props.columnsVisible, 0)
      else // to left
        this.state.firstColumnPos = cursor.column
      needUpdate = true;
    }
    if (needUpdate) {
      // prevent default action to stop additional (native) caret movement
      this.state.willBeRerendered = true;
      this._preventDefaultEventAction(this.state.keyEvent);
      this.forceUpdate();
    }
  },
});

  return Editor
});<commit_msg>Fix: turn off spell checking<commit_after>/**
 * Use cursor position code from https://github.com/yields/editable (MIT license)
 */

define(['react', 'rangy'], function(React, rangy) {

function isDefined(arg) {
  if (typeof arg != 'undefined')
    return true;
  return false;
}

var LexerDomainClassname = {
  1: "operators",
  2: "constants",
  3: "string",
  4: "keywords",
  5: "comments",
  6: "variable_name",
  7: "other",
  8: "whitespace",
  9: "invalid",
  10: "preprocessor",
}

var Editor = React.createClass({

  propTypes: {
    linesVisible: React.PropTypes.number,
    columnsVisible: React.PropTypes.number,

    text: React.PropTypes.instanceOf(Rope).isRequired,
    onChange: React.PropTypes.func.isRequired,
    autoFocus: React.PropTypes.bool
  },

  getDefaultProps: function() {
    return {
      autoFocus: true,
      linesVisible: 40,
      columnsVisible: 80,
    };
  },

  getInitialState: function(){
    return {
      firstLinePos: 1,
      firstColumnPos: 0,
      virtualCursor: {line: 1, column: 0},
      keyEvent: null,
      cursorHandled: false,
    };
  },

  componentDidMount: function(){
    if (this.props.autoFocus) {
      this.autofocus();
    }
  },

  componentDidUpdate: function(prevProps, prevState){

    // set cursor
    //cursorShowedPosition = this._getCursorShowPosition()
    // 1. Get cursor position relative to showed buffer
    // 2.a Get the
 node belongs to the line
    // 2.b Find the nested  tag belongs to the relative position in the line
    // 3. Set cursor to the specified selection
    this._showCursor();

    this.state.willBeRerendered = false;
  },

  autofocus: function(){
    this.getDOMNode().focus();
    this._showCursor();
  },

  render: function() {
    var content = [];
    var id, lexems, codeLine;
    for (var iLine = this.state.firstLinePos; iLine < this.state.firstLinePos + this.props.linesVisible; iLine += 1) {
      codeLine = [];
      lexems = this.props.text.getLexems(
            RopePosition(iLine, this.state.firstColumnPos),
            RopePosition(iLine, this.state.firstColumnPos + this.props.columnsVisible)
          )

      // assume at least one (newline symbol) is presented
      for (var i = 0; i < lexems.length; i += 1) {
        codeLine.push(
          {lexems[i].string}
        )
      }
      if (lexems.length == 0)
        codeLine = " ";
      id = 'codeLine_' + iLine;
      content.push(

{codeLine}

      )
    }

    return (

        {content}

{this.state.virtualCursor.line} - {this.state.virtualCursor.column}/{this.state.firstColumnPos}

    )
  },

  onMouseDown: function(e) {

  },

  onSelect: function(e) {
    // 1. Determine the line when cursor caret is located
    // 2. Determine real string column
    // 3. Update the virtual cursor

    if (this.state.cursorHandled) {
      // handle manually by other methods
      this.state.cursorHandled = false;
      return;
    }

    var lineNode = this._getLineNodeFromNestedNode(rangy.getSelection().getRangeAt(0).startContainer);
    this.state.virtualCursor.line = this._extractLineNumber(lineNode);
    this.state.virtualCursor.column = this._getRealCursorColumnFromRelative(this._getCursorPosition(lineNode));

    // For DEBUG
    this.forceUpdate();
  },

  onPaste: function(e){
    // handle paste manually to ensure we unset our placeholder
    e.preventDefault();

    var data = e.clipboardData.getData('text/plain');

    // prevent text longer then our max-length from being
    // added
    this.setText(data);
  },

  onKeyDown: function(e) {
    var key = e.key;
    var cursorReal = this._getCursorOnRealLine();
    this.state.keyEvent = e;

    if (! (key == 'ArrowUp' || key == 'ArrowDown' || key == 'PageUp' || key == 'PageDown'))
      this.state.virtualCursor = cursorReal;

    if (key == 'ArrowLeft') {
      if (this._cursorOnLineStart()) {
        if (this._previousLineExist())
          this._moveCursorToLineEnd(cursorReal.line - 1)
      } else // not on real line start
        this._moveCursorLeft()

    } else if (key == 'ArrowRight') {
      if (this._cursorOnLineEnd()) {
        if (this._nextLineExist())
          this._moveCursorToLineStart(cursorReal.line + 1)
      } else // not on real line end
        this._moveCursorRight()

    } else if (key == 'ArrowUp') {
      if (this._previousLineExist())
        this._moveCursorUp()
      else // prev line does not exist
        this._moveCursorToLineStart(cursorReal.line)

    } else if (key == 'ArrowDown') {
      if (this._nextLineExist())
        this._moveCursorDown()
      else // next line does not exist
        this._moveCursorToLineEnd(cursorReal.line)

    } else if (key == 'Home') {
      this._moveCursorToLineStart(cursorReal.line)

    } else if (key == 'End') {
      this._moveCursorToLineEnd(cursorReal.line)

    } else if (key == 'PageUp') {
      if (this._previousLineExist())
        this._moveCursorUp(this.props.linesVisible)
      else
        this._moveCursorToLineStart(cursorReal.line)

    } else if (key == 'PageDown') {
      if (this._nextLineExist())
        this._moveCursorDown(this.props.linesVisible)
      else
        this._moveCursorToLineEnd(cursorReal.line)

    } else if (key == 'Backspace') {
      if (this._cursorOnLineStart()) {
        if (this._previousLineExist()) {
          var previousLine = cursorReal.line - 1,
              previousLineLength = this.props.text.getLineLength(previousLine);
          this._moveCursorToLineEnd(previousLine);
          this.props.text.remove(
            RopePosition(previousLine, previousLineLength),
            RopePosition(previousLine, previousLineLength));
        }
      } else { // not on real line start
        var symbolPosition = RopePosition(cursorReal.line, cursorReal.column - 1);
        this.props.text.remove(symbolPosition, symbolPosition);
        this._moveCursorLeft();
      }
      this._preventDefaultEventAction(e)
      this.forceUpdate();

    } else if (key == 'Delete') {
      if (this._cursorOnLineEnd()) {
        if (this._nextLineExist()) {
          var symbolPosition = RopePosition(cursorReal.line, this.props.text.getLineLength(cursorReal.line))
          this.props.text.remove(symbolPosition, symbolPosition);
        }
      } else { // not on real line end
        var symbolPosition = RopePosition(cursorReal.line, cursorReal.column);
        this.props.text.remove(symbolPosition, symbolPosition);
      }
      this._preventDefaultEventAction(e)
      this.forceUpdate();

    } else if (key == 'Tab') {

    }

    if (!this.state.willBeRerendered && (key == 'ArrowUp' || key == 'ArrowDown' || key == 'PageUp' || key == 'PageDown')) {
      // show cursor at end of line if virtual cursor > line length
      this._preventDefaultEventAction(e)
      this._showCursor();
    }

  },

  onKeyPress: function(e){
    var key = e.key;
    var cursorReal = this._getCursorOnRealLine();
    this.state.keyEvent = e;

    if (key == 'Enter')
      key = "\n";

    this.props.text.insert(RopePosition(cursorReal.line, cursorReal.column), key)
    if (key == "\n")
      this._moveCursorToLineStart(cursorReal.line + 1);
    else
      this._moveCursorRight();
    this._preventDefaultEventAction(e)
    this.forceUpdate();
  },

  _cursorOnLineEnd: function() {
    return this.state.virtualCursor.column === this.props.text.getLineLength(this.state.virtualCursor.line)
  },

  _cursorOnLineStart: function() {
    return this.state.virtualCursor.column === 0;
  },

  _extractLineNumber: function(node) {
    return Number(node.dataset.reactid.split('$')[1].split('_')[1]);
  },

  _getCursorOnRealLine: function() {
    var line = this.state.virtualCursor.line;
    return {
      line: line,
      column: Math.min(this.state.virtualCursor.column, this.props.text.getLineLength(line))
    }
  },

  _getLineNodeFromNestedNode: function(node) {
    while (!node.classList || !node.classList.length || node.classList[0] != 'codeLine')
      node = node.parentNode;
    return node
  },

  _getRealCursorColumnFromRelative: function(relativeColumn) {
    return this.state.firstColumnPos + relativeColumn;
  },

  _moveCursorDown: function(lines) {
    this.state.cursorHandled = true;
    if (!isDefined(lines)) lines = 1;
    var nextLine = Math.min(this.state.virtualCursor.line + lines, this.props.text.getLinesCount());
    var toRight = this._getCursorOnRealLine().column <= this.props.text.getLineLength(nextLine);
    this.state.virtualCursor.line = nextLine;
    this._updateShowBuffer({toUp: 0, toRight: toRight})
  },

  _moveCursorLeft: function() {
    this.state.cursorHandled = true;
    this.state.virtualCursor.column -= 1;
    this._updateShowBuffer({toUp: 1, toRight: 0});
  },

  _moveCursorRight: function() {
    this.state.cursorHandled = true;
    this.state.virtualCursor.column += 1;
    this._updateShowBuffer({toUp: 0, toRight: 1});
  },

  _moveCursorToLineEnd: function(line) {
    this.state.cursorHandled = true;
    var toUp = this.state.virtualCursor.line > line;
    var length = this.props.text.getLineLength(line)
    var toRight = this.state.virtualCursor.column < length
    this.state.virtualCursor = {line: line, column: length};
    this._updateShowBuffer({toUp: toUp, toRight: toRight})
  },

  _moveCursorToLineStart: function(line) {
    this.state.cursorHandled = true;
    var toUp = this.state.virtualCursor.line > line;
    this.state.virtualCursor = {line: line, column: 0};
    this._updateShowBuffer({toUp: toUp, toRight: 0})
  },

  _moveCursorUp: function (lines) {
    this.state.cursorHandled = true;
    if (!isDefined(lines)) lines = 1;
    var previousLine = Math.max(this.state.virtualCursor.line - lines, 1);
    var toRight = this._getCursorOnRealLine().column <= this.props.text.getLineLength(previousLine);
    this.state.virtualCursor.line = previousLine;
    this._updateShowBuffer({toUp: 1, toRight: toRight})
  },

  _nextLineExist: function() {
    return this.state.virtualCursor.line + 1 <= this.props.text.getLinesCount()
  },

  _preventDefaultEventAction: function(event) {
    event.preventDefault();
    event.stopPropagation();
  },

  _previousLineExist: function() {
    return this.state.virtualCursor.line > 1;
  },

  /**
  * Set / get caret position with `el`.
  *
  * @param {Element} el
  * @param {Number} at
  * @return {Number}
  * @api private
  */

  _getCursorPosition: function(node) {
    var range = window.getSelection().getRangeAt(0);
    var clone = range.cloneRange();
    clone.selectNodeContents(node);
    clone.setEnd(range.endContainer, range.endOffset);
    return clone.toString().length;
  },

  _setCursorPosition: function(node, pos) {
    var length = 0, abort;

    this._visitNodes(node, function(node){
      if (3 != node.nodeType) return;
      length += node.textContent.length;
      if (length >= pos) {
        if (abort) return;
        abort = true;
        var sel = document.getSelection();
        var range = document.createRange();
        var sub = length - node.textContent.length;
        range.setStart(node, pos - sub);
        range.setEnd(node, pos - sub);
        sel.removeAllRanges();
        sel.addRange(range);
        return true;
      }
    });
  },

  /**
   * Walk all text nodes of `node`.
   *
   * @param {Element|Node} node
   * @param {Function} fn
   * @api private
   */

  _visitNodes: function (node, fn){
    var nodes = node.childNodes;
    for (var i = 0; i < nodes.length; ++i) {
      if (fn(nodes[i])) break;
      this._visitNodes(nodes[i], fn);
    }
  },

  _showCursor: function(){
    if (!rangy.initialized)
      rangy.init();
    // 1. Get cursor position relative to showed buffer
    // 2.a Get the
 node belongs to the line
    // 2.b Find the nested  tag belongs to the relative position in the line
    // 3. Set cursor to the specified selection

    var relativeColumn = Math.min(this.state.virtualCursor.column, this.props.text.getLineLength(this.state.virtualCursor.line)) - this.state.firstColumnPos;
    var node = React.findDOMNode(this.refs['codeLine_' + this.state.virtualCursor.line]);

    node.focus();
    this._setCursorPosition(node, relativeColumn);
  },

  // Virtual cursor represents maximum columned cursor used while movements
  // This cursor not represented real shown cursor and the positions isn't real RopePosition
  //  because it not in real lines bounds. But it represents the absolute position in line, not
  //  relative to shown in GUI.
  // This info is used while real cursor and selection showing, dynamically changes before
  _updateVirtualCursor: function(prevPosition, nextPosition) {

  },

  _updateShowBuffer: function(args) {
    // update based on virtual cursor info and real strings
    var cursor = this._getCursorOnRealLine(this.state.virtualCursor);
    var needUpdate = false;

    if (!(this.state.firstLinePos <= cursor.line && cursor.line <= this.state.firstLinePos + this.props.linesVisible - 1)) {
      if (args.toUp)
        this.state.firstLinePos = cursor.line
      else // toDown
        this.state.firstLinePos = Math.max(cursor.line - this.props.linesVisible + 1, 0)
      needUpdate = true;
    }

    if (!(this.state.firstColumnPos <= cursor.column && cursor.column <= this.state.firstColumnPos + this.props.columnsVisible)) {
      if (args.toRight)
          this.state.firstColumnPos = Math.max(cursor.column - this.props.columnsVisible, 0)
      else // to left
        this.state.firstColumnPos = cursor.column
      needUpdate = true;
    }
    if (needUpdate) {
      // prevent default action to stop additional (native) caret movement
      this.state.willBeRerendered = true;
      this._preventDefaultEventAction(this.state.keyEvent);
      this.forceUpdate();
    }
  },
});

  return Editor
});<|endoftext|>


<commit_before>var Transaction = require('ethereumjs-tx');
var _ = require('lodash');
var util = require('../util');

module.exports = {
  init: function(optionsOrRlp) {
    if (optionsOrRlp) {
      if (_.isString(optionsOrRlp)) {
        this.tx = new Transaction(util.toBuffer(optionsOrRlp));
        this.from = util.toHex(this.tx.getSenderAddress());
        this.to = this.tx.to.length > 0 ? util.toHex(this.tx.to) : null;
        this.nonce = util.toBigNumber(this.tx.nonce);
        this.gasLimit = util.toBigNumber(this.tx.gasLimit);
        this.gasPrice = util.toBigNumber(this.tx.gasPrice);
        this.value = util.toBigNumber(this.tx.value);
      } else
        _.assign(this, optionsOrRlp);
    }
    return this;
  },
  getTx: function() {
    if (this.tx) return this.tx;
    var tx = new Transaction({
      nonce: !this.nonce || this.nonce.isZero() ? null : this.nonce.toNumber(),
      gasPrice: this.gasPrice.toNumber(),
      gasLimit: this.gasLimit.toNumber(),
      to: this.to ? util.toBuffer(this.to) : null,
      value: !this.value || this.value.isZero() ? null : this.value.toNumber(),
      data: this.data ? util.toBuffer(this.data) : null
    });
    tx.sign(util.toBuffer(this.pkey));
    return tx;
  }
};
<commit_msg>Fix the bug with data field parsing<commit_after>var Transaction = require('ethereumjs-tx');
var _ = require('lodash');
var util = require('../util');

module.exports = {
  init: function(optionsOrRlp) {
    if (optionsOrRlp) {
      if (_.isString(optionsOrRlp)) {
        this.tx = new Transaction(util.toBuffer(optionsOrRlp));
        this.from = util.toHex(this.tx.getSenderAddress());
        this.to = this.tx.to.length > 0 ? util.toHex(this.tx.to) : null;
        this.nonce = util.toBigNumber(this.tx.nonce);
        this.gasLimit = util.toBigNumber(this.tx.gasLimit);
        this.gasPrice = util.toBigNumber(this.tx.gasPrice);
        this.value = util.toBigNumber(this.tx.value);
        this.data = this.tx.data.length > 0 ? util.toHex(this.tx.data) : null;
      } else
        _.assign(this, optionsOrRlp);
    }
    return this;
  },
  getTx: function() {
    if (this.tx) return this.tx;
    var tx = new Transaction({
      nonce: !this.nonce || this.nonce.isZero() ? null : this.nonce.toNumber(),
      gasPrice: this.gasPrice.toNumber(),
      gasLimit: this.gasLimit.toNumber(),
      to: this.to ? util.toBuffer(this.to) : null,
      value: !this.value || this.value.isZero() ? null : this.value.toNumber(),
      data: this.data ? util.toBuffer(this.data) : null
    });
    tx.sign(util.toBuffer(this.pkey));
    return tx;
  }
};
<|endoftext|>


<commit_before>const timetrack = require('./timetrack');

module.exports = (pluginContext) => {
    return (projectName, env = {}) => {
        return new Promise((resolve, reject) => {
            try {
            timetrack.loadData();
            timetrack.registerStart(projectName);
            const result = timetrack.save();
            } catch (e) {
                alert('fout: ' + e);
            }
            resolve(result);
        });
    }
}
<commit_msg>Remove debug code<commit_after>const timetrack = require('./timetrack');

module.exports = (pluginContext) => {
    return (projectName, env = {}) => {
        return new Promise((resolve, reject) => {
            timetrack.loadData();
            timetrack.registerStart(projectName);
            const result = timetrack.save();
            resolve(result);
        });
    }
}
<|endoftext|>


<commit_before>define([
  'require',
  './lib/widget',
  './lib/store',
  './lib/sync',
  './lib/wireClient',
  './lib/nodeConnect',
  './lib/util',
  './lib/webfinger',
  './lib/foreignClient',
  './lib/baseClient'
], function(require, widget, store, sync, wireClient, nodeConnect, util, webfinger, foreignClient, BaseClient) {

  "use strict";

  var claimedModules = {}, modules = {}, moduleNameRE = /^[a-z]+$/;

  var logger = util.getLogger('base');

  // Namespace: remoteStorage
  var remoteStorage =  {

    //
    // Method: defineModule
    //
    // Define a new module, with given name.
    // Module names MUST be unique. The given builder will be called
    // immediately, with two arguments, which are both instances of
    // <BaseClient>. The first accesses the private section of a modules
    // storage space, the second the public one. The public area can
    // be read by any client (not just an authenticated one), while
    // it can only be written by an authenticated client with read-write
    // access claimed on it.
    //
    // The builder is expected to return an object, as described under
    // <getModuleInfo>.
    //
    // Parameter:
    //   moduleName - Name of the module to define. MUST be a-z and all lowercase.
    //   builder    - Builder function that holds the module definition.
    //
    // Example:
    //   (start code)
    //
    //   remoteStorage.defineModule('beers', function(privateClient, publicClient) {
    //
    //     function nameToKey(name) {
    //       return name.replace(/\s/, '-');
    //     }
    //
    //     return {
    //       exports: {
    //
    //         addBeer: function(name) {
    //           privateClient.storeObject('beer', nameToKey(name), {
    //             name: name,
    //             drinkCount: 0
    //           });
    //         },
    //
    //         logDrink: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           beer.drinkCount++;
    //           privateClient.storeObject('beer', key, beer);
    //         },
    //
    //         publishBeer: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           publicClient.storeObject('beer', key, beer);
    //         }
    //
    //       }
    //     }
    //   });
    //
    //   // to use that code from an app, you need to add:
    //
    //   remoteStorage.claimAccess('beers', 'rw');
    //
    //   remoteStorage.displayWidget(/* see documentation */)
    //
    //   remoteStorage.addBeer('<replace-with-favourite-beer-kind>');
    //
    //   (end code)
    //
    // See also:
    //   <BaseClient>
    //
    defineModule: function(moduleName, builder) {

      if(! moduleNameRE.test(moduleName)) {
        throw 'Invalid moduleName: "'+moduleName+'", only a-z lowercase allowed.';
      }

      var module = builder(
        // private client:
        new BaseClient(moduleName, false),
        // public client:
        new BaseClient(moduleName, true)
      );
      modules[moduleName] = module;
      this[moduleName] = module.exports;
    },

    //
    // Method: getModuleList
    //
    // list known module names
    //
    // Returns:
    //   Array of module names.
    //
    getModuleList: function() {
      return Object.keys(modules);
    },

    // Method: getClaimedModuleList
    //
    // list of modules currently claimed access on
    //
    // Returns:
    //   Array of module names.
    //
    getClaimedModuleList: function() {
      return Object.keys(claimedModules);
    },

    //
    // Method: getModuleInfo
    //
    // Retrieve meta-information about a given module.
    //
    // If the module doesn't exist, the result will be undefined.
    //
    // Parameters:
    //   moduleName - name of the module
    //
    // Returns:
    //   An object, usually containing the following keys,
    //   * exports - don't ever use this. it's basically the module's instance.
    //   * name - the name of the module, but you knew that already.
    //   * dataHints - an object, describing internas about the module.
    //
    //
    //   Some of the dataHints used are:
    //
    //     objectType <type> - description of an object
    //                         type implemented by the module
    //     "objectType message" - (example)
    //
    //     <attributeType> <objectType>#<attribute> - description of an attribute
    //
    //     "string message#subject" - (example)
    //
    //     directory <path> - description of a path's purpose
    //
    //     "directory documents/notes/" - (example)
    //
    //     item <path> - description of a specific item
    //
    //     "item documents/notes/calendar" - (example)
    //
    //   Hope this helps.
    //
    getModuleInfo: function(moduleName) {
      return modules[moduleName];
    },

    //
    // Method: claimAccess
    //
    // Either:
    //   <claimAccess(moduleName, claim)>
    //
    // Or:
    //   <claimAccess(moduleClaimMap)>
    //
    //
    //
    // Method: claimAccess(moduleName, claim)
    //
    // Claim access on a single module
    //
    // You need to claim access to a module before you can
    // access data from it.
    //
    // Parameters:
    //   moduleName - name of the module. For a list of defined modules, use <getModuleList>
    //   claim      - permission to claim, either *r* (read-only) or *rw* (read-write)
    //
    // Example:
    //   > remoteStorage.claimAccess('contacts', 'r');
    //
    //
    //
    // Method: claimAccess(moduleClaimMap)
    //
    // Claim access to multiple modules.
    //
    // Parameters:
    //   moduleClaimMap - a JSON object with module names as keys and claims as values.
    //
    // Example:
    //   > remoteStorage.claimAccess({
    //   >   contacts: 'r',
    //   >   documents: 'rw',
    //   >   money: 'r'
    //   > });
    //
    claimAccess: function(moduleName, mode) {

      var modeTestRegex = /^rw?$/;
      function testMode(moduleName, mode) {
        if(!modeTestRegex.test(mode)) {
          throw "Claimed access to module '" + moduleName + "' but mode not correctly specified ('" + mode + "').";
        }
      }

      var moduleObj;
      if(typeof moduleName === 'object') {
        moduleObj = moduleName;
      } else {
        testMode(moduleName, mode);
        moduleObj = {};
        moduleObj[moduleName] = mode;
      }
      for(var _moduleName in moduleObj) {
        var _mode = moduleObj[_moduleName];
        testMode(_moduleName, _mode);
        this.claimModuleAccess(_moduleName, _mode);
      }
    },

    // PRIVATE
    claimModuleAccess: function(moduleName, mode) {
      logger.debug('claimModuleAccess', moduleName, mode);
      if(!(moduleName in modules)) {
        throw "Module not defined: " + moduleName;
      }

      if(moduleName in claimedModules) {
        return;
      }

      if(moduleName == 'root') {
        moduleName = '';
        widget.addScope('', mode);
        store.setNodeAccess('/', mode);
      } else {
        widget.addScope(moduleName, mode);
        store.setNodeAccess('/'+moduleName+'/', mode);
        store.setNodeAccess('/public/'+moduleName+'/', mode);
      }
      claimedModules[moduleName] = true;
    },

    // PRIVATE
    setBearerToken: function(bearerToken, claimedScopes) {
      wireClient.setBearerToken(bearerToken);
    },

    getBearerToken: function() {
      return wireClient.getBearerToken();
    },

    disconnectRemote : wireClient.disconnectRemote,

    //
    // Method: flushLocal()
    //
    // Forget this ever happened.
    //
    // Delete all locally stored data.
    // This doesn't clear localStorage, just removes everything
    // remoteStorage.js saved there. Other data your app might
    // have put into localStorage stays there.
    //
    // Call this method to implement "logout".
    //
    // If you are using the widget (which you should!), you don't need this.
    //
    // Example:
    //   > remoteStorage.flushLocal();
    //
    flushLocal       : store.forgetAll,

    //
    // Method: fullSync
    //
    // Synchronize local <-> remote storage.
    //
    // Syncing starts at the access roots (the once you claimed using claimAccess)
    // and moves down the directory tree.
    // The actual changes to either local or remote storage happen in the
    // future, so you should attach change handlers on the modules you're
    // interested in.
    //
    // Parameters:
    //   path - relative path from the storage root.
    //   callback - (optional) callback to be notified when synchronization has finished or failed.
    //
    // Example:
    //   > remoteStorage.money.on('change', function(changeEvent) {
    //   >   // handle change event (update UI etc)
    //   > });
    //   >
    //   > remoteStorage.fullSync('/money/', function(errors) {
    //   >   // handle errors, if any.
    //   > });
    //
    // Yields:
    //   Array of error messages - when errors occured. When fullSync is called and the user is not connected, this is also considered an error.
    //   null - no error occured, synchronization finished gracefully.
    //
    fullSync: sync.fullSync,

    // Method: syncNow
    //
    // DEPRECATED!!! use fullSync instead.
    syncNow          : function(path, depth, callback) {
      if(! depth) {
        callback = depth;
        depth = null;
      }

      sync.partialSync(path, depth, callback);
    },

    //
    // Method: displayWidget
    //
    // Add the remotestorage widget to the page.
    //
    // Parameters:
    //   domID - DOM ID of element to attach widget elements to
    //   options - Options, as described below.
    //   options.authDialog - Strategy to display OAuth dialog. Either 'redirect', 'popup' or a function. Defaults to 'redirect'. If this is a function, that function will receive the URL of the auth dialog. The OAuth dance will redirect back to the current location, with an access token, so that must be possible.
    //   options.syncShortcut - Whether to setup CTRL+S as a shortcut for immediate sync. Default is true.
    //   options.locale - Locale to use for the widget. Currently ignored.
    //
    // Minimal Example:
    //
    //    *in HTML *
    //    >

    //
    //    *in the app's JS*
    //    > remoteStorage.displayWidget('remotestorage-connect');
    //
    //    Once you're connected, press CTRL+S to observe the spinning cube :)
    //
    //    *Note* that in real life you would have to call <claimAccess> before calling displayWidget. Otherwise you can't access any actual data.
    //
    // Popup Example:
    //
    //    (using the same markup as above)
    //
    //    > remoteStorage.displayWidget('remotestorage-connect', { authDialog: 'popup' });
    //
    displayWidget    : widget.display,

    //
    // Method: onWidget
    //
    // Add event handler to the widget.
    // See <widget.Events> for available Events.
    //
    // Parameters:
    //   eventType - type of event to add handler to
    //   handler   - handler function
    //
    onWidget: widget.on,

    // Method: getWidgetState
    //
    // Get the widget state, reflecting the general connection state.
    //
    // Defined widget states are:
    //   anonymous    - initial state
    //   typing       - userAddress input visible, user typing her address.
    //   connecting   - pre-authentication, webfinger discovery.
    //   authing      - about to redirect to the auth endpoint (if authDialog=popup,
    //                  means the popup is open)
    //   connected    - Discovery & Auth done, connected to remotestorage.
    //   busy         - Currently exchaning data. (spinning cube)
    //   disconnected - fired, when user clicks 'disconnect'. use this to clear your
    //                  app's views of the data. immediately transitions to 'anonymous'
    //                  afterwards.
    //
    getWidgetState   : widget.getState,

    //
    getSyncState     : sync.getState,
    //
    setStorageInfo   : wireClient.setStorageInfo,

    getStorageHref   : wireClient.getStorageHref,

    disableSyncThrottling: sync.disableThrottling,

    nodeConnect: nodeConnect,

    util: util,

    // Method: getForeignClient
    //
    // Get a <ForeignClient> instance for a given user.
    //
    // Parameters:
    //   userAddress - a user address in the form user@host
    //   callback - a callback to receive the client
    //
    // If there is no storageInfo cached for this userAddress, this will trigger
    // a webfinger discovery and when that succeeded, return the client through
    // the callback.
    //
    // Example:
    //   (start code)
    //   var client = remoteStorage.getForeignClient('alice@wonderland.lit', function(error, client) {
    //     if(error) {
    //       console.error("Discovery failed: ", error);
    //     } else {
    //       client.getPublishedObjects(function(objects) {
    //         console.log('public stuff', objects);
    //       });
    //     }
    //   });
    //   (end code)
    getForeignClient: function(userAddress, callback) {
      var client = foreignClient.getClient(userAddress);
      if(wireClient.hasStorageInfo(userAddress)) {
        callback(null, client);
      } else {
        webfinger.getStorageInfo(
          userAddress, { timeout: 5000 }, function(err, storageInfo) {
            if(err) {
              callback(err);
            } else {
              wireClient.addStorageInfo(userAddress, storageInfo);
              callback(null, client);
            }
          }
        );
      }
    },

    getClient: function(scope) {
      return new BaseClient(scope);
    }

  };

  return remoteStorage;
});
<commit_msg>corrected documentation for remoteStorage.fullSync<commit_after>define([
  'require',
  './lib/widget',
  './lib/store',
  './lib/sync',
  './lib/wireClient',
  './lib/nodeConnect',
  './lib/util',
  './lib/webfinger',
  './lib/foreignClient',
  './lib/baseClient'
], function(require, widget, store, sync, wireClient, nodeConnect, util, webfinger, foreignClient, BaseClient) {

  "use strict";

  var claimedModules = {}, modules = {}, moduleNameRE = /^[a-z]+$/;

  var logger = util.getLogger('base');

  // Namespace: remoteStorage
  var remoteStorage =  {

    //
    // Method: defineModule
    //
    // Define a new module, with given name.
    // Module names MUST be unique. The given builder will be called
    // immediately, with two arguments, which are both instances of
    // <BaseClient>. The first accesses the private section of a modules
    // storage space, the second the public one. The public area can
    // be read by any client (not just an authenticated one), while
    // it can only be written by an authenticated client with read-write
    // access claimed on it.
    //
    // The builder is expected to return an object, as described under
    // <getModuleInfo>.
    //
    // Parameter:
    //   moduleName - Name of the module to define. MUST be a-z and all lowercase.
    //   builder    - Builder function that holds the module definition.
    //
    // Example:
    //   (start code)
    //
    //   remoteStorage.defineModule('beers', function(privateClient, publicClient) {
    //
    //     function nameToKey(name) {
    //       return name.replace(/\s/, '-');
    //     }
    //
    //     return {
    //       exports: {
    //
    //         addBeer: function(name) {
    //           privateClient.storeObject('beer', nameToKey(name), {
    //             name: name,
    //             drinkCount: 0
    //           });
    //         },
    //
    //         logDrink: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           beer.drinkCount++;
    //           privateClient.storeObject('beer', key, beer);
    //         },
    //
    //         publishBeer: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           publicClient.storeObject('beer', key, beer);
    //         }
    //
    //       }
    //     }
    //   });
    //
    //   // to use that code from an app, you need to add:
    //
    //   remoteStorage.claimAccess('beers', 'rw');
    //
    //   remoteStorage.displayWidget(/* see documentation */)
    //
    //   remoteStorage.addBeer('<replace-with-favourite-beer-kind>');
    //
    //   (end code)
    //
    // See also:
    //   <BaseClient>
    //
    defineModule: function(moduleName, builder) {

      if(! moduleNameRE.test(moduleName)) {
        throw 'Invalid moduleName: "'+moduleName+'", only a-z lowercase allowed.';
      }

      var module = builder(
        // private client:
        new BaseClient(moduleName, false),
        // public client:
        new BaseClient(moduleName, true)
      );
      modules[moduleName] = module;
      this[moduleName] = module.exports;
    },

    //
    // Method: getModuleList
    //
    // list known module names
    //
    // Returns:
    //   Array of module names.
    //
    getModuleList: function() {
      return Object.keys(modules);
    },

    // Method: getClaimedModuleList
    //
    // list of modules currently claimed access on
    //
    // Returns:
    //   Array of module names.
    //
    getClaimedModuleList: function() {
      return Object.keys(claimedModules);
    },

    //
    // Method: getModuleInfo
    //
    // Retrieve meta-information about a given module.
    //
    // If the module doesn't exist, the result will be undefined.
    //
    // Parameters:
    //   moduleName - name of the module
    //
    // Returns:
    //   An object, usually containing the following keys,
    //   * exports - don't ever use this. it's basically the module's instance.
    //   * name - the name of the module, but you knew that already.
    //   * dataHints - an object, describing internas about the module.
    //
    //
    //   Some of the dataHints used are:
    //
    //     objectType <type> - description of an object
    //                         type implemented by the module
    //     "objectType message" - (example)
    //
    //     <attributeType> <objectType>#<attribute> - description of an attribute
    //
    //     "string message#subject" - (example)
    //
    //     directory <path> - description of a path's purpose
    //
    //     "directory documents/notes/" - (example)
    //
    //     item <path> - description of a specific item
    //
    //     "item documents/notes/calendar" - (example)
    //
    //   Hope this helps.
    //
    getModuleInfo: function(moduleName) {
      return modules[moduleName];
    },

    //
    // Method: claimAccess
    //
    // Either:
    //   <claimAccess(moduleName, claim)>
    //
    // Or:
    //   <claimAccess(moduleClaimMap)>
    //
    //
    //
    // Method: claimAccess(moduleName, claim)
    //
    // Claim access on a single module
    //
    // You need to claim access to a module before you can
    // access data from it.
    //
    // Parameters:
    //   moduleName - name of the module. For a list of defined modules, use <getModuleList>
    //   claim      - permission to claim, either *r* (read-only) or *rw* (read-write)
    //
    // Example:
    //   > remoteStorage.claimAccess('contacts', 'r');
    //
    //
    //
    // Method: claimAccess(moduleClaimMap)
    //
    // Claim access to multiple modules.
    //
    // Parameters:
    //   moduleClaimMap - a JSON object with module names as keys and claims as values.
    //
    // Example:
    //   > remoteStorage.claimAccess({
    //   >   contacts: 'r',
    //   >   documents: 'rw',
    //   >   money: 'r'
    //   > });
    //
    claimAccess: function(moduleName, mode) {

      var modeTestRegex = /^rw?$/;
      function testMode(moduleName, mode) {
        if(!modeTestRegex.test(mode)) {
          throw "Claimed access to module '" + moduleName + "' but mode not correctly specified ('" + mode + "').";
        }
      }

      var moduleObj;
      if(typeof moduleName === 'object') {
        moduleObj = moduleName;
      } else {
        testMode(moduleName, mode);
        moduleObj = {};
        moduleObj[moduleName] = mode;
      }
      for(var _moduleName in moduleObj) {
        var _mode = moduleObj[_moduleName];
        testMode(_moduleName, _mode);
        this.claimModuleAccess(_moduleName, _mode);
      }
    },

    // PRIVATE
    claimModuleAccess: function(moduleName, mode) {
      logger.debug('claimModuleAccess', moduleName, mode);
      if(!(moduleName in modules)) {
        throw "Module not defined: " + moduleName;
      }

      if(moduleName in claimedModules) {
        return;
      }

      if(moduleName == 'root') {
        moduleName = '';
        widget.addScope('', mode);
        store.setNodeAccess('/', mode);
      } else {
        widget.addScope(moduleName, mode);
        store.setNodeAccess('/'+moduleName+'/', mode);
        store.setNodeAccess('/public/'+moduleName+'/', mode);
      }
      claimedModules[moduleName] = true;
    },

    // PRIVATE
    setBearerToken: function(bearerToken, claimedScopes) {
      wireClient.setBearerToken(bearerToken);
    },

    getBearerToken: function() {
      return wireClient.getBearerToken();
    },

    disconnectRemote : wireClient.disconnectRemote,

    //
    // Method: flushLocal()
    //
    // Forget this ever happened.
    //
    // Delete all locally stored data.
    // This doesn't clear localStorage, just removes everything
    // remoteStorage.js saved there. Other data your app might
    // have put into localStorage stays there.
    //
    // Call this method to implement "logout".
    //
    // If you are using the widget (which you should!), you don't need this.
    //
    // Example:
    //   > remoteStorage.flushLocal();
    //
    flushLocal       : store.forgetAll,

    //
    // Method: fullSync
    //
    // Synchronize local <-> remote storage.
    //
    // Syncing starts at the access roots (the once you claimed using claimAccess)
    // and moves down the directory tree.
    // Only nodes with a 'force' flag on themselves or one of their ancestors will
    // be synchronized. Use <BaseClient.use> and <BaseClient.release> to set / unset
    // force flags.
    // The actual changes to either local or remote storage happen in the
    // future, so you should attach change handlers on the modules you're
    // interested in.
    //
    // Parameters:
    //   callback - (optional) callback to be notified when synchronization has finished or failed.
    //
    // Example:
    //   >
    //   > remoteStorage.claimAccess('money', 'rw');
    //   >
    //   > remoteStorage.money.on('change', function(changeEvent) {
    //   >   // handle change event (update UI etc)
    //   > });
    //   >
    //   > remoteStorage.fullSync(function(errors) {
    //   >   // handle errors, if any.
    //   > });
    //
    // Yields:
    //   Array of error messages - when errors occured. When fullSync is called and the user is not connected, this is also considered an error.
    //   null - no error occured, synchronization finished gracefully.
    //
    fullSync: sync.fullSync,

    // Method: syncNow
    //
    // DEPRECATED!!! use fullSync instead.
    syncNow          : function(path, depth, callback) {
      if(! depth) {
        callback = depth;
        depth = null;
      }

      sync.partialSync(path, depth, callback);
    },

    //
    // Method: displayWidget
    //
    // Add the remotestorage widget to the page.
    //
    // Parameters:
    //   domID - DOM ID of element to attach widget elements to
    //   options - Options, as described below.
    //   options.authDialog - Strategy to display OAuth dialog. Either 'redirect', 'popup' or a function. Defaults to 'redirect'. If this is a function, that function will receive the URL of the auth dialog. The OAuth dance will redirect back to the current location, with an access token, so that must be possible.
    //   options.syncShortcut - Whether to setup CTRL+S as a shortcut for immediate sync. Default is true.
    //   options.locale - Locale to use for the widget. Currently ignored.
    //
    // Minimal Example:
    //
    //    *in HTML *
    //    >

    //
    //    *in the app's JS*
    //    > remoteStorage.displayWidget('remotestorage-connect');
    //
    //    Once you're connected, press CTRL+S to observe the spinning cube :)
    //
    //    *Note* that in real life you would have to call <claimAccess> before calling displayWidget. Otherwise you can't access any actual data.
    //
    // Popup Example:
    //
    //    (using the same markup as above)
    //
    //    > remoteStorage.displayWidget('remotestorage-connect', { authDialog: 'popup' });
    //
    displayWidget    : widget.display,

    //
    // Method: onWidget
    //
    // Add event handler to the widget.
    // See <widget.Events> for available Events.
    //
    // Parameters:
    //   eventType - type of event to add handler to
    //   handler   - handler function
    //
    onWidget: widget.on,

    // Method: getWidgetState
    //
    // Get the widget state, reflecting the general connection state.
    //
    // Defined widget states are:
    //   anonymous    - initial state
    //   typing       - userAddress input visible, user typing her address.
    //   connecting   - pre-authentication, webfinger discovery.
    //   authing      - about to redirect to the auth endpoint (if authDialog=popup,
    //                  means the popup is open)
    //   connected    - Discovery & Auth done, connected to remotestorage.
    //   busy         - Currently exchaning data. (spinning cube)
    //   disconnected - fired, when user clicks 'disconnect'. use this to clear your
    //                  app's views of the data. immediately transitions to 'anonymous'
    //                  afterwards.
    //
    getWidgetState   : widget.getState,

    //
    getSyncState     : sync.getState,
    //
    setStorageInfo   : wireClient.setStorageInfo,

    getStorageHref   : wireClient.getStorageHref,

    disableSyncThrottling: sync.disableThrottling,

    nodeConnect: nodeConnect,

    util: util,

    // Method: getForeignClient
    //
    // Get a <ForeignClient> instance for a given user.
    //
    // Parameters:
    //   userAddress - a user address in the form user@host
    //   callback - a callback to receive the client
    //
    // If there is no storageInfo cached for this userAddress, this will trigger
    // a webfinger discovery and when that succeeded, return the client through
    // the callback.
    //
    // Example:
    //   (start code)
    //   var client = remoteStorage.getForeignClient('alice@wonderland.lit', function(error, client) {
    //     if(error) {
    //       console.error("Discovery failed: ", error);
    //     } else {
    //       client.getPublishedObjects(function(objects) {
    //         console.log('public stuff', objects);
    //       });
    //     }
    //   });
    //   (end code)
    getForeignClient: function(userAddress, callback) {
      var client = foreignClient.getClient(userAddress);
      if(wireClient.hasStorageInfo(userAddress)) {
        callback(null, client);
      } else {
        webfinger.getStorageInfo(
          userAddress, { timeout: 5000 }, function(err, storageInfo) {
            if(err) {
              callback(err);
            } else {
              wireClient.addStorageInfo(userAddress, storageInfo);
              callback(null, client);
            }
          }
        );
      }
    },

    getClient: function(scope) {
      return new BaseClient(scope);
    }

  };

  return remoteStorage;
});
<|endoftext|>


<commit_before>define([
  'require',
  './lib/widget',
  './lib/store',
  './lib/sync',
  './lib/wireClient',
  './lib/nodeConnect',
  './lib/util',
  './lib/webfinger',
  './lib/foreignClient',
  './lib/baseClient',
  './lib/schedule',
  './lib/i18n'
], function(require, widget, store, sync, wireClient, nodeConnect, util, webfinger, foreignClient, BaseClient, schedule, i18n) {

  "use strict";

  var claimedModules = {}, modules = {}, moduleNameRE = /^[a-z\-]+$/;

  var logger = util.getLogger('base');

  // Namespace: remoteStorage
  var remoteStorage =  {

    //
    // Method: defineModule
    //
    // Define a new module, with given name.
    // Module names MUST be unique. The given builder will be called
    // immediately, with two arguments, which are both instances of
    // <BaseClient>. The first accesses the private section of a modules
    // storage space, the second the public one. The public area can
    // be read by any client (not just an authenticated one), while
    // it can only be written by an authenticated client with read-write
    // access claimed on it.
    //
    // The builder is expected to return an object, as described under
    // <getModuleInfo>.
    //
    // Parameter:
    //   moduleName - Name of the module to define. MUST be a-z and all lowercase.
    //   builder    - Builder function that holds the module definition.
    //
    // Example:
    //   (start code)
    //
    //   remoteStorage.defineModule('beers', function(privateClient, publicClient) {
    //
    //     function nameToKey(name) {
    //       return name.replace(/\s/, '-');
    //     }
    //
    //     return {
    //       exports: {
    //
    //         addBeer: function(name) {
    //           privateClient.storeObject('beer', nameToKey(name), {
    //             name: name,
    //             drinkCount: 0
    //           });
    //         },
    //
    //         logDrink: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           beer.drinkCount++;
    //           privateClient.storeObject('beer', key, beer);
    //         },
    //
    //         publishBeer: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           publicClient.storeObject('beer', key, beer);
    //         }
    //
    //       }
    //     }
    //   });
    //
    //   // to use that code from an app, you need to add:
    //
    //   remoteStorage.claimAccess('beers', 'rw');
    //
    //   remoteStorage.displayWidget(/* see documentation */)
    //
    //   remoteStorage.addBeer('<replace-with-favourite-beer-kind>');
    //
    //   (end code)
    //
    // See also:
    //   <BaseClient>
    //
    defineModule: function(moduleName, builder) {

      if(! moduleNameRE.test(moduleName)) {
        throw 'Invalid moduleName: "'+moduleName+'", only a-z lowercase allowed.';
      }

      var module = builder(
        // private client:
        new BaseClient(moduleName, false),
        // public client:
        new BaseClient(moduleName, true)
      );
      modules[moduleName] = module;
      this[moduleName] = module.exports;
    },

    claimedModules: claimedModules,

    //
    // Method: getModuleList
    //
    // list known module names
    //
    // Returns:
    //   Array of module names.
    //
    getModuleList: function() {
      return Object.keys(modules);
    },

    // Method: getClaimedModuleList
    //
    // list of modules currently claimed access on
    //
    // Returns:
    //   Array of module names.
    //
    getClaimedModuleList: function() {
      return Object.keys(claimedModules);
    },

    //
    // Method: getModuleInfo
    //
    // Retrieve meta-information about a given module.
    //
    // If the module doesn't exist, the result will be undefined.
    //
    // Parameters:
    //   moduleName - name of the module
    //
    // Returns:
    //   An object, usually containing the following keys,
    //   * exports - don't ever use this. it's basically the module's instance.
    //   * name - the name of the module, but you knew that already.
    //   * dataHints - an object, describing internas about the module.
    //
    //
    //   Some of the dataHints used are:
    //
    //     objectType <type> - description of an object
    //                         type implemented by the module
    //     "objectType message" - (example)
    //
    //     <attributeType> <objectType>#<attribute> - description of an attribute
    //
    //     "string message#subject" - (example)
    //
    //     directory <path> - description of a path's purpose
    //
    //     "directory documents/notes/" - (example)
    //
    //     item <path> - description of a specific item
    //
    //     "item documents/notes/calendar" - (example)
    //
    //   Hope this helps.
    //
    getModuleInfo: function(moduleName) {
      return modules[moduleName];
    },

    //
    // Method: claimAccess
    //
    // Either:
    //   <claimAccess(moduleName, claim)>
    //
    // Or:
    //   <claimAccess(moduleClaimMap)>
    //
    //
    //
    // Method: claimAccess(moduleName, claim)
    //
    // Claim access on a single module
    //
    // You need to claim access to a module before you can
    // access data from it.
    //
    // Parameters:
    //   moduleName - name of the module. For a list of defined modules, use <getModuleList>
    //   claim      - permission to claim, either *r* (read-only) or *rw* (read-write)
    //
    // Example:
    //   > remoteStorage.claimAccess('contacts', 'r');
    //
    //
    //
    // Method: claimAccess(moduleClaimMap)
    //
    // Claim access to multiple modules.
    //
    // Parameters:
    //   moduleClaimMap - a JSON object with module names as keys and claims as values.
    //
    // Example:
    //   > remoteStorage.claimAccess({
    //   >   contacts: 'r',
    //   >   documents: 'rw',
    //   >   money: 'r'
    //   > });
    //
    claimAccess: function(moduleName, mode) {

      var modeTestRegex = /^rw?$/;
      function testMode(moduleName, mode) {
        if(!modeTestRegex.test(mode)) {
          throw "Claimed access to module '" + moduleName + "' but mode not correctly specified ('" + mode + "').";
        }
      }

      var moduleObj;
      if(typeof moduleName === 'object') {
        moduleObj = moduleName;
      } else {
        testMode(moduleName, mode);
        moduleObj = {};
        moduleObj[moduleName] = mode;
      }

      return util.asyncEach(Object.keys(moduleObj), function(_moduleName) {
        var _mode = moduleObj[_moduleName];
        testMode(_moduleName, _mode);
        return this.claimModuleAccess(_moduleName, _mode);
      }.bind(this));
    },

    // PRIVATE
    claimModuleAccess: function(moduleName, mode) {
      logger.debug('claimModuleAccess', moduleName, mode);
      if(!(moduleName in modules)) {
        throw "Module not defined: " + moduleName;
      }

      if(moduleName in claimedModules) {
        return;
      }

      claimedModules[moduleName] = mode;

      if(moduleName === 'root') {
        moduleName = '';
        return store.setNodeAccess('/', mode).
          then(util.curry(store.setNodeForce, '/', true, true));
      } else {
        var privPath = '/'+moduleName+'/';
        var pubPath = '/public/'+moduleName+'/';
        return store.setNodeAccess(privPath, mode).
          then(util.curry(store.setNodeForce, privPath, true, true)).
          then(util.curry(store.setNodeAccess, pubPath, mode)).
          then(util.curry(store.setNodeForce, pubPath, true, true));
      }
    },

    // PRIVATE
    setBearerToken: function(bearerToken, claimedScopes) {
      wireClient.setBearerToken(bearerToken);
    },

    getBearerToken: function() {
      return wireClient.getBearerToken();
    },

    disconnectRemote : wireClient.disconnectRemote,

    //
    // Method: flushLocal()
    //
    // Forget this ever happened.
    //
    // Delete all locally stored data.
    // This doesn't clear localStorage, just removes everything
    // remoteStorage.js saved there. Other data your app might
    // have put into localStorage stays there.
    //
    // Call this method to implement "logout".
    //
    // If you are using the widget (which you should!), you don't need this.
    //
    // Example:
    //   > remoteStorage.flushLocal();
    //
    flushLocal       : function() {
      store.forgetAll();
      sync.clearSettings();
      widget.clearSettings();
      schedule.reset();
      wireClient.disconnectRemote();
      i18n.clearSettings();
    },

    //
    // Method: fullSync
    //
    // Synchronize local <-> remote storage.
    //
    // Syncing starts at the access roots (the once you claimed using claimAccess)
    // and moves down the directory tree.
    // Only nodes with a 'force' flag on themselves or one of their ancestors will
    // be synchronized. Use <BaseClient.use> and <BaseClient.release> to set / unset
    // force flags.
    // The actual changes to either local or remote storage happen in the
    // future, so you should attach change handlers on the modules you're
    // interested in.
    //
    // Parameters:
    //   callback - (optional) callback to be notified when synchronization has finished or failed.
    //
    // Example:
    //   >
    //   > remoteStorage.claimAccess('money', 'rw');
    //   >
    //   > remoteStorage.money.on('change', function(changeEvent) {
    //   >   // handle change event (update UI etc)
    //   > });
    //   >
    //   > remoteStorage.fullSync(function(errors) {
    //   >   // handle errors, if any.
    //   > });
    //
    // Yields:
    //   Array of error messages - when errors occured. When fullSync is called and the user is not connected, this is also considered an error.
    //   null - no error occured, synchronization finished gracefully.
    //
    fullSync: sync.fullSync,

    setWidgetView: widget.setView,

    //
    // Method: displayWidget
    //
    // Add the remotestorage widget to the page.
    //
    // Parameters:
    //   domID - DOM ID of element to attach widget elements to
    //   options - Options, as described below.
    //   options.authDialog - Strategy to display OAuth dialog. Either 'redirect', 'popup' or a function. Defaults to 'redirect'. If this is a function, that function will receive the URL of the auth dialog. The OAuth dance will redirect back to the current location, with an access token, so that must be possible.
    //   options.syncShortcut - Whether to setup CTRL+S as a shortcut for immediate sync. Default is true.
    //   options.locale - Locale to use for the widget. Currently ignored.
    //
    // Minimal Example:
    //
    //    *in HTML *
    //    >

    //
    //    *in the app's JS*
    //    > remoteStorage.displayWidget('remotestorage-connect');
    //
    //    Once you're connected, press CTRL+S to observe the spinning cube :)
    //
    //    *Note* that in real life you would have to call <claimAccess> before calling displayWidget. Otherwise you can't access any actual data.
    //
    // Popup Example:
    //
    //    (using the same markup as above)
    //
    //    > remoteStorage.displayWidget('remotestorage-connect', { authDialog: 'popup' });
    //
    displayWidget: function(domId, options) {
      widget.display(remoteStorage, domId, util.extend({}, options));
    },

    //
    // Method: onWidget
    //
    // Add event handler to the widget.
    // See <widget.Events> for available Events.
    //
    // Parameters:
    //   eventType - type of event to add handler to
    //   handler   - handler function
    //
    onWidget: widget.on,

    // Method: getWidgetState
    //
    // Get the widget state, reflecting the general connection state.
    //
    // Defined widget states are:
    //   anonymous    - initial state
    //   typing       - userAddress input visible, user typing her address.
    //   connecting   - pre-authentication, webfinger discovery.
    //   authing      - about to redirect to the auth endpoint (if authDialog=popup,
    //                  means the popup is open)
    //   connected    - Discovery & Auth done, connected to remotestorage.
    //   busy         - Currently exchaning data. (spinning cube)
    //   disconnected - fired, when user clicks 'disconnect'. use this to clear your
    //                  app's views of the data. immediately transitions to 'anonymous'
    //                  afterwards.
    //
    getWidgetState: widget.getState,

    //
    getSyncState: sync.getState,
    //
    setStorageInfo: wireClient.setStorageInfo,

    getStorageHref: wireClient.getStorageHref,

    disableSyncThrottling: sync.disableThrottling,

    nodeConnect: nodeConnect,

    util: util,

    // Method: getForeignClient
    //
    // Get a <ForeignClient> instance for a given user.
    //
    // Parameters:
    //   userAddress - a user address in the form user@host
    //   callback - a callback to receive the client
    //
    // If there is no storageInfo cached for this userAddress, this will trigger
    // a webfinger discovery and when that succeeded, return the client through
    // the callback.
    //
    // Example:
    //   (start code)
    //   var client = remoteStorage.getForeignClient('alice@wonderland.lit', function(error, client) {
    //     if(error) {
    //       console.error("Discovery failed: ", error);
    //     } else {
    //       client.getPublishedObjects(function(objects) {
    //         console.log('public stuff', objects);
    //       });
    //     }
    //   });
    //   (end code)
    getForeignClient: function(userAddress, callback) {
      var client = foreignClient.getClient(userAddress);
      if(wireClient.hasStorageInfo(userAddress)) {
        callback(null, client);
      } else {
        webfinger.getStorageInfo(
          userAddress, { timeout: 5000 }, function(err, storageInfo) {
            if(err) {
              callback(err);
            } else {
              wireClient.addStorageInfo(userAddress, storageInfo);
              callback(null, client);
            }
          }
        );
      }
    },

    getClient: function(scope) {
      return new BaseClient(scope);
    },

    // Property: store
    // Public access to <store>
    store: store,

    sync: sync,
    widget: widget,

    i18n: i18n

  };

  return remoteStorage;
});
<commit_msg>remoteStorage.schedule publicly available now<commit_after>define([
  'require',
  './lib/widget',
  './lib/store',
  './lib/sync',
  './lib/wireClient',
  './lib/nodeConnect',
  './lib/util',
  './lib/webfinger',
  './lib/foreignClient',
  './lib/baseClient',
  './lib/schedule',
  './lib/i18n'
], function(require, widget, store, sync, wireClient, nodeConnect, util, webfinger, foreignClient, BaseClient, schedule, i18n) {

  "use strict";

  var claimedModules = {}, modules = {}, moduleNameRE = /^[a-z\-]+$/;

  var logger = util.getLogger('base');

  // Namespace: remoteStorage
  var remoteStorage =  {

    //
    // Method: defineModule
    //
    // Define a new module, with given name.
    // Module names MUST be unique. The given builder will be called
    // immediately, with two arguments, which are both instances of
    // <BaseClient>. The first accesses the private section of a modules
    // storage space, the second the public one. The public area can
    // be read by any client (not just an authenticated one), while
    // it can only be written by an authenticated client with read-write
    // access claimed on it.
    //
    // The builder is expected to return an object, as described under
    // <getModuleInfo>.
    //
    // Parameter:
    //   moduleName - Name of the module to define. MUST be a-z and all lowercase.
    //   builder    - Builder function that holds the module definition.
    //
    // Example:
    //   (start code)
    //
    //   remoteStorage.defineModule('beers', function(privateClient, publicClient) {
    //
    //     function nameToKey(name) {
    //       return name.replace(/\s/, '-');
    //     }
    //
    //     return {
    //       exports: {
    //
    //         addBeer: function(name) {
    //           privateClient.storeObject('beer', nameToKey(name), {
    //             name: name,
    //             drinkCount: 0
    //           });
    //         },
    //
    //         logDrink: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           beer.drinkCount++;
    //           privateClient.storeObject('beer', key, beer);
    //         },
    //
    //         publishBeer: function(name) {
    //           var key = nameToKey(name);
    //           var beer = privateClient.getObject(key);
    //           publicClient.storeObject('beer', key, beer);
    //         }
    //
    //       }
    //     }
    //   });
    //
    //   // to use that code from an app, you need to add:
    //
    //   remoteStorage.claimAccess('beers', 'rw');
    //
    //   remoteStorage.displayWidget(/* see documentation */)
    //
    //   remoteStorage.addBeer('<replace-with-favourite-beer-kind>');
    //
    //   (end code)
    //
    // See also:
    //   <BaseClient>
    //
    defineModule: function(moduleName, builder) {

      if(! moduleNameRE.test(moduleName)) {
        throw 'Invalid moduleName: "'+moduleName+'", only a-z lowercase allowed.';
      }

      var module = builder(
        // private client:
        new BaseClient(moduleName, false),
        // public client:
        new BaseClient(moduleName, true)
      );
      modules[moduleName] = module;
      this[moduleName] = module.exports;
    },

    claimedModules: claimedModules,

    //
    // Method: getModuleList
    //
    // list known module names
    //
    // Returns:
    //   Array of module names.
    //
    getModuleList: function() {
      return Object.keys(modules);
    },

    // Method: getClaimedModuleList
    //
    // list of modules currently claimed access on
    //
    // Returns:
    //   Array of module names.
    //
    getClaimedModuleList: function() {
      return Object.keys(claimedModules);
    },

    //
    // Method: getModuleInfo
    //
    // Retrieve meta-information about a given module.
    //
    // If the module doesn't exist, the result will be undefined.
    //
    // Parameters:
    //   moduleName - name of the module
    //
    // Returns:
    //   An object, usually containing the following keys,
    //   * exports - don't ever use this. it's basically the module's instance.
    //   * name - the name of the module, but you knew that already.
    //   * dataHints - an object, describing internas about the module.
    //
    //
    //   Some of the dataHints used are:
    //
    //     objectType <type> - description of an object
    //                         type implemented by the module
    //     "objectType message" - (example)
    //
    //     <attributeType> <objectType>#<attribute> - description of an attribute
    //
    //     "string message#subject" - (example)
    //
    //     directory <path> - description of a path's purpose
    //
    //     "directory documents/notes/" - (example)
    //
    //     item <path> - description of a specific item
    //
    //     "item documents/notes/calendar" - (example)
    //
    //   Hope this helps.
    //
    getModuleInfo: function(moduleName) {
      return modules[moduleName];
    },

    //
    // Method: claimAccess
    //
    // Either:
    //   <claimAccess(moduleName, claim)>
    //
    // Or:
    //   <claimAccess(moduleClaimMap)>
    //
    //
    //
    // Method: claimAccess(moduleName, claim)
    //
    // Claim access on a single module
    //
    // You need to claim access to a module before you can
    // access data from it.
    //
    // Parameters:
    //   moduleName - name of the module. For a list of defined modules, use <getModuleList>
    //   claim      - permission to claim, either *r* (read-only) or *rw* (read-write)
    //
    // Example:
    //   > remoteStorage.claimAccess('contacts', 'r');
    //
    //
    //
    // Method: claimAccess(moduleClaimMap)
    //
    // Claim access to multiple modules.
    //
    // Parameters:
    //   moduleClaimMap - a JSON object with module names as keys and claims as values.
    //
    // Example:
    //   > remoteStorage.claimAccess({
    //   >   contacts: 'r',
    //   >   documents: 'rw',
    //   >   money: 'r'
    //   > });
    //
    claimAccess: function(moduleName, mode) {

      var modeTestRegex = /^rw?$/;
      function testMode(moduleName, mode) {
        if(!modeTestRegex.test(mode)) {
          throw "Claimed access to module '" + moduleName + "' but mode not correctly specified ('" + mode + "').";
        }
      }

      var moduleObj;
      if(typeof moduleName === 'object') {
        moduleObj = moduleName;
      } else {
        testMode(moduleName, mode);
        moduleObj = {};
        moduleObj[moduleName] = mode;
      }

      return util.asyncEach(Object.keys(moduleObj), function(_moduleName) {
        var _mode = moduleObj[_moduleName];
        testMode(_moduleName, _mode);
        return this.claimModuleAccess(_moduleName, _mode);
      }.bind(this));
    },

    // PRIVATE
    claimModuleAccess: function(moduleName, mode) {
      logger.debug('claimModuleAccess', moduleName, mode);
      if(!(moduleName in modules)) {
        throw "Module not defined: " + moduleName;
      }

      if(moduleName in claimedModules) {
        return;
      }

      claimedModules[moduleName] = mode;

      if(moduleName === 'root') {
        moduleName = '';
        return store.setNodeAccess('/', mode).
          then(util.curry(store.setNodeForce, '/', true, true));
      } else {
        var privPath = '/'+moduleName+'/';
        var pubPath = '/public/'+moduleName+'/';
        return store.setNodeAccess(privPath, mode).
          then(util.curry(store.setNodeForce, privPath, true, true)).
          then(util.curry(store.setNodeAccess, pubPath, mode)).
          then(util.curry(store.setNodeForce, pubPath, true, true));
      }
    },

    // PRIVATE
    setBearerToken: function(bearerToken, claimedScopes) {
      wireClient.setBearerToken(bearerToken);
    },

    getBearerToken: function() {
      return wireClient.getBearerToken();
    },

    disconnectRemote : wireClient.disconnectRemote,

    //
    // Method: flushLocal()
    //
    // Forget this ever happened.
    //
    // Delete all locally stored data.
    // This doesn't clear localStorage, just removes everything
    // remoteStorage.js saved there. Other data your app might
    // have put into localStorage stays there.
    //
    // Call this method to implement "logout".
    //
    // If you are using the widget (which you should!), you don't need this.
    //
    // Example:
    //   > remoteStorage.flushLocal();
    //
    flushLocal       : function() {
      store.forgetAll();
      sync.clearSettings();
      widget.clearSettings();
      schedule.reset();
      wireClient.disconnectRemote();
      i18n.clearSettings();
    },

    //
    // Method: fullSync
    //
    // Synchronize local <-> remote storage.
    //
    // Syncing starts at the access roots (the once you claimed using claimAccess)
    // and moves down the directory tree.
    // Only nodes with a 'force' flag on themselves or one of their ancestors will
    // be synchronized. Use <BaseClient.use> and <BaseClient.release> to set / unset
    // force flags.
    // The actual changes to either local or remote storage happen in the
    // future, so you should attach change handlers on the modules you're
    // interested in.
    //
    // Parameters:
    //   callback - (optional) callback to be notified when synchronization has finished or failed.
    //
    // Example:
    //   >
    //   > remoteStorage.claimAccess('money', 'rw');
    //   >
    //   > remoteStorage.money.on('change', function(changeEvent) {
    //   >   // handle change event (update UI etc)
    //   > });
    //   >
    //   > remoteStorage.fullSync(function(errors) {
    //   >   // handle errors, if any.
    //   > });
    //
    // Yields:
    //   Array of error messages - when errors occured. When fullSync is called and the user is not connected, this is also considered an error.
    //   null - no error occured, synchronization finished gracefully.
    //
    fullSync: sync.fullSync,

    setWidgetView: widget.setView,

    //
    // Method: displayWidget
    //
    // Add the remotestorage widget to the page.
    //
    // Parameters:
    //   domID - DOM ID of element to attach widget elements to
    //   options - Options, as described below.
    //   options.authDialog - Strategy to display OAuth dialog. Either 'redirect', 'popup' or a function. Defaults to 'redirect'. If this is a function, that function will receive the URL of the auth dialog. The OAuth dance will redirect back to the current location, with an access token, so that must be possible.
    //   options.syncShortcut - Whether to setup CTRL+S as a shortcut for immediate sync. Default is true.
    //   options.locale - Locale to use for the widget. Currently ignored.
    //
    // Minimal Example:
    //
    //    *in HTML *
    //    >

    //
    //    *in the app's JS*
    //    > remoteStorage.displayWidget('remotestorage-connect');
    //
    //    Once you're connected, press CTRL+S to observe the spinning cube :)
    //
    //    *Note* that in real life you would have to call <claimAccess> before calling displayWidget. Otherwise you can't access any actual data.
    //
    // Popup Example:
    //
    //    (using the same markup as above)
    //
    //    > remoteStorage.displayWidget('remotestorage-connect', { authDialog: 'popup' });
    //
    displayWidget: function(domId, options) {
      widget.display(remoteStorage, domId, util.extend({}, options));
    },

    //
    // Method: onWidget
    //
    // Add event handler to the widget.
    // See <widget.Events> for available Events.
    //
    // Parameters:
    //   eventType - type of event to add handler to
    //   handler   - handler function
    //
    onWidget: widget.on,

    // Method: getWidgetState
    //
    // Get the widget state, reflecting the general connection state.
    //
    // Defined widget states are:
    //   anonymous    - initial state
    //   typing       - userAddress input visible, user typing her address.
    //   connecting   - pre-authentication, webfinger discovery.
    //   authing      - about to redirect to the auth endpoint (if authDialog=popup,
    //                  means the popup is open)
    //   connected    - Discovery & Auth done, connected to remotestorage.
    //   busy         - Currently exchaning data. (spinning cube)
    //   disconnected - fired, when user clicks 'disconnect'. use this to clear your
    //                  app's views of the data. immediately transitions to 'anonymous'
    //                  afterwards.
    //
    getWidgetState: widget.getState,

    //
    getSyncState: sync.getState,
    //
    setStorageInfo: wireClient.setStorageInfo,

    getStorageHref: wireClient.getStorageHref,

    disableSyncThrottling: sync.disableThrottling,

    nodeConnect: nodeConnect,

    util: util,

    // Method: getForeignClient
    //
    // Get a <ForeignClient> instance for a given user.
    //
    // Parameters:
    //   userAddress - a user address in the form user@host
    //   callback - a callback to receive the client
    //
    // If there is no storageInfo cached for this userAddress, this will trigger
    // a webfinger discovery and when that succeeded, return the client through
    // the callback.
    //
    // Example:
    //   (start code)
    //   var client = remoteStorage.getForeignClient('alice@wonderland.lit', function(error, client) {
    //     if(error) {
    //       console.error("Discovery failed: ", error);
    //     } else {
    //       client.getPublishedObjects(function(objects) {
    //         console.log('public stuff', objects);
    //       });
    //     }
    //   });
    //   (end code)
    getForeignClient: function(userAddress, callback) {
      var client = foreignClient.getClient(userAddress);
      if(wireClient.hasStorageInfo(userAddress)) {
        callback(null, client);
      } else {
        webfinger.getStorageInfo(
          userAddress, { timeout: 5000 }, function(err, storageInfo) {
            if(err) {
              callback(err);
            } else {
              wireClient.addStorageInfo(userAddress, storageInfo);
              callback(null, client);
            }
          }
        );
      }
    },

    getClient: function(scope) {
      return new BaseClient(scope);
    },

    // Property: store
    // Public access to <store>
    store: store,

    sync: sync,
    widget: widget,

    i18n: i18n,

    schedule: schedule

  };

  return remoteStorage;
});
<|endoftext|>