<commit_before>import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Immutable, { Record  } from 'immutable';

import * as allActionCreators from '../actions';

import { renderAnnoTextToHTML } from '../util/annotext';

const languageOptions = [
  { value: 'ja', label: 'Japanese' },
  { value: 'en', label: 'English' },
];

const newlinesToBrs = s => s.split('\n').map((o, i) => {o}
);

// Select, "uncontrolled" but watches changes
class Select extends Component {
  componentWillMount() {
    const { options, onSet } = this.props;
    if (options.length > 0) {
      onSet(options[0].value);
    }
  }

  render() {
    const { options, onSet } = this.props;
    return (
      <select onChange={e => onSet(e.target.value)}>
        {options.map((o, i) => <option key={i} value={o.value}>{o.label}</option>)}
      </select>
    );
  }
}

// ClipboardCopier
class ClipboardCopier extends Component {
  constructor(props) {
    super(props);
    this.onSubmit = this.onSubmit.bind(this);
  }

  onSubmit(e) {
    e.preventDefault();
    // TODO: could check input is set and input.select is truthy, and wrap copy+blur in a try block
    this.inputElem.select();
    document.execCommand('copy');
    this.inputElem.blur();
  }

  render() {
    const { text, buttonText } = this.props;
    return (
      <form onSubmit={this.onSubmit}>
        <input style={{ /*width: '2em'*/ }} type="text" value={text} readOnly ref={(el) => { this.inputElem = el; }} />
        <button type="submit">{buttonText}</button>
      </form>
    );
  }
}

// NewDocForm
const NewDocForm = connect()(
  class extends Component {
    render() {
      const kindOptions = [
        { value: 'video', label: 'Video' },
        { value: 'comic', label: 'Comic' },
      ];
      const { actions } = this.props;
      return (
        <form onSubmit={e => { e.preventDefault(); actions.newDoc(this.kindVal); }}>
          <Select options={kindOptions} onSet={v => { this.kindVal = v; }} />
          <button type="submit">Create New Document</button>
        </form>
      );
    }
  }
);

// FileChooserForm
const FileChooser = ({ label, accept, onChoose }) => (
  {label} <input type="file" accept={accept} onChange={e => { onChoose(e.target.files[0]); e.target.value = null; }} />
);

// VideoImportControls
class VideoImportControls extends Component {
  render() {
    const { actions } = this.props;
    return (

        <form>
          <FileChooser label="Import Video" accept="video/*" onChoose={(file) => { actions.importVideoFile(file, this.videoLanguageVal); }} />
          <Select options={languageOptions} onSet={v => { this.videoLanguageVal = v; }} />
        </form>
        <form>
          <FileChooser label="Import Subs (SRT)" accept=".srt" onChoose={(file) => { actions.importSubsFile(file, this.subLanguageVal); }} />
          <Select options={languageOptions} onSet={v => { this.subLanguageVal = v; }} />
        </form>

    );
  }
}

class VideoMedia extends Component {
  render() {
    const { media, onTimeUpdate, mountedVideoElement } = this.props;
    return (

{media.size ? (
        <video src={media.first().videoURL} controls onTimeUpdate={e => { onTimeUpdate(e.target.currentTime); }} ref={(el) => { mountedVideoElement(el); }} />
      ) : ''
      }

    );
  }
}

class PlayControls extends Component {
  constructor(props) {
    super(props);
    this.handleKeyDown = this.handleKeyDown.bind(this);
  }

  componentDidMount() {
    document.body.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    document.body.removeEventListener('keydown', this.handleKeyDown);
  }

  handleKeyDown(e) {
    const { onBack, onTogglePause, onHideText, onRevealMoreText } = this.props;

    // console.log(e);
    if (!e.repeat) {
      switch (e.keyCode) {
        case 65: // a
          onBack();
          break;

        case 68: // d
          onHideText();
          break;

        case 70: // f
          onRevealMoreText();
          break;

        case 32: // space
          onTogglePause();
          break;

        default:
          // ignore
          break;
      }
    }
  }

  render() {
    const { onBack, onTogglePause, onHideText, onRevealMoreText } = this.props;
    return (
      <form style={{ textAlign: 'center', margin: '10px auto' }}>
        <button type="button" onClick={() => { onBack(); }}>Jump Back [A]</button>
        <button type="button" onClick={() => { onHideText(); }}>Hide Texts [D]</button>
        <button type="button" onClick={() => { onRevealMoreText(); }}>Reveal Next Text [F]</button>
        <button type="button" onClick={() => { onTogglePause(); }}>Play/Pause [Space]</button>
      </form>
    );
  }
}

const isAncestorNode = (potentialAncestor, given) => {
  let n = given;
  while (true) {
    if (n === potentialAncestor) {
      return true;
    }
    if (n.parentNode) {
      n = n.parentNode;
    } else {
      return false;
    }
  }
}

const CPRange = new Record({
  cpBegin: null,
  cpEnd: null,
});

class AnnoText extends Component {
  constructor(props) {
    super(props);
    this.state = {
      selectionRange: null,
      inspectedIndex: null, // codepoint index of char we're doing a "tooltip" for
    };
    this.tooltipTimeout = null; // it does not work to have this in state
    this.handleSelectionChange = this.handleSelectionChange.bind(this);
    this.handleCharMouseEnter = this.handleCharMouseEnter.bind(this);
    this.handleCharMouseLeave = this.handleCharMouseLeave.bind(this);
  }

  componentDidMount() {
    document.addEventListener('selectionchange', this.handleSelectionChange);
  }

  componentWillUnmount() {
    document.removeEventListener('selectionchange', this.handleSelectionChange);
    this.clearTooltipTimeout();
  }

  clearTooltipTimeout() {
    if (this.tooltipTimeout) {
      window.clearTimeout(this.tooltipTimeout);
      this.tooltipTimeout = null;
    }
  }

  setTooltipTimeout() {
    if (this.tooltipTimeout) {
      window.clearTimeout(this.tooltipTimeout);
    }
    this.tooltipTimeout = window.setTimeout(
      () => { this.setState({ inspectedIndex: null }); this.tooltipTimeout = null; },
      500
    );
  }

  currentSelectionIndexRange() {
    const selObj = window.getSelection();
    if (selObj.isCollapsed) {
      return null;
    } else {
      const selRange = selObj.getRangeAt(0);
      const ancestorNode = selRange.commonAncestorContainer;
      if (isAncestorNode(this.textContainerElem, ancestorNode)) {
        const frag = selRange.cloneContents();

        if (ancestorNode.nodeType === Node.TEXT_NODE) {
          // If we selected text that is inside any element, we'll get a text node.
          // It might not be useful text (might be furigana), so need to check what element it's inside.
          // For now we can assume that its immediate parent will be a .textchar element if it's a good character.
          const parent = ancestorNode.parentNode;
          if (parent.classList.contains('textchar')) {
            const cpIndex = +parent.getAttribute('data-index');
            return new CPRange({cpBegin: cpIndex, cpEnd: cpIndex+1});
          } else {
            return null;
          }
        } else {
          const cpIndexes = [];

          // Iterate over all .textchar elems inside the fragment
          for (const el of frag.querySelectorAll('.textchar')) {
            // We might get extraneous .textchar elems that have nothing inside them. Only consider ones that have text inside.
            if (el.textContent.length > 0) {
              const cpIndex = +el.getAttribute('data-index');
              cpIndexes.push(cpIndex);
            }
          }

          if (cpIndexes.length === 0) {
            return null;
          } else {
            var minIndex = Math.min(...cpIndexes);
            var maxIndex = Math.max(...cpIndexes);
            if ((minIndex !== cpIndexes[0]) || (maxIndex !== cpIndexes[cpIndexes.length-1])) {
              throw new Error('Unexpected');
            }
            return new CPRange({cpBegin: minIndex, cpEnd: maxIndex+1});
          }
        }
      } else {
        return null;
      }
    }
  }

  handleSelectionChange(e) {
    const newSelRange = this.currentSelectionIndexRange();
    if (!Immutable.is(newSelRange, this.state.selectionRange)) {
      this.setState({selectionRange: newSelRange});
    }
  }

  handleCharMouseEnter(e) {
    const cpIndex = +e.currentTarget.getAttribute('data-index');
    this.setState({inspectedIndex: cpIndex});
    this.clearTooltipTimeout();
  }

  handleCharMouseLeave(e) {
    this.setTooltipTimeout();
  }

  render() {
    const { annoText, language } = this.props;

    const inspectedIndex = this.state.inspectedIndex;
    const hitWords = [];
    const hitWordInfoElems = [];
    const hitWordIndexes = new Set();
    if (inspectedIndex !== null) {
      annoText.words.forEach((word) => {
        if ((inspectedIndex >= word.cpBegin) && (inspectedIndex < word.cpEnd)) {
          hitWords.push(word);
        }
      });
      hitWords.forEach((hitWord) => {
        hitWordInfoElems.push(<span key={`wordinfo-${hitWord.cpBegin}:${hitWord.cpEnd}`}>{hitWord.lemma}
Tangorin);
      });
      hitWords.forEach((hitWord) => {
        for (let i = hitWord.cpBegin; i < hitWord.cpEnd; i++) {
          hitWordIndexes.add(i);
        }
      });
    }

    const textRangeToElems = (cpBegin, cpEnd) => {
      const pieces = [];
      for (let i = cpBegin; i < cpEnd; i++) {
        const c = textArr[i];
        if (c === '\n') {
          pieces.push(<br key={`char-${i}`} />);
        } else {
          const isInspected = (i === inspectedIndex);

          const toolTipElem = (isInspected && (hitWordInfoElems.length > 0))
            ? {hitWordInfoElems}
            : '';

          const classNames = ['textchar'];
          if (isInspected) {
            classNames.push('inspected');
          } else if (hitWordIndexes.has(i)) {
            classNames.push('highlighted');
          }

          pieces.push(<span className={classNames.join(' ')} onMouseEnter={this.handleCharMouseEnter} onMouseLeave={this.handleCharMouseLeave} data-index={i} key={`char-${i}`}>{toolTipElem}{c});
        }
      }
      return pieces;
    };

    const children = [];
    const textArr = [...annoText.text.trim()]; // split up by unicode chars
    const rubyArr = annoText.ruby.toArray();

    rubyArr.sort((a, b) => a.cpBegin - b.cpBegin);

    let idx = 0;
    for (const r of rubyArr) {
      if (r.cpBegin < idx) {
        throw new Error('Overlapping ruby');
      }

      if (r.cpBegin > idx) {
        children.push(...textRangeToElems(idx, r.cpBegin));
      }

      children.push(<ruby key={`ruby-${r.cpBegin}:${r.cpEnd}`}>{textRangeToElems(r.cpBegin, r.cpEnd)}<rp>(</rp><rt>{r.rubyText}</rt><rp>)</rp></ruby>);

      idx = r.cpEnd;
    }

    // Handle remaining text
    if (idx < textArr.length) {
      children.push(...textRangeToElems(idx, textArr.length));
    }

    const floatWidth = '10em';
    return (

          <ClipboardCopier text={renderAnnoTextToHTML(annoText)} buttonText="Copy HTML" />
          {/*
          {this.state.selectionRange ? (
            <form>
              <button type="button" className="clear-ruby-button">Clear Ruby</button>

              <input className="set-ruby-text" placeholder="Ruby text" /><button type="button" className="set-ruby-button">Set Ruby</button>

              <button type="button" className="clear-words-button">Clear Words</button>

              <input className="mark-word-lemma" placeholder="Lemma (if not base form)" /><button type="button" className="mark-word-button">Mark Word</button>
            </form>
          ) : ''}
          */}

 { this.textContainerElem = el; }}>{children}

    );
  }
}

const TextChunk = ({ chunk, language }) => (
  <AnnoText annoText={chunk.annoText} language={language} />
);

const TextChunksBox = ({ chunks, language, revealed }) => (

{language.toUpperCase()}

{chunks.map(c => (revealed ? <TextChunk chunk={c} key={c.uid} language={language} /> :
(hidden)
))}

);

// Doc
class Doc extends Component {
  constructor(props) {
    super(props);
    this.videoElement = null;
  }

  render() {
    const { doc, actions } = this.props;
    return (

Kind: {doc.kind}

          <VideoImportControls actions={actions} />

Media:

{doc.media.map((o, i) =>
#{i} [{o.language}]
)}

Texts:

{doc.texts.map((o, i) =>
#{i} [{o.language}]
)}

        <VideoMedia media={doc.media} onTimeUpdate={time => { actions.videoTimeUpdate(time); }} mountedVideoElement={(el) => { this.videoElement = el; }} />
        <PlayControls actions={actions} onBack={
            () => {
              if (this.videoElement) {
                const nt = this.videoElement.currentTime - 3.0;
                this.videoElement.currentTime = nt >= 0 ? nt : 0;
              }
            }
          } onTogglePause={
            () => {
              if (this.videoElement) {
                if (this.videoElement.paused) {
                  this.videoElement.play();
                } else {
                  this.videoElement.pause();
                }
              }
            }
          } onHideText={
            () => {
              actions.hideText();
            }
          } onRevealMoreText={
            () => {
              actions.revealMoreText();
            }
          }
        />
        {doc.texts.map((text, i) => <TextChunksBox key={i} chunks={text.currentChunks} language={text.language} revealed={doc.textRevelation > i} />)}

    );
  }
}

// App
const App = connect(
  state => ({
    doc: state.doc,
  }),
  dispatch => ({
    actions: bindActionCreators(allActionCreators, dispatch),
  })
)(({ doc, actions }) => (
  doc ? (
    <Doc doc={doc} actions={actions} />
  ) : (
    <NewDocForm actions={actions} />
  )
));

export default App;
<commit_msg>Link out to other Japanese dictionaries<commit_after>import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Immutable, { Record  } from 'immutable';

import * as allActionCreators from '../actions';

import { renderAnnoTextToHTML } from '../util/annotext';

const languageOptions = [
  { value: 'ja', label: 'Japanese' },
  { value: 'en', label: 'English' },
];

const newlinesToBrs = s => s.split('\n').map((o, i) => {o}
);

// Select, "uncontrolled" but watches changes
class Select extends Component {
  componentWillMount() {
    const { options, onSet } = this.props;
    if (options.length > 0) {
      onSet(options[0].value);
    }
  }

  render() {
    const { options, onSet } = this.props;
    return (
      <select onChange={e => onSet(e.target.value)}>
        {options.map((o, i) => <option key={i} value={o.value}>{o.label}</option>)}
      </select>
    );
  }
}

// ClipboardCopier
class ClipboardCopier extends Component {
  constructor(props) {
    super(props);
    this.onSubmit = this.onSubmit.bind(this);
  }

  onSubmit(e) {
    e.preventDefault();
    // TODO: could check input is set and input.select is truthy, and wrap copy+blur in a try block
    this.inputElem.select();
    document.execCommand('copy');
    this.inputElem.blur();
  }

  render() {
    const { text, buttonText } = this.props;
    return (
      <form onSubmit={this.onSubmit}>
        <input style={{ /*width: '2em'*/ }} type="text" value={text} readOnly ref={(el) => { this.inputElem = el; }} />
        <button type="submit">{buttonText}</button>
      </form>
    );
  }
}

// NewDocForm
const NewDocForm = connect()(
  class extends Component {
    render() {
      const kindOptions = [
        { value: 'video', label: 'Video' },
        { value: 'comic', label: 'Comic' },
      ];
      const { actions } = this.props;
      return (
        <form onSubmit={e => { e.preventDefault(); actions.newDoc(this.kindVal); }}>
          <Select options={kindOptions} onSet={v => { this.kindVal = v; }} />
          <button type="submit">Create New Document</button>
        </form>
      );
    }
  }
);

// FileChooserForm
const FileChooser = ({ label, accept, onChoose }) => (
  {label} <input type="file" accept={accept} onChange={e => { onChoose(e.target.files[0]); e.target.value = null; }} />
);

// VideoImportControls
class VideoImportControls extends Component {
  render() {
    const { actions } = this.props;
    return (

        <form>
          <FileChooser label="Import Video" accept="video/*" onChoose={(file) => { actions.importVideoFile(file, this.videoLanguageVal); }} />
          <Select options={languageOptions} onSet={v => { this.videoLanguageVal = v; }} />
        </form>
        <form>
          <FileChooser label="Import Subs (SRT)" accept=".srt" onChoose={(file) => { actions.importSubsFile(file, this.subLanguageVal); }} />
          <Select options={languageOptions} onSet={v => { this.subLanguageVal = v; }} />
        </form>

    );
  }
}

class VideoMedia extends Component {
  render() {
    const { media, onTimeUpdate, mountedVideoElement } = this.props;
    return (

{media.size ? (
        <video src={media.first().videoURL} controls onTimeUpdate={e => { onTimeUpdate(e.target.currentTime); }} ref={(el) => { mountedVideoElement(el); }} />
      ) : ''
      }

    );
  }
}

class PlayControls extends Component {
  constructor(props) {
    super(props);
    this.handleKeyDown = this.handleKeyDown.bind(this);
  }

  componentDidMount() {
    document.body.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    document.body.removeEventListener('keydown', this.handleKeyDown);
  }

  handleKeyDown(e) {
    const { onBack, onTogglePause, onHideText, onRevealMoreText } = this.props;

    // console.log(e);
    if (!e.repeat) {
      switch (e.keyCode) {
        case 65: // a
          onBack();
          break;

        case 68: // d
          onHideText();
          break;

        case 70: // f
          onRevealMoreText();
          break;

        case 32: // space
          onTogglePause();
          break;

        default:
          // ignore
          break;
      }
    }
  }

  render() {
    const { onBack, onTogglePause, onHideText, onRevealMoreText } = this.props;
    return (
      <form style={{ textAlign: 'center', margin: '10px auto' }}>
        <button type="button" onClick={() => { onBack(); }}>Jump Back [A]</button>
        <button type="button" onClick={() => { onHideText(); }}>Hide Texts [D]</button>
        <button type="button" onClick={() => { onRevealMoreText(); }}>Reveal Next Text [F]</button>
        <button type="button" onClick={() => { onTogglePause(); }}>Play/Pause [Space]</button>
      </form>
    );
  }
}

const isAncestorNode = (potentialAncestor, given) => {
  let n = given;
  while (true) {
    if (n === potentialAncestor) {
      return true;
    }
    if (n.parentNode) {
      n = n.parentNode;
    } else {
      return false;
    }
  }
}

const CPRange = new Record({
  cpBegin: null,
  cpEnd: null,
});

class AnnoText extends Component {
  constructor(props) {
    super(props);
    this.state = {
      selectionRange: null,
      inspectedIndex: null, // codepoint index of char we're doing a "tooltip" for
    };
    this.tooltipTimeout = null; // it does not work to have this in state
    this.handleSelectionChange = this.handleSelectionChange.bind(this);
    this.handleCharMouseEnter = this.handleCharMouseEnter.bind(this);
    this.handleCharMouseLeave = this.handleCharMouseLeave.bind(this);
  }

  componentDidMount() {
    document.addEventListener('selectionchange', this.handleSelectionChange);
  }

  componentWillUnmount() {
    document.removeEventListener('selectionchange', this.handleSelectionChange);
    this.clearTooltipTimeout();
  }

  clearTooltipTimeout() {
    if (this.tooltipTimeout) {
      window.clearTimeout(this.tooltipTimeout);
      this.tooltipTimeout = null;
    }
  }

  setTooltipTimeout() {
    if (this.tooltipTimeout) {
      window.clearTimeout(this.tooltipTimeout);
    }
    this.tooltipTimeout = window.setTimeout(
      () => { this.setState({ inspectedIndex: null }); this.tooltipTimeout = null; },
      500
    );
  }

  currentSelectionIndexRange() {
    const selObj = window.getSelection();
    if (selObj.isCollapsed) {
      return null;
    } else {
      const selRange = selObj.getRangeAt(0);
      const ancestorNode = selRange.commonAncestorContainer;
      if (isAncestorNode(this.textContainerElem, ancestorNode)) {
        const frag = selRange.cloneContents();

        if (ancestorNode.nodeType === Node.TEXT_NODE) {
          // If we selected text that is inside any element, we'll get a text node.
          // It might not be useful text (might be furigana), so need to check what element it's inside.
          // For now we can assume that its immediate parent will be a .textchar element if it's a good character.
          const parent = ancestorNode.parentNode;
          if (parent.classList.contains('textchar')) {
            const cpIndex = +parent.getAttribute('data-index');
            return new CPRange({cpBegin: cpIndex, cpEnd: cpIndex+1});
          } else {
            return null;
          }
        } else {
          const cpIndexes = [];

          // Iterate over all .textchar elems inside the fragment
          for (const el of frag.querySelectorAll('.textchar')) {
            // We might get extraneous .textchar elems that have nothing inside them. Only consider ones that have text inside.
            if (el.textContent.length > 0) {
              const cpIndex = +el.getAttribute('data-index');
              cpIndexes.push(cpIndex);
            }
          }

          if (cpIndexes.length === 0) {
            return null;
          } else {
            var minIndex = Math.min(...cpIndexes);
            var maxIndex = Math.max(...cpIndexes);
            if ((minIndex !== cpIndexes[0]) || (maxIndex !== cpIndexes[cpIndexes.length-1])) {
              throw new Error('Unexpected');
            }
            return new CPRange({cpBegin: minIndex, cpEnd: maxIndex+1});
          }
        }
      } else {
        return null;
      }
    }
  }

  handleSelectionChange(e) {
    const newSelRange = this.currentSelectionIndexRange();
    if (!Immutable.is(newSelRange, this.state.selectionRange)) {
      this.setState({selectionRange: newSelRange});
    }
  }

  handleCharMouseEnter(e) {
    const cpIndex = +e.currentTarget.getAttribute('data-index');
    this.setState({inspectedIndex: cpIndex});
    this.clearTooltipTimeout();
  }

  handleCharMouseLeave(e) {
    this.setTooltipTimeout();
  }

  render() {
    const { annoText, language } = this.props;

    const inspectedIndex = this.state.inspectedIndex;
    const hitWords = [];
    const hitWordInfoElems = [];
    const hitWordIndexes = new Set();
    if (inspectedIndex !== null) {
      annoText.words.forEach((word) => {
        if ((inspectedIndex >= word.cpBegin) && (inspectedIndex < word.cpEnd)) {
          hitWords.push(word);
        }
      });
      hitWords.forEach((hitWord) => {
        var encLemma = encodeURIComponent(hitWord.lemma);
        hitWordInfoElems.push(
          <span key={`wordinfo-${hitWord.cpBegin}:${hitWord.cpEnd}`}>{hitWord.lemma}

              Weblio{' '}
              ALC{' '}
              goo{' '}
              Tangorin

        );
      });
      hitWords.forEach((hitWord) => {
        for (let i = hitWord.cpBegin; i < hitWord.cpEnd; i++) {
          hitWordIndexes.add(i);
        }
      });
    }

    const textRangeToElems = (cpBegin, cpEnd) => {
      const pieces = [];
      for (let i = cpBegin; i < cpEnd; i++) {
        const c = textArr[i];
        if (c === '\n') {
          pieces.push(<br key={`char-${i}`} />);
        } else {
          const isInspected = (i === inspectedIndex);

          const toolTipElem = (isInspected && (hitWordInfoElems.length > 0))
            ? {hitWordInfoElems}
            : '';

          const classNames = ['textchar'];
          if (isInspected) {
            classNames.push('inspected');
          } else if (hitWordIndexes.has(i)) {
            classNames.push('highlighted');
          }

          pieces.push(<span className={classNames.join(' ')} onMouseEnter={this.handleCharMouseEnter} onMouseLeave={this.handleCharMouseLeave} data-index={i} key={`char-${i}`}>{toolTipElem}{c});
        }
      }
      return pieces;
    };

    const children = [];
    const textArr = [...annoText.text.trim()]; // split up by unicode chars
    const rubyArr = annoText.ruby.toArray();

    rubyArr.sort((a, b) => a.cpBegin - b.cpBegin);

    let idx = 0;
    for (const r of rubyArr) {
      if (r.cpBegin < idx) {
        throw new Error('Overlapping ruby');
      }

      if (r.cpBegin > idx) {
        children.push(...textRangeToElems(idx, r.cpBegin));
      }

      children.push(<ruby key={`ruby-${r.cpBegin}:${r.cpEnd}`}>{textRangeToElems(r.cpBegin, r.cpEnd)}<rp>(</rp><rt>{r.rubyText}</rt><rp>)</rp></ruby>);

      idx = r.cpEnd;
    }

    // Handle remaining text
    if (idx < textArr.length) {
      children.push(...textRangeToElems(idx, textArr.length));
    }

    const floatWidth = '10em';
    return (

          <ClipboardCopier text={renderAnnoTextToHTML(annoText)} buttonText="Copy HTML" />
          {/*
          {this.state.selectionRange ? (
            <form>
              <button type="button" className="clear-ruby-button">Clear Ruby</button>

              <input className="set-ruby-text" placeholder="Ruby text" /><button type="button" className="set-ruby-button">Set Ruby</button>

              <button type="button" className="clear-words-button">Clear Words</button>

              <input className="mark-word-lemma" placeholder="Lemma (if not base form)" /><button type="button" className="mark-word-button">Mark Word</button>
            </form>
          ) : ''}
          */}

 { this.textContainerElem = el; }}>{children}

    );
  }
}

const TextChunk = ({ chunk, language }) => (
  <AnnoText annoText={chunk.annoText} language={language} />
);

const TextChunksBox = ({ chunks, language, revealed }) => (

{language.toUpperCase()}

{chunks.map(c => (revealed ? <TextChunk chunk={c} key={c.uid} language={language} /> :
(hidden)
))}

);

// Doc
class Doc extends Component {
  constructor(props) {
    super(props);
    this.videoElement = null;
  }

  render() {
    const { doc, actions } = this.props;
    return (

Kind: {doc.kind}

          <VideoImportControls actions={actions} />

Media:

{doc.media.map((o, i) =>
#{i} [{o.language}]
)}

Texts:

{doc.texts.map((o, i) =>
#{i} [{o.language}]
)}

        <VideoMedia media={doc.media} onTimeUpdate={time => { actions.videoTimeUpdate(time); }} mountedVideoElement={(el) => { this.videoElement = el; }} />
        <PlayControls actions={actions} onBack={
            () => {
              if (this.videoElement) {
                const nt = this.videoElement.currentTime - 3.0;
                this.videoElement.currentTime = nt >= 0 ? nt : 0;
              }
            }
          } onTogglePause={
            () => {
              if (this.videoElement) {
                if (this.videoElement.paused) {
                  this.videoElement.play();
                } else {
                  this.videoElement.pause();
                }
              }
            }
          } onHideText={
            () => {
              actions.hideText();
            }
          } onRevealMoreText={
            () => {
              actions.revealMoreText();
            }
          }
        />
        {doc.texts.map((text, i) => <TextChunksBox key={i} chunks={text.currentChunks} language={text.language} revealed={doc.textRevelation > i} />)}

    );
  }
}

// App
const App = connect(
  state => ({
    doc: state.doc,
  }),
  dispatch => ({
    actions: bindActionCreators(allActionCreators, dispatch),
  })
)(({ doc, actions }) => (
  doc ? (
    <Doc doc={doc} actions={actions} />
  ) : (
    <NewDocForm actions={actions} />
  )
));

export default App;
<|endoftext|>


<commit_before>'use strict'

const stream = require('pull-stream')
const pullDecode = require('pull-utf8-decoder')
const isNode = require('detect-node')
const PullBlobStore = (isNode) ? require('fs-pull-blob-store') : require('idb-pull-blob-store')

module.exports = class DatabaseManager {
  constructor (fileMetadata) {
    this.metadata = fileMetadata
    this.config = new PullBlobStore('config')
  }
  setupFileStorage (aUserHash) {
    this.files = new PullBlobStore('files-' + aUserHash)
  }

  configExists () {
    let config = this.config
    return new Promise(function (resolve, reject) {
      config.exists('config', function (err, exists) {
        if (err) return reject(err)
        resolve(exists)
      })
    })
  }

  getConfig () {
    let config = this.config
    return new Promise(function (resolve, reject) {
      stream(
        config.read('config'),
        pullDecode(),
        stream.drain(function (data) { resolve(JSON.parse(data)) },
          function (err) {
            if (err) return reject(err)
          })
      )
    })
  }

  storeConfig (jsonConfig) {
    return new Promise((resolve, reject) => {
      stream(
        stream.once(new Buffer(JSON.stringify(jsonConfig))),
        this.config.write('config', function (err) {
          if (err) return reject(err)
          resolve()
        })
      )
    })
  }

  fileExists (fileHash) {
    let storage = this.files
    return new Promise(function (resolve, reject) {
      storage.exists(fileHash, function (err, exists) {
        if (err) return reject(err)
        resolve(exists)
      })
    })
  }
  storeFile () {

  }
  getFile (fileHash) {
    return new Promise((resolve, reject) => {
      stream(
        this.getFileReader(fileHash),
        stream.flatten(),
        stream.collect(function (err, arr) {
          if (err) return reject(err)
          resolve(arr)
        })
      )
    })
  }
  getFileWriter (fileHash, cb) {
    return this.files.write(fileHash, function (err) {
      if (err) return cb(err)
      cb()
    })
  }
  getFileReader (fileHash) {
    return this.files.read(fileHash)
  }
  deleteFile (fileHash) {
    return new Promise((resolve, reject) => {
      this.files.remove(fileHash, function (err) {
        if (err) return reject(err)
        resolve()
      })
    })
  }
}
<commit_msg>promise -> deferred<commit_after>'use strict'

const stream = require('pull-stream')
const pullDecode = require('pull-utf8-decoder')
const isNode = require('detect-node')
const PullBlobStore = (isNode) ? require('fs-pull-blob-store') : require('idb-pull-blob-store')

const deferred = require('deferred')

module.exports = class DatabaseManager {
  constructor (fileMetadata) {
    this.metadata = fileMetadata
    this.config = new PullBlobStore('config')
  }
  setupFileStorage (aUserHash) {
    this.files = new PullBlobStore('files-' + aUserHash)
  }

  configExists () {
    return deferred.promisify(this.config.exists.bind(this.config))('config')
  }

  getConfig () {
    let config = this.config
    return new Promise(function (resolve, reject) {
      stream(
        config.read('config'),
        pullDecode(),
        stream.drain(function (data) { resolve(JSON.parse(data)) },
          function (err) {
            if (err) return reject(err)
          })
      )
    })
  }

  storeConfig (jsonConfig) {
    return new Promise((resolve, reject) => {
      stream(
        stream.once(new Buffer(JSON.stringify(jsonConfig))),
        this.config.write('config', function (err) {
          if (err) return reject(err)
          resolve()
        })
      )
    })
  }

  fileExists (fileHash) {
    return deferred.promisify(this.files.exists.bind(this.files))(fileHash)
  }
  storeFile () {

  }
  getFile (fileHash) {
    var def = deferred()
    stream(
        this.getFileReader(fileHash),
        stream.flatten(),
        stream.collect(function (err, arr) {
          if (err) return def.reject(err)
          def.resolve(arr)
        })
      )
    return def.promise
  }
  getFileWriter (fileHash, cb) {
    return this.files.write(fileHash, function (err) {
      if (err) return cb(err)
      cb()
    })
  }
  getFileReader (fileHash) {
    return this.files.read(fileHash)
  }
  deleteFile (fileHash) {
    return deferred.promisify(this.files.remove)(fileHash)
  }
}
<|endoftext|>


<commit_before>
// Single Date Formatting
// -------------------------------------------------------------------------------------------------

// call this if you want Moment's original format method to be used
function oldMomentFormat(mom, formatStr) {
	return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
}

// Formats `date` with a Moment formatting string, but allow our non-zero areas and
// additional token.
function formatDate(date, formatStr) {
	return formatDateWithChunks(date, getFormatStringChunks(formatStr));
}

function formatDateWithChunks(date, chunks) {
	var s = '';
	var i;

	for (i=0; i<chunks.length; i++) {
		s += formatDateWithChunk(date, chunks[i]);
	}

	return s;
}

// addition formatting tokens we want recognized
var tokenOverrides = {
	t: function(date) { // "a" or "p"
		return oldMomentFormat(date, 'a').charAt(0);
	},
	T: function(date) { // "A" or "P"
		return oldMomentFormat(date, 'A').charAt(0);
	}
};

function formatDateWithChunk(date, chunk) {
	var token;
	var maybeStr;

	if (typeof chunk === 'string') { // a literal string
		return chunk;
	}
	else if ((token = chunk.token)) { // a token, like "YYYY"
		if (tokenOverrides[token]) {
			return tokenOverrides[token](date); // use our custom token
		}
		return oldMomentFormat(date, token);
	}
	else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
		maybeStr = formatDateWithChunks(date, chunk.maybe);
		if (maybeStr.match(/[1-9]/)) {
			return maybeStr;
		}
	}

	return '';
}

// Date Range Formatting
// -------------------------------------------------------------------------------------------------
// TODO: make it work with timezone offset

// Using a formatting string meant for a single date, generate a range string, like
// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
// If the dates are the same as far as the format string is concerned, just return a single
// rendering of one date, without any separator.
function formatRange(date1, date2, formatStr, separator, isRTL) {
	var localeData;

	date1 = FC.moment.parseZone(date1);
	date2 = FC.moment.parseZone(date2);

	localeData = date1.localeData();

	// Expand localized format strings, like "LL" -> "MMMM D YYYY"
	formatStr = localeData.longDateFormat(formatStr) || formatStr;
	// BTW, this is not important for `formatDate` because it is impossible to put custom tokens
	// or non-zero areas in Moment's localized format strings.

	separator = separator || ' - ';

	return formatRangeWithChunks(
		date1,
		date2,
		getFormatStringChunks(formatStr),
		separator,
		isRTL
	);
}
FC.formatRange = formatRange; // expose

function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
	var unzonedDate1 = date1.clone().stripZone(); // for formatSimilarChunk
	var unzonedDate2 = date2.clone().stripZone(); // "
	var chunkStr; // the rendering of the chunk
	var leftI;
	var leftStr = '';
	var rightI;
	var rightStr = '';
	var middleI;
	var middleStr1 = '';
	var middleStr2 = '';
	var middleStr = '';

	// Start at the leftmost side of the formatting string and continue until you hit a token
	// that is not the same between dates.
	for (leftI=0; leftI<chunks.length; leftI++) {
		chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunks[leftI]);
		if (chunkStr === false) {
			break;
		}
		leftStr += chunkStr;
	}

	// Similarly, start at the rightmost side of the formatting string and move left
	for (rightI=chunks.length-1; rightI>leftI; rightI--) {

		// If current chunk is on the boundary of unique date-content, and is a special-case
		// date-formatting postfix character, then don't consume it. Consider it unique date-content.
		// TODO: configurable special-case characters.
		if (rightI - 1 === leftI && chunks[rightI] === '.') {
			break;
		}

		chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunks[rightI]);
		if (chunkStr === false) {
			break;
		}
		rightStr = chunkStr + rightStr;
	}

	// The area in the middle is different for both of the dates.
	// Collect them distinctly so we can jam them together later.
	for (middleI=leftI; middleI<=rightI; middleI++) {
		middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
		middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
	}

	if (middleStr1 || middleStr2) {
		if (isRTL) {
			middleStr = middleStr2 + separator + middleStr1;
		}
		else {
			middleStr = middleStr1 + separator + middleStr2;
		}
	}

	return leftStr + middleStr + rightStr;
}

var similarUnitMap = {
	Y: 'year',
	M: 'month',
	D: 'day', // day of month
	d: 'day', // day of week
	// prevents a separator between anything time-related...
	A: 'second', // AM/PM
	a: 'second', // am/pm
	T: 'second', // A/P
	t: 'second', // a/p
	H: 'second', // hour (24)
	h: 'second', // hour (12)
	m: 'second', // minute
	s: 'second' // second
};
// TODO: week maybe?

// Given a formatting chunk, and given that both dates are similar in the regard the
// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
function formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunk) {
	var token;
	var unit;

	if (typeof chunk === 'string') { // a literal string
		return chunk;
	}
	else if ((token = chunk.token)) {
		unit = similarUnitMap[token.charAt(0)];

		// are the dates the same for this unit of measurement?
		// use the unzoned dates for this calculation because unreliable when near DST (bug #2396)
		if (unit && unzonedDate1.isSame(unzonedDate2, unit)) {
			return oldMomentFormat(date1, token); // would be the same if we used `date2`
			// BTW, don't support custom tokens
		}
	}

	return false; // the chunk is NOT the same for the two dates
	// BTW, don't support splitting on non-zero areas
}

// Chunking Utils
// -------------------------------------------------------------------------------------------------

var formatStringChunkCache = {};

function getFormatStringChunks(formatStr) {
	if (formatStr in formatStringChunkCache) {
		return formatStringChunkCache[formatStr];
	}
	return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
}

// Break the formatting string into an array of chunks
function chunkFormatString(formatStr) {
	var chunks = [];
	var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination
	var match;

	while ((match = chunker.exec(formatStr))) {
		if (match[1]) { // a literal string inside [ ... ]
			chunks.push(match[1]);
		}
		else if (match[2]) { // non-zero formatting inside ( ... )
			chunks.push({ maybe: chunkFormatString(match[2]) });
		}
		else if (match[3]) { // a formatting token
			chunks.push({ token: match[3] });
		}
		else if (match[5]) { // an unenclosed literal string

			// separate non-whitespace spans from whitespace spans.
			// TODO: generalize whitespace-splitting instead of hardcoding for one scenario.
			if (match[5] === '. ') {
				chunks.push('.', ' ');
			}
			else {
				chunks.push(match[5]);
			}
		}
	}

	return chunks;
}

// Misc Utils
// -------------------------------------------------------------------------------------------------

// granularity only goes up until day
// TODO: unify with similarUnitMap
var tokenGranularities = {
	Y: { value: 1, unit: 'year' },
	M: { value: 2, unit: 'month' },
	W: { value: 3, unit: 'week' },
	w: { value: 3, unit: 'week' },
	D: { value: 4, unit: 'day' }, // day of month
	d: { value: 4, unit: 'day' } // day of week
};

// returns a unit string, either 'year', 'month', 'day', or null
// for the most granular formatting token in the string.
FC.queryMostGranularFormatUnit = function(formatStr) {
	var chunks = getFormatStringChunks(formatStr);
	var i, chunk;
	var candidate;
	var best;

	for (i = 0; i < chunks.length; i++) {
		chunk = chunks[i];
		if (chunk.token) {
			candidate = tokenGranularities[chunk.token.charAt(0)];
			if (candidate) {
				if (!best || candidate.value > best.value) {
					best = candidate;
				}
			}
		}
	}

	if (best) {
		return best.unit;
	}

	return null;
};
<commit_msg>lots of range-formatting refactoring. better performance too<commit_after>(function() {

// exports
FC.formatDate = formatDate;
FC.formatRange = formatRange;
FC.oldMomentFormat = oldMomentFormat;
FC.queryMostGranularFormatUnit = queryMostGranularFormatUnit;

// Config
// ---------------------------------------------------------------------------------------------------------------------

/*
Inserted between chunks in the fake ("intermediate") formatting string.
Important that it passes as whitespace (\s) because moment often identifies non-standalone months
via a regexp with an \s.
*/
var PART_SEPARATOR = '\u000b'; // vertical tab

/*
Inserted as the first character of a literal-text chunk to indicate that the literal text is not actually literal text,
but rather, a "special" token that has custom rendering (see specialTokens map).
*/
var SPECIAL_TOKEN_MARKER = '\u001f'; // information separator 1

/*
Inserted at the beginning and end of a span of text that must have non-zero numeric characters.
Handling of these markers is done in a post-processing step at the very end of text rendering.
*/
var MAYBE_MARKER = '\u001e'; // information separator 2
var MAYBE_REGEXP = new RegExp(MAYBE_MARKER + '([^' + MAYBE_MARKER + ']*)' + MAYBE_MARKER, 'g'); // must be global

/*
Addition formatting tokens we want recognized
*/
var specialTokens = {
	t: function(date) { // "a" or "p"
		return oldMomentFormat(date, 'a').charAt(0);
	},
	T: function(date) { // "A" or "P"
		return oldMomentFormat(date, 'A').charAt(0);
	}
};

/*
The first characters of formatting tokens for units that are 1 day or larger.
`value` is for ranking relative size (lower means bigger).
`unit` is a normalized unit, used for comparing moments.
*/
var largeTokenMap = {
	Y: { value: 1, unit: 'year' },
	M: { value: 2, unit: 'month' },
	W: { value: 3, unit: 'week' }, // ISO week
	w: { value: 3, unit: 'week' }, // local week
	D: { value: 4, unit: 'day' }, // day of month
	d: { value: 4, unit: 'day' } // day of week
};

// Single Date Formatting
// ---------------------------------------------------------------------------------------------------------------------

/*
Formats `date` with a Moment formatting string, but allow our non-zero areas and special token
*/
function formatDate(date, formatStr) {
	return renderFakeFormatString(
		getParsedFormatString(formatStr).fakeFormatString,
		date
	);
}

/*
Call this if you want Moment's original format method to be used
*/
function oldMomentFormat(mom, formatStr) {
	return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
}

// Date Range Formatting
// -------------------------------------------------------------------------------------------------
// TODO: make it work with timezone offset

/*
Using a formatting string meant for a single date, generate a range string, like
"Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
If the dates are the same as far as the format string is concerned, just return a single
rendering of one date, without any separator.
*/
function formatRange(date1, date2, formatStr, separator, isRTL) {
	var localeData;

	date1 = FC.moment.parseZone(date1);
	date2 = FC.moment.parseZone(date2);

	localeData = date1.localeData();

	// Expand localized format strings, like "LL" -> "MMMM D YYYY".
	// BTW, this is not important for `formatDate` because it is impossible to put custom tokens
	// or non-zero areas in Moment's localized format strings.
	formatStr = localeData.longDateFormat(formatStr) || formatStr;

	return renderParsedFormat(
		getParsedFormatString(formatStr),
		date1,
		date2,
		separator || ' - ',
		isRTL
	);
}

/*
Renders a range with an already-parsed format string.
*/
function renderParsedFormat(parsedFormat, date1, date2, separator, isRTL) {
	var sameUnits = parsedFormat.sameUnits;
	var unzonedDate1 = date1.clone().stripZone(); // for same-unit comparisons
	var unzonedDate2 = date2.clone().stripZone(); // "

	var renderedParts1 = renderFakeFormatStringParts(parsedFormat.fakeFormatString, date1);
	var renderedParts2 = renderFakeFormatStringParts(parsedFormat.fakeFormatString, date2);

	var leftI;
	var leftStr = '';
	var rightI;
	var rightStr = '';
	var middleI;
	var middleStr1 = '';
	var middleStr2 = '';
	var middleStr = '';

	// Start at the leftmost side of the formatting string and continue until you hit a token
	// that is not the same between dates.
	for (
		leftI = 0;
		leftI < sameUnits.length && (!sameUnits[leftI] || unzonedDate1.isSame(unzonedDate2, sameUnits[leftI]));
		leftI++
	) {
		leftStr += renderedParts1[leftI];
	}

	// Similarly, start at the rightmost side of the formatting string and move left
	for (
		rightI = sameUnits.length - 1;
		rightI > leftI && (!sameUnits[rightI] || unzonedDate1.isSame(unzonedDate2, sameUnits[rightI]));
		rightI--
	) {
		// If current chunk is on the boundary of unique date-content, and is a special-case
		// date-formatting postfix character, then don't consume it. Consider it unique date-content.
		// TODO: make configurable
		if (rightI - 1 === leftI && renderedParts1[rightI] === '.') {
			break;
		}

		rightStr = renderedParts1[rightI] + rightStr;
	}

	// The area in the middle is different for both of the dates.
	// Collect them distinctly so we can jam them together later.
	for (middleI = leftI; middleI <= rightI; middleI++) {
		middleStr1 += renderedParts1[middleI];
		middleStr2 += renderedParts2[middleI];
	}

	if (middleStr1 || middleStr2) {
		if (isRTL) {
			middleStr = middleStr2 + separator + middleStr1;
		}
		else {
			middleStr = middleStr1 + separator + middleStr2;
		}
	}

	return processMaybeMarkers(
		leftStr + middleStr + rightStr
	);
}

// Format String Parsing
// ---------------------------------------------------------------------------------------------------------------------

var parsedFormatStrCache = {};

/*
Returns a parsed format string, leveraging a cache.
*/
function getParsedFormatString(formatStr) {
	return parsedFormatStrCache[formatStr] ||
		(parsedFormatStrCache[formatStr] = parseFormatString(formatStr));
}

/*
Parses a format string into the following:
- fakeFormatString: a momentJS formatting string, littered with special control characters that get post-processed.
- sameUnits: for every part in fakeFormatString, if the part is a token, the value will be a unit string (like "day"),
  that indicates how similar a range's start & end must be in order to share the same formatted text.
  If not a token, then the value is null.
  Always a flat array (not nested liked "chunks").
*/
function parseFormatString(formatStr) {
	var chunks = chunkFormatString(formatStr);

	return {
		fakeFormatString: buildFakeFormatString(chunks),
		sameUnits: buildSameUnits(chunks)
	};
}

/*
Break the formatting string into an array of chunks.
A 'maybe' chunk will have nested chunks.
*/
function chunkFormatString(formatStr) {
	var chunks = [];
	var match;

	// TODO: more descrimination
	// \4 is a backreference to the first character of a multi-character set.
	var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;

	while ((match = chunker.exec(formatStr))) {
		if (match[1]) { // a literal string inside [ ... ]
			chunks.push.apply(chunks, // append
				splitStringLiteral(match[1])
			);
		}
		else if (match[2]) { // non-zero formatting inside ( ... )
			chunks.push({ maybe: chunkFormatString(match[2]) });
		}
		else if (match[3]) { // a formatting token
			chunks.push({ token: match[3] });
		}
		else if (match[5]) { // an unenclosed literal string
			chunks.push.apply(chunks, // append
				splitStringLiteral(match[5])
			);
		}
	}

	return chunks;
}

/*
Potentially splits a literal-text string into multiple parts. For special cases.
*/
function splitStringLiteral(s) {
	if (s === '. ') {
		return [ '.', ' ' ]; // for locales with periods bound to the end of each year/month/date
	}
	else {
		return [ s ];
	}
}

/*
Given chunks parsed from a real format string, generate a fake (aka "intermediate") format string with special control
characters that will eventually be given to moment for formatting, and then post-processed.
*/
function buildFakeFormatString(chunks) {
	var parts = [];
	var i, chunk;

	for (i = 0; i < chunks.length; i++) {
		chunk = chunks[i];

		if (typeof chunk === 'string') {
			parts.push('[' + chunk + ']');
		}
		else if (chunk.token) {
			if (chunk.token in specialTokens) {
				parts.push(
					SPECIAL_TOKEN_MARKER + // useful during post-processing
					'[' + chunk.token + ']' // preserve as literal text
				);
			}
			else {
				parts.push(chunk.token); // unprotected text implies a format string
			}
		}
		else if (chunk.maybe) {
			parts.push(
				MAYBE_MARKER + // useful during post-processing
				buildFakeFormatString(chunk.maybe) +
				MAYBE_MARKER
			);
		}
	}

	return parts.join(PART_SEPARATOR);
}

/*
Given parsed chunks from a real formatting string, generates an array of unit strings (like "day") that indicate
in which regard two dates must be similar in order to share range formatting text.
The `chunks` can be nested (because of "maybe" chunks), however, the returned array will be flat.
*/
function buildSameUnits(chunks) {
	var units = [];
	var i, chunk;
	var tokenInfo;

	for (i = 0; i < chunks.length; i++) {
		chunk = chunks[i];

		if (chunk.token) {
			tokenInfo = largeTokenMap[chunk.token.charAt(0)];
			units.push(tokenInfo ? tokenInfo.unit : 'second'); // default to a very strict same-second
		}
		else if (chunk.maybe) {
			units.push.apply(units, // append
				buildSameUnits(chunk.maybe)
			);
		}
		else {
			units.push(null);
		}
	}

	return units;
}

// Rendering to text
// ---------------------------------------------------------------------------------------------------------------------

/*
Formats a date with a fake format string, post-processes the control characters, then returns.
*/
function renderFakeFormatString(fakeFormatString, date) {
	return processMaybeMarkers(
		renderFakeFormatStringParts(fakeFormatString, date).join('')
	);
}

/*
Formats a date into parts that will have been post-processed, EXCEPT for the "maybe" markers.
*/
function renderFakeFormatStringParts(fakeFormatString, date) {
	var parts = [];
	var fakeRender = oldMomentFormat(date, fakeFormatString);
	var fakeParts = fakeRender.split(PART_SEPARATOR);
	var i, fakePart;

	for (i = 0; i < fakeParts.length; i++) {
		fakePart = fakeParts[i];

		if (fakePart.charAt(0) === SPECIAL_TOKEN_MARKER) {
			parts.push(
				specialTokens[fakePart.substring(1)](date) // the literal string IS the token's name
			);
		}
		else {
			parts.push(fakePart);
		}
	}

	return parts;
}

/*
Accepts an almost-finally-formatted string and processes the "maybe" control characters, returning a new string.
*/
function processMaybeMarkers(s) {
	return s.replace(MAYBE_REGEXP, function(m0, m1) { // regex assumed to have 'g' flag
		if (m1.match(/[1-9]/)) { // any non-zero numeric characters?
			return m1;
		}
		else {
			return '';
		}
	});
}

// Misc Utils
// -------------------------------------------------------------------------------------------------

/* TODO: test with Scheduler
Returns a unit string, either 'year', 'month', 'day', or null for the most granular formatting token in the string.
*/
function queryMostGranularFormatUnit(formatStr) {
	var chunks = chunkFormatString(formatStr);
	var i, chunk;
	var candidate;
	var best;

	for (i = 0; i < chunks.length; i++) {
		chunk = chunks[i];

		if (chunk.token) {
			candidate = largeTokenMap[chunk.token.charAt(0)];
			if (candidate) {
				if (!best || candidate.value > best.value) {
					best = candidate;
				}
			}
		}
	}

	if (best) {
		return best.unit;
	}

	return null;
};

})();

// quick local references
var formatDate = FC.formatDate;
var formatRange = FC.formatRange;
var oldMomentFormat = FC.oldMomentFormat;
<|endoftext|>


<commit_before>import { DNAConfig } from './dna-config.next.js';

/**
 * Helper class
 * @class DNAHelper
 */
export class DNAHelper {
    /**
     * Trigger `onRegister` callbacks and register the Custom Element
     * (if the `skipWebComponent` option !== true).
     * @param {Function|String} fn The definition or the tag name of the Custom Element.
     * @param {Object} options A set of options for the registration of the Custom Element.
     * @return {Function} The Custom Element constructor.
     */
    static register(fn, options = {}) {
        let scope = fn;
        let config = options;
        let tagName;
        let res;

        if (typeof fn === 'string') {
            tagName = fn;
            if (typeof config === 'function') {
                scope = config;
                config = {};
            } else if (typeof config.prototype !== 'undefined') {
                scope = config.prototype;
            }
        }
        if (typeof scope === 'function') {
            tagName = (
                tagName ||
                config.tagName ||
                (scope.hasOwnProperty('tagName') && scope.tagName) ||
                DNAHelper.classToElement(scope)
            );
            config.prototype = scope.prototype;
            if (!config.extends && typeof scope.extends === 'string') {
                config.extends = scope.extends;
            }
        } else {
            config.prototype = scope;
            scope = function(...args) {
                config.prototype.constructor.apply(this, args);
            };
            scope.prototype = config.prototype;
        }
        Object.defineProperty(scope, 'tagName', {
            configurable: true,
            get: () => tagName,
        });
        Object.defineProperty(scope.prototype, 'is', {
            configurable: false,
            get: () => tagName,
        });
        if (typeof scope.onRegister === 'function') {
            scope.onRegister.call(scope);
        }
        if (DNAConfig.useWebComponents) {
            res = document.registerElement(tagName, config);
        } else {
            res = function() {
                let el = document.createElement(tagName);
                Object.setPrototypeOf(el, scope.prototype);
                setTimeout(() => el.createdCallback(), 0);
                return el;
            };
        }
        if (typeof scope === 'function') {
            res.prototype.constructor = scope;
        }
        return res;
    }
    /**
     * Convert a Class name into HTML tag.
     * @param {Class} fn Grab the tag name from this class.
     * @return {String} The tag name for the Custom Element.
     */
    static classToElement(fn) {
        let name = fn.name || fn.toString().match(/^function\s*([^\s(]+)/)[1];
        if (!name) {
            return undefined;
        }
        return fn.name
            .replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
            .replace(/^\-/, '');
    }
    /**
     * Convert a HTML tag into a Class name.
     * @param {Class} fn Grab the class name from this tag.
     * @return {String} The class name for the Custom Element.
     */
    static elementToClass(tag) {
        return tag
            .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match) => {
                if (+match === 0) return '';
                return match.toUpperCase();
            })
            .replace(/[\-|\_]/g, '');
    }
    /**
     * Get an element's property descriptor.
     * @param {Function} ctr The element constructor.
     * @param {string} prop The property to analyze.
     * @return {object} The descriptor of the property.
     */
    static getDescriptor(ctr, prop) {
        let res;
        if (ctr) {
            res = Object.getOwnPropertyDescriptor(ctr, prop);
            let proto = Object.getPrototypeOf(ctr);
            if (!res && proto) {
                res = DNAHelper.getDescriptor(proto, prop);
            }
        }
        return res;
    }
    /**
     * Wrap a property descriptor get function.
     * @param {string} prop The property to wrap.
     * @param {object} descriptor The property descriptor.
     * @return {Function} The descriptor get function wrapped.
     */
    static wrapDescriptorGet(prop, descriptor) {
        return function() {
            let res;
            if (typeof descriptor.get === 'function') {
                try {
                    res = descriptor.get.call(this);
                } catch (ex) {
                    res = this[`__${prop}`];
                }
            } else {
                res = this[`__${prop}`];
            }
            return res;
        };
    }
    /**
     * Wrap a property descriptor set function.
     * @param {string} prop The property to wrap.
     * @param {object} descriptor The property descriptor.
     * @return {Function} The descriptor set function wrapped.
     */
    static wrapDescriptorSet(prop, descriptor, callback) {
        if (descriptor && descriptor.set && descriptor.set.callbacks) {
            descriptor.set.callbacks.push(callback);
            return descriptor.set;
        }
        let setter = function(value) {
            if (descriptor.set) {
                try {
                    descriptor.set.call(this, value);
                } catch (ex) {
                    this[`__${prop}`] = value;
                }
            } else {
                this[`__${prop}`] = value;
            }
            let res = this[prop];
            setter.callbacks.forEach((clb) => {
                if (typeof clb === 'function') {
                    clb.call(this, prop, res);
                }
            });
            return res;
        };
        setter.callbacks = [callback];
        return setter;
    }
    /**
     * Convert a string from camelCase to dash-case.
     * @param {string} str The string to convert.
     * @return {string} The converted string.
     */
    static camelToDash(str) {
        return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
    }
    /**
     * Convert a string from dash-case to camelCase.
     * @param {string} str The string to convert.
     * @return {string} The converted string.
     */
    static dashToCamel(str) {
        return str.replace(/\W+(.)/g, (x, chr) => chr.toUpperCase());
    }
}
<commit_msg>[minor] docs: fix missing doc for param in DNAHelper<commit_after>import { DNAConfig } from './dna-config.next.js';

/**
 * Helper class
 * @class DNAHelper
 */
export class DNAHelper {
    /**
     * Trigger `onRegister` callbacks and register the Custom Element
     * (if the `skipWebComponent` option !== true).
     * @param {Function|String} fn The definition or the tag name of the Custom Element.
     * @param {Object} options A set of options for the registration of the Custom Element.
     * @return {Function} The Custom Element constructor.
     */
    static register(fn, options = {}) {
        let scope = fn;
        let config = options;
        let tagName;
        let res;

        if (typeof fn === 'string') {
            tagName = fn;
            if (typeof config === 'function') {
                scope = config;
                config = {};
            } else if (typeof config.prototype !== 'undefined') {
                scope = config.prototype;
            }
        }
        if (typeof scope === 'function') {
            tagName = (
                tagName ||
                config.tagName ||
                (scope.hasOwnProperty('tagName') && scope.tagName) ||
                DNAHelper.classToElement(scope)
            );
            config.prototype = scope.prototype;
            if (!config.extends && typeof scope.extends === 'string') {
                config.extends = scope.extends;
            }
        } else {
            config.prototype = scope;
            scope = function(...args) {
                config.prototype.constructor.apply(this, args);
            };
            scope.prototype = config.prototype;
        }
        Object.defineProperty(scope, 'tagName', {
            configurable: true,
            get: () => tagName,
        });
        Object.defineProperty(scope.prototype, 'is', {
            configurable: false,
            get: () => tagName,
        });
        if (typeof scope.onRegister === 'function') {
            scope.onRegister.call(scope);
        }
        if (DNAConfig.useWebComponents) {
            res = document.registerElement(tagName, config);
        } else {
            res = function() {
                let el = document.createElement(tagName);
                Object.setPrototypeOf(el, scope.prototype);
                setTimeout(() => el.createdCallback(), 0);
                return el;
            };
        }
        if (typeof scope === 'function') {
            res.prototype.constructor = scope;
        }
        return res;
    }
    /**
     * Convert a Class name into HTML tag.
     * @param {Class} fn Grab the tag name from this class.
     * @return {String} The tag name for the Custom Element.
     */
    static classToElement(fn) {
        let name = fn.name || fn.toString().match(/^function\s*([^\s(]+)/)[1];
        if (!name) {
            return undefined;
        }
        return fn.name
            .replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
            .replace(/^\-/, '');
    }
    /**
     * Convert a HTML tag into a Class name.
     * @param {Class} fn Grab the class name from this tag.
     * @return {String} The class name for the Custom Element.
     */
    static elementToClass(tag) {
        return tag
            .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match) => {
                if (+match === 0) return '';
                return match.toUpperCase();
            })
            .replace(/[\-|\_]/g, '');
    }
    /**
     * Get an element's property descriptor.
     * @param {Function} ctr The element constructor.
     * @param {string} prop The property to analyze.
     * @return {object} The descriptor of the property.
     */
    static getDescriptor(ctr, prop) {
        let res;
        if (ctr) {
            res = Object.getOwnPropertyDescriptor(ctr, prop);
            let proto = Object.getPrototypeOf(ctr);
            if (!res && proto) {
                res = DNAHelper.getDescriptor(proto, prop);
            }
        }
        return res;
    }
    /**
     * Wrap a property descriptor get function.
     * @param {string} prop The property to wrap.
     * @param {object} descriptor The property descriptor.
     * @return {Function} The descriptor get function wrapped.
     */
    static wrapDescriptorGet(prop, descriptor) {
        return function() {
            let res;
            if (typeof descriptor.get === 'function') {
                try {
                    res = descriptor.get.call(this);
                } catch (ex) {
                    res = this[`__${prop}`];
                }
            } else {
                res = this[`__${prop}`];
            }
            return res;
        };
    }
    /**
     * Wrap a property descriptor set function.
     * @param {string} prop The property to wrap.
     * @param {object} descriptor The property descriptor.
     * @param {Function} callback An optional callback to trigger on set.
     * @return {Function} The descriptor set function wrapped.
     */
    static wrapDescriptorSet(prop, descriptor, callback) {
        if (descriptor && descriptor.set && descriptor.set.callbacks) {
            descriptor.set.callbacks.push(callback);
            return descriptor.set;
        }
        let setter = function(value) {
            if (descriptor.set) {
                try {
                    descriptor.set.call(this, value);
                } catch (ex) {
                    this[`__${prop}`] = value;
                }
            } else {
                this[`__${prop}`] = value;
            }
            let res = this[prop];
            setter.callbacks.forEach((clb) => {
                if (typeof clb === 'function') {
                    clb.call(this, prop, res);
                }
            });
            return res;
        };
        setter.callbacks = [callback];
        return setter;
    }
    /**
     * Convert a string from camelCase to dash-case.
     * @param {string} str The string to convert.
     * @return {string} The converted string.
     */
    static camelToDash(str) {
        return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
    }
    /**
     * Convert a string from dash-case to camelCase.
     * @param {string} str The string to convert.
     * @return {string} The converted string.
     */
    static dashToCamel(str) {
        return str.replace(/\W+(.)/g, (x, chr) => chr.toUpperCase());
    }
}
<|endoftext|>


<commit_before>var hzLinks = document.getElementsByTagName('a');
for (var i=0, l=hzLinks.length; i < l; i++) {
	hzLinks[i].addEventListener('mouseenter', hzMouseOn, false);
	hzLinks[i].addEventListener('mouseleave', hzMouseOff, false);
}

var hzDiv = document.createElement('div');
hzDiv.id = "hzDiv"
hzDiv.setSize = function(width, height) {
	var x = width;
	var y = height;
	var maxX = document.documentElement.clientWidth * window.devicePixelRatio * (self.options.maxSize / 100);
	var maxY = document.documentElement.clientHeight * window.devicePixelRatio * (self.options.maxSize / 100);
	if((x > maxX) || (y > maxY)) {
		var rX = maxX / x;
		var rY = maxY / y;
		if(rX < rY) {
			x = Math.floor(x * rX);
			y = Math.floor(y * rX);
		} else {
			x = Math.floor(x * rY);
			y = Math.floor(y * rY);
		}
	}
	this.style.width = x+"px";
	this.style.height = y+"px";
	this.style.marginLeft = Math.floor(x / -2)+"px";
	this.style.marginTop = Math.floor(y / -2)+"px";
}
hzDiv.show = function(el) {
	switch(el) {
		case 'image':
			hzVideo.style.display = 'none';
			hzImg.style.display = 'inline';
			break;
		case 'video':
			hzImg.style.display = 'none';
			hzVideo.style.display = 'inline';
			break;
	}
	hzText.set();
	window.addEventListener('scroll', hzMouseOff, false);
	window.addEventListener('wheel', hzWheel, false);
	hzDiv.style.display = 'inline';
	self.port.emit('visit', hzCurUrl);
}
hzDiv.hide = function() {
	hzDiv.style.display = 'none';
	window.removeEventListener('scroll', hzMouseOff, false);
	window.removeEventListener('wheel', hzWheel, false);
}
document.body.appendChild(hzDiv);

var hzImg = document.createElement('img');
hzImg.show = function(url, src) {
	if(url == hzCurUrl) {
		var i = self.options.delay - (new Date().getTime() - hzMark);
		if(i > 0) {
			hzWait = setTimeout( function(){ hzImg.show(url, src) }, i);
		} else {
			this.src = src;
			hzDiv.setSize(this.naturalWidth, this.naturalHeight);
			hzDiv.show('image');
		}
	}
}
hzDiv.appendChild(hzImg);

var hzText = document.createElement('div');
hzText.set = function() {
	while(this.firstChild) { this.removeChild(this.firstChild) }
	if(hzAlbumImgs.length > 1) {
		this.appendChild(document.createTextNode((hzAlbumImgIndex+1)+"/"+hzAlbumImgs.length));
	} else {
		this.appendChild(document.createTextNode(""));
	}
}
hzDiv.appendChild(hzText);

var hzVideo = document.createElement('video');
hzVideo.show = function(url, vid) {
	if(url == hzCurUrl) {
		var i = self.options.delay - (new Date().getTime() - hzMark);
		if(i > 0) {
			hzWait = setTimeout( function(){ hzVideo.show(url, vid) }, i);
		} else {
			this.src = vid.src;
			hzDiv.setSize(vid.width*1.6, vid.height*1.6);
			hzDiv.show('video');
		}
	}
}
hzVideo.autoplay = true;
hzVideo.loop = true;
hzVideo.muted = true;
hzDiv.appendChild(hzVideo);

var hzWait;
function hzLoadImg(url, src) {
	var img = new Image();
	img.onload = function() { hzImg.show(url, src) }
	img.src = src;
}

var hzAlbumImgs = [];
var hzAlbumImgIndex;
function hzLoadAlbum(d) {
	var delay = 0;
	if(d['data']['images']) {
		$.each(d['data']['images'], function(i,v){
			var h = hzTarget.protocol+"//i.imgur.com/"+v['id']+".jpg";
			setTimeout( function(){ new Image().src = h }, delay);
			delay += 500;
			hzAlbumImgs.push(h);
		});
	} else if(d['data']['id']) {
		hzAlbumImgs.push(hzTarget.protocol+"//i.imgur.com/"+d['data']['id']+".jpg");
	}
	if(hzAlbumImgs) {
		hzLoadImg(hzCurUrl, hzAlbumImgs[0]);
		hzAlbumImgIndex = 0;
	}
}
function hzWheel(e) {
	if(hzAlbumImgs.length > 1) {
		e.preventDefault();
		e.stopPropagation();
		if(e.deltaY > 0) {
			++hzAlbumImgIndex;
			if(hzAlbumImgIndex > hzAlbumImgs.length - 1) { hzAlbumImgIndex = 0 }
		} else if(e.deltaY < 0) {
			--hzAlbumImgIndex;
			if(hzAlbumImgIndex < 0) { hzAlbumImgIndex = hzAlbumImgs.length - 1 }
		}
		hzImg.show(hzCurUrl, hzAlbumImgs[hzAlbumImgIndex]);
	}
}

var hzTarget = document.createElement('a');
var hzCurUrl, hzMark;
function hzMouseOn(e) {
	clearTimeout(hzWait);
	self.port.emit('load', '', 'about:blank');
	hzCurUrl = '';
	hzDiv.hide();
	hzAlbumImgs = [];
	if(e) {
		hzMark = new Date().getTime();
		hzCurUrl = e.target.toString();
		hzTarget.href = hzCurUrl;
		var p = hzTarget.hostname.split('.').reverse();
		switch(p[1]) {
			case 'deviantart':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
			case 'flickr':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
			case 'gfycat':
				self.port.emit('load', hzCurUrl, hzTarget.protocol+"//gfycat.com/"+hzTarget.pathname.split('.')[0]);
				return;
			case 'imgflip':
				p = hzTarget.pathname.split('/');
				if(p.length > 2) { hzTarget.href = hzTarget.protocol+"//i.imgflip.com/"+p[2].split('#')[0]+".jpg" }
				break;
			case 'imgur':
				var alb = "";
				hzTarget.pathname = hzTarget.pathname.split('?')[0].split('#')[0].split(',')[0];
				p = hzTarget.pathname.split('/');
				switch(p[1]) {
					case 'a':
						alb = "https://api.imgur.com/3/album/"+p[2];
					case 'gallery':
						alb = alb || "https://api.imgur.com/3/gallery/"+p[2];
						hzCurWait = $.ajax({ url:alb, type:'GET', datatype:'json', success:hzLoadAlbum, beforeSend:function(h){ h.setRequestHeader('Authorization', 'Client-ID f781dcd19302057') } });
						return;
					default:
						if(hzTarget.pathname.split('.').length == 1) { hzTarget.href += ".jpg" }
						switch(hzTarget.pathname.split('.').reverse()[0]) {
							case 'gif':
								hzTarget.href += "v";
							case 'gifv':
								self.port.emit('load', hzCurUrl, hzTarget.href);
								return;
							default:
								hzTarget.href = hzTarget.protocol+"//i.imgur.com/"+hzTarget.pathname.split('/').pop();
						}
				}
				break;
			case 'instagram':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
			case 'livememe':
				p = hzTarget.pathname.split('.');
				if(p.length == 1) { hzTarget.href = hzTarget.protocol+"//i.lvme.me"+p.pop()+".jpg" }
				break;
			case 'makeameme':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
		}
		hzLoadImg(hzCurUrl, hzTarget.href);
	}
}
function hzMouseOff(e) { hzMouseOn(null) }

self.port.on('image', function(url, imgs){
	hzAlbumImgs = imgs;
	hzAlbumImgIndex = 0;
	hzLoadImg(url, hzAlbumImgs[0]);
});

self.port.on('video', hzVideo.show);
<commit_msg>lunarbaboon mk2<commit_after>var hzLinks = document.getElementsByTagName('a');
for (var i=0, l=hzLinks.length; i < l; i++) {
	hzLinks[i].addEventListener('mouseenter', hzMouseOn, false);
	hzLinks[i].addEventListener('mouseleave', hzMouseOff, false);
}

var hzDiv = document.createElement('div');
hzDiv.id = "hzDiv"
hzDiv.setSize = function(width, height) {
	var x = width;
	var y = height;
	var maxX = document.documentElement.clientWidth * window.devicePixelRatio * (self.options.maxSize / 100);
	var maxY = document.documentElement.clientHeight * window.devicePixelRatio * (self.options.maxSize / 100);
	if((x > maxX) || (y > maxY)) {
		var rX = maxX / x;
		var rY = maxY / y;
		if(rX < rY) {
			x = Math.floor(x * rX);
			y = Math.floor(y * rX);
		} else {
			x = Math.floor(x * rY);
			y = Math.floor(y * rY);
		}
	}
	this.style.width = x+"px";
	this.style.height = y+"px";
	this.style.marginLeft = Math.floor(x / -2)+"px";
	this.style.marginTop = Math.floor(y / -2)+"px";
}
hzDiv.show = function(el) {
	switch(el) {
		case 'image':
			hzVideo.style.display = 'none';
			hzImg.style.display = 'inline';
			break;
		case 'video':
			hzImg.style.display = 'none';
			hzVideo.style.display = 'inline';
			break;
	}
	hzText.set();
	window.addEventListener('scroll', hzMouseOff, false);
	window.addEventListener('wheel', hzWheel, false);
	hzDiv.style.display = 'inline';
	self.port.emit('visit', hzCurUrl);
}
hzDiv.hide = function() {
	hzDiv.style.display = 'none';
	window.removeEventListener('scroll', hzMouseOff, false);
	window.removeEventListener('wheel', hzWheel, false);
}
document.body.appendChild(hzDiv);

var hzImg = document.createElement('img');
hzImg.show = function(url, src) {
	if(url == hzCurUrl) {
		var i = self.options.delay - (new Date().getTime() - hzMark);
		if(i > 0) {
			hzWait = setTimeout( function(){ hzImg.show(url, src) }, i);
		} else {
			this.src = src;
			hzDiv.setSize(this.naturalWidth, this.naturalHeight);
			hzDiv.show('image');
		}
	}
}
hzDiv.appendChild(hzImg);

var hzText = document.createElement('div');
hzText.set = function() {
	while(this.firstChild) { this.removeChild(this.firstChild) }
	if(hzAlbumImgs.length > 1) {
		this.appendChild(document.createTextNode((hzAlbumImgIndex+1)+"/"+hzAlbumImgs.length));
	} else {
		this.appendChild(document.createTextNode(""));
	}
}
hzDiv.appendChild(hzText);

var hzVideo = document.createElement('video');
hzVideo.show = function(url, vid) {
	if(url == hzCurUrl) {
		var i = self.options.delay - (new Date().getTime() - hzMark);
		if(i > 0) {
			hzWait = setTimeout( function(){ hzVideo.show(url, vid) }, i);
		} else {
			this.src = vid.src;
			hzDiv.setSize(vid.width*1.6, vid.height*1.6);
			hzDiv.show('video');
		}
	}
}
hzVideo.autoplay = true;
hzVideo.loop = true;
hzVideo.muted = true;
hzDiv.appendChild(hzVideo);

var hzWait;
function hzLoadImg(url, src) {
	var img = new Image();
	img.onload = function() { hzImg.show(url, src) }
	img.src = src;
}

var hzAlbumImgs = [];
var hzAlbumImgIndex;
function hzLoadAlbum(d) {
	var delay = 0;
	if(d['data']['images']) {
		$.each(d['data']['images'], function(i,v){
			var h = hzTarget.protocol+"//i.imgur.com/"+v['id']+".jpg";
			setTimeout( function(){ new Image().src = h }, delay);
			delay += 500;
			hzAlbumImgs.push(h);
		});
	} else if(d['data']['id']) {
		hzAlbumImgs.push(hzTarget.protocol+"//i.imgur.com/"+d['data']['id']+".jpg");
	}
	if(hzAlbumImgs) {
		hzLoadImg(hzCurUrl, hzAlbumImgs[0]);
		hzAlbumImgIndex = 0;
	}
}
function hzWheel(e) {
	if(hzAlbumImgs.length > 1) {
		e.preventDefault();
		e.stopPropagation();
		if(e.deltaY > 0) {
			++hzAlbumImgIndex;
			if(hzAlbumImgIndex > hzAlbumImgs.length - 1) { hzAlbumImgIndex = 0 }
		} else if(e.deltaY < 0) {
			--hzAlbumImgIndex;
			if(hzAlbumImgIndex < 0) { hzAlbumImgIndex = hzAlbumImgs.length - 1 }
		}
		hzImg.show(hzCurUrl, hzAlbumImgs[hzAlbumImgIndex]);
	}
}

var hzTarget = document.createElement('a');
var hzCurUrl, hzMark;
function hzMouseOn(e) {
	clearTimeout(hzWait);
	self.port.emit('load', '', 'about:blank');
	hzCurUrl = '';
	hzDiv.hide();
	hzAlbumImgs = [];
	if(e) {
		hzMark = new Date().getTime();
		hzCurUrl = e.target.toString();
		hzTarget.href = hzCurUrl;
		var p = hzTarget.hostname.split('.').reverse();
		switch(p[1]) {
			case 'deviantart':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
			case 'flickr':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
			case 'gfycat':
				self.port.emit('load', hzCurUrl, hzTarget.protocol+"//gfycat.com/"+hzTarget.pathname.split('.')[0]);
				return;
			case 'imgflip':
				p = hzTarget.pathname.split('/');
				if(p.length > 2) { hzTarget.href = hzTarget.protocol+"//i.imgflip.com/"+p[2].split('#')[0]+".jpg" }
				break;
			case 'imgur':
				var alb = "";
				hzTarget.pathname = hzTarget.pathname.split('?')[0].split('#')[0].split(',')[0];
				p = hzTarget.pathname.split('/');
				switch(p[1]) {
					case 'a':
						alb = "https://api.imgur.com/3/album/"+p[2];
					case 'gallery':
						alb = alb || "https://api.imgur.com/3/gallery/"+p[2];
						hzCurWait = $.ajax({ url:alb, type:'GET', datatype:'json', success:hzLoadAlbum, beforeSend:function(h){ h.setRequestHeader('Authorization', 'Client-ID f781dcd19302057') } });
						return;
					default:
						if(hzTarget.pathname.split('.').length == 1) { hzTarget.href += ".jpg" }
						switch(hzTarget.pathname.split('.').reverse()[0]) {
							case 'gif':
								hzTarget.href += "v";
							case 'gifv':
								self.port.emit('load', hzCurUrl, hzTarget.href);
								return;
							default:
								hzTarget.href = hzTarget.protocol+"//i.imgur.com/"+hzTarget.pathname.split('/').pop();
						}
				}
				break;
			case 'instagram':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
			case 'livememe':
				p = hzTarget.pathname.split('.');
				if(p.length == 1) { hzTarget.href = hzTarget.protocol+"//i.lvme.me"+p.pop()+".jpg" }
				break;
			case 'lunarbaboon':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
			case 'makeameme':
				self.port.emit('load', hzCurUrl, hzTarget.href);
				return;
		}
		hzLoadImg(hzCurUrl, hzTarget.href);
	}
}
function hzMouseOff(e) { hzMouseOn(null) }

self.port.on('image', function(url, imgs){
	hzAlbumImgs = imgs;
	hzAlbumImgIndex = 0;
	hzLoadImg(url, hzAlbumImgs[0]);
});

self.port.on('video', hzVideo.show);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

const Hamburger = props => (
  <button
    aria-label="toggle side nav"
    onClick={props.onClick}
    className={`${props.open ? 'open' : ''} ${
      props.hamburgerColor === 'black' ? 'burnt' : ''
    }`}
  >

  </button>
);

Hamburger.defaultProps = {
  open: false,
};

Hamburger.propTypes = {
  onClick: PropTypes.func.isRequired,
  open: PropTypes.bool,
  hamburgerColor: PropTypes.string.isRequired,
};

export default Hamburger;
<commit_msg>fix hamburger left positioning<commit_after>import React from 'react';
import PropTypes from 'prop-types';

const Hamburger = props => (
  <button
    aria-label="toggle side nav"
    onClick={props.onClick}
    className={`${props.open ? 'open' : ''} ${
      props.hamburgerColor === 'black' ? 'burnt' : ''
    }`}
  >

  </button>
);

Hamburger.defaultProps = {
  open: false,
};

Hamburger.propTypes = {
  onClick: PropTypes.func.isRequired,
  open: PropTypes.bool,
  hamburgerColor: PropTypes.string.isRequired,
};

export default Hamburger;
<|endoftext|>


<commit_before>
function generic_ajaxform_dialog(url, title, id, width, btn_submit_name, redirectToUrl) {
  $(id).load(url,
      function () {
        function onsuccess(response, status, xhr, form) {
          var parse = $(response);
          if ($('.errorlist', parse).length != 0) {
            $(id).html(response);
            $(id + ' form').ajaxForm({
              success: onsuccess,
            });
            console.log('error');
          } else {
            console.log('success');
            if (redirectToUrl) {
              window.location = redirectToUrl;
            }
            else {
              window.location.reload(true);
            }
          }
        }
        $('form', this).ajaxForm({
          success: onsuccess,
        });
        $('#social-security-id input').keyup(function() {
            if ($(this).val().length < 13) {
              $('p#nir-key span').removeAttr('id')
              $('p#nir-key span').text('-');
            } else {
              $('p#nir-key span').attr('id', 'highlight')
              var key = 97 - ($(this).val() % 97)
              if (isNaN(key)) {
                $('p#nir-key span').text('NIR invalide');
              } else {
                $('p#nir-key span').text(key);
              }
            }
        });
        $(this).dialog({title: title,
          width: width,
          buttons: [ { text: "Annuler",
            click: function() { $(this).dialog("close"); } },
          { text: btn_submit_name,
            click: function() { $(id + " form").submit(); } }]});
      });
}

(function ($) {
  $.fn.dialogButton = function (opts) {
    var id = $(this).attr('id');
    this.on('click', function () {
      var $dialog = $('
<form class="inline-form" method="post"></form>
');
      var default_button = opts.default_button == undefined ? 'Envoyer' : opts.default_button;
      var form_action = opts.url.split(' ')[0];
      var $form = $('form', $dialog);
      $dialog.appendTo('#dialogs');
      if (opts.next_url) {
          $form.attr('action', form_action + '?next_url=' + opts.next_url);
      } else {
          $form.attr('action', form_action);
      }
      var buttons = [{
          text: default_button,
          click: function () {
            $form.submit();
          }
        },
        {
          text: 'Annuler',
          click: function () {
              $(this).dialog('close');
          }
        }];
      $dialog.css('max-height', $(window).height() - 200);
      $form.load(opts.url, function () {
        $dialog.dialog({
          modal: opts.modal == undefined ? true : opts.modal,
          width: 900,
          maxHeight: $(window).height() - 100,
          buttons: buttons,
          close: function () {
            $(this).remove();
          }
        });
      });
    })
  };
  $(function () {
    window.calebasse_dialogs = function(base) {

      var base = base || $('body').get(0);
      $('.dialog-button', base).each(function (i, button) {
        var $button = $(button);
        $button.dialogButton({
          url: $button.data('url') || $button.closest('a').attr('href'),
          default_button: $button.data('default-button') || $button.text(),
          title: $button.attr('title') || $button.text(),
          next_url: $button.data('next-url') || false
        });
      });

      /* Form buttons with the '.enable-on-change' class are only enabled if an
       * input or a select of the form is modified. */
      $('form button.enable-on-change, form input[type="submit"]', base).prop('disabled', 'true');
      $('form input, form select', base).on('change', function () {
        var form = $(this).closest('form');
        $('button.enable-on-change, form input[type="submit"]', form).enable();
      })
      $('form input', base).on('keyup', function () {
        var form = $(this).closest('form');
        $('button.enable-on-change, form input[type="submit"]', form).enable();
      })
      $('form.form-with-confirmation', base).on('submit', function () {
          var mesg = $(this).data('confirmation-msg') || "Êtes-vous sûr ?";
          return window.confirm(mesg);
      });
      $('form .datepicker', base).each(function (i, span) {
        var $span = $(span);
        var $input = $('input', span);
        var months = $span.data('number-of-months');
        var before_selector = $span.data('before-selector');
        var after_selector = $span.data('after-selector');
        $input.datepicker({dateFormat: 'd/m/yy' });
        if (months) {
          $input.datepicker("option", "numberOfMonths", months);
        }
        if (before_selector) {
          var $before_target = $('input', $(before_selector));
          $input.datepicker("option", "maxDate", $before_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $before_target.datepicker( "option", "minDate", selectedDate );
          });
        }
        if (after_selector) {
          var $after_target = $('input', $(after_selector));
          $input.datepicker("option", "minDate", $after_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $after_target.datepicker( "option", "maxDate", selectedDate );
          });
        }
      });
      $('form .reset', base).on('click', function () {
        var $this = $(this);
        var $form = $($this.closest('form'));
        $('input', $form).val('');
      });
      $('body').on('focus', 'form .datepicker input', function (e) {
        var $input = $(e.target);
        var $span = $($input.closest('.datepicker'));
        var months = $span.data('number-of-months');
        var before_selector = $span.data('before-selector');
        var after_selector = $span.data('after-selector');
        if ($input.is('.hasDatepicker')) {
          return true;
        }
        $input.datepicker({dateFormat: 'd/m/yy' });
        if (months) {
          $input.datepicker("option", "numberOfMonths", months);
        }
        if (before_selector) {
          var $before_target = $('input', $(before_selector));
          $input.datepicker("option", "maxDate", $before_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $before_target.datepicker( "option", "minDate", selectedDate );
          });
        }
        if (after_selector) {
          var $after_target = $('input', $(after_selector));
          $input.datepicker("option", "minDate", $after_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $after_target.datepicker( "option", "maxDate", selectedDate );
          });
        }
      });
    };
    window.calebasse_dialogs();
  });
})(window.jQuery)
<commit_msg>js: add a on_load_callback parameter to the ajax dialog<commit_after>
function generic_ajaxform_dialog(url, title, id, width, btn_submit_name, redirectToUrl, on_load_callback) {
  $(id).load(url,
      function () {
        function onsuccess(response, status, xhr, form) {
          var parse = $(response);
          if ($('.errorlist', parse).length != 0) {
            $(id).html(response);
            $(id + ' form').ajaxForm({
              success: onsuccess,
            });
            console.log('error');
          } else {
            console.log('success');
            if (redirectToUrl) {
              window.location = redirectToUrl;
            }
            else {
              window.location.reload(true);
            }
          }
        }
        $('form', this).ajaxForm({
          success: onsuccess,
        });
        $('#social-security-id input').keyup(function() {
            if ($(this).val().length < 13) {
              $('p#nir-key span').removeAttr('id')
              $('p#nir-key span').text('-');
            } else {
              $('p#nir-key span').attr('id', 'highlight')
              var key = 97 - ($(this).val() % 97)
              if (isNaN(key)) {
                $('p#nir-key span').text('NIR invalide');
              } else {
                $('p#nir-key span').text(key);
              }
            }
        });
        $(this).dialog({title: title,
          width: width,
          buttons: [ { text: "Annuler",
            click: function() { $(this).dialog("close"); } },
          { text: btn_submit_name,
            click: function() { $(id + " form").submit(); } }]});
	if (on_load_callback) {
            on_load_callback($(this));
	}
      });
}

(function ($) {
  $.fn.dialogButton = function (opts) {
    var id = $(this).attr('id');
    this.on('click', function () {
      var $dialog = $('
<form class="inline-form" method="post"></form>
');
      var default_button = opts.default_button == undefined ? 'Envoyer' : opts.default_button;
      var form_action = opts.url.split(' ')[0];
      var $form = $('form', $dialog);
      $dialog.appendTo('#dialogs');
      if (opts.next_url) {
          $form.attr('action', form_action + '?next_url=' + opts.next_url);
      } else {
          $form.attr('action', form_action);
      }
      var buttons = [{
          text: default_button,
          click: function () {
            $form.submit();
          }
        },
        {
          text: 'Annuler',
          click: function () {
              $(this).dialog('close');
          }
        }];
      $dialog.css('max-height', $(window).height() - 200);
      $form.load(opts.url, function () {
        $dialog.dialog({
          modal: opts.modal == undefined ? true : opts.modal,
          width: 900,
          maxHeight: $(window).height() - 100,
          buttons: buttons,
          close: function () {
            $(this).remove();
          }
        });
      });
    })
  };
  $(function () {
    window.calebasse_dialogs = function(base) {

      var base = base || $('body').get(0);
      $('.dialog-button', base).each(function (i, button) {
        var $button = $(button);
        $button.dialogButton({
          url: $button.data('url') || $button.closest('a').attr('href'),
          default_button: $button.data('default-button') || $button.text(),
          title: $button.attr('title') || $button.text(),
          next_url: $button.data('next-url') || false
        });
      });

      /* Form buttons with the '.enable-on-change' class are only enabled if an
       * input or a select of the form is modified. */
      $('form button.enable-on-change, form input[type="submit"]', base).prop('disabled', 'true');
      $('form input, form select', base).on('change', function () {
        var form = $(this).closest('form');
        $('button.enable-on-change, form input[type="submit"]', form).enable();
      })
      $('form input', base).on('keyup', function () {
        var form = $(this).closest('form');
        $('button.enable-on-change, form input[type="submit"]', form).enable();
      })
      $('form.form-with-confirmation', base).on('submit', function () {
          var mesg = $(this).data('confirmation-msg') || "Êtes-vous sûr ?";
          return window.confirm(mesg);
      });
      $('form .datepicker', base).each(function (i, span) {
        var $span = $(span);
        var $input = $('input', span);
        var months = $span.data('number-of-months');
        var before_selector = $span.data('before-selector');
        var after_selector = $span.data('after-selector');
        $input.datepicker({dateFormat: 'd/m/yy' });
        if (months) {
          $input.datepicker("option", "numberOfMonths", months);
        }
        if (before_selector) {
          var $before_target = $('input', $(before_selector));
          $input.datepicker("option", "maxDate", $before_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $before_target.datepicker( "option", "minDate", selectedDate );
          });
        }
        if (after_selector) {
          var $after_target = $('input', $(after_selector));
          $input.datepicker("option", "minDate", $after_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $after_target.datepicker( "option", "maxDate", selectedDate );
          });
        }
      });
      $('form .reset', base).on('click', function () {
        var $this = $(this);
        var $form = $($this.closest('form'));
        $('input', $form).val('');
      });
      $('body').on('focus', 'form .datepicker input', function (e) {
        var $input = $(e.target);
        var $span = $($input.closest('.datepicker'));
        var months = $span.data('number-of-months');
        var before_selector = $span.data('before-selector');
        var after_selector = $span.data('after-selector');
        if ($input.is('.hasDatepicker')) {
          return true;
        }
        $input.datepicker({dateFormat: 'd/m/yy' });
        if (months) {
          $input.datepicker("option", "numberOfMonths", months);
        }
        if (before_selector) {
          var $before_target = $('input', $(before_selector));
          $input.datepicker("option", "maxDate", $before_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $before_target.datepicker( "option", "minDate", selectedDate );
          });
        }
        if (after_selector) {
          var $after_target = $('input', $(after_selector));
          $input.datepicker("option", "minDate", $after_target.val());
          $input.datepicker("option", "onClose", function (selectedDate) {
            $after_target.datepicker( "option", "maxDate", selectedDate );
          });
        }
      });
    };
    window.calebasse_dialogs();
  });
})(window.jQuery)
<|endoftext|>


<commit_before>Prism.languages.sql= {
	'comment': {
		pattern: /(^|[^\\])(\/\*[\w\W]*?\*\/|((--)|(\/\/)).*?(\r?\n|$))/g,
		lookbehind: true
	},
	'string' : /("|')(\\?.)*?\1/g,
	'keyword' : /\b(ACTION|ADD|AFTER|ALGORITHM|ALTER|ANALYZE|APPLY|AS|ASC|AUTHORIZATION|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADE|CASCADED|CASE|CHAIN|CHAR VARYING|CHARACTER VARYING|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATA|DATABASE|DATABASES|DATETIME|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DOUBLE PRECISION|DROP|DUMMY|DUMP|DUMPFILE|DUPLICATE KEY|ELSE|ENABLE|ENCLOSED BY|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPE|ESCAPED BY|EXCEPT|EXEC|EXECUTE|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR|FOR EACH ROW|FORCE|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GEOMETRY|GEOMETRYCOLLECTION|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|IDENTITY|IDENTITY_INSERT|IDENTITYCOL|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTO|INVOKER|ISOLATION LEVEL|JOIN|KEY|KEYS|KILL|LANGUAGE SQL|LAST|LEFT|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONGBLOB|LONGTEXT|MATCH|MATCHED|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MERGE|MIDDLEINT|MODIFIES SQL DATA|MODIFY|MULTILINESTRING|MULTIPOINT|MULTIPOLYGON|NATIONAL|NATIONAL CHAR VARYING|NATIONAL CHARACTER|NATIONAL CHARACTER VARYING|NATIONAL VARCHAR|NATURAL|NCHAR|NCHAR VARCHAR|NEXT|NO|NO SQL|NOCHECK|NOCYCLE|NONCLUSTERED|NULLIF|NUMERIC|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPTIMIZE|OPTION|OPTIONALLY|ORDER|OUT|OUTER|OUTFILE|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREV|PRIMARY|PRINT|PRIVILEGES|PROC|PROCEDURE|PUBLIC|PURGE|QUICK|RAISERROR|READ|READS SQL DATA|READTEXT|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEATABLE|REPLICATION|REQUIRE|RESTORE|RESTRICT|RETURN|RETURNS|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROWCOUNT|ROWGUIDCOL|ROWS?|RTREE|RULE|SAVE|SAVEPOINT|SCHEMA|SELECT|SERIAL|SERIALIZABLE|SESSION|SESSION_USER|SET|SETUSER|SHARE MODE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|START|STARTING BY|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLE|TABLES|TABLESPACE|TEMPORARY|TEMPTABLE|TERMINATED BY|TEXT|TEXTSIZE|THEN|TIMESTAMP|TINYBLOB|TINYINT|TINYTEXT|TO|TOP|TRAN|TRANSACTION|TRANSACTIONS|TRIGGER|TRUNCATE|TSEQUAL|TYPE|TYPES|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNPIVOT|UPDATE|UPDATETEXT|USAGE|USE|USER|USING|VALUE|VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH|WITH ROLLUP|WITHIN|WORK|WRITE|WRITETEXT)\b/gi,
	'boolean' : /\b(TRUE|FALSE|NULL)\b/gi,
	'number' : /\b-?(0x)?\d*\.?[\da-f]+\b/g,
	'operator' : /\b(ALL|AND|ANY|BETWEEN|EXISTS|IN|LIKE|NOT|OR|IS|UNIQUE|CHARACTER SET|COLLATE|DIV|OFFSET|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b|[-+]{1}|!|=?<|=?>|={1}|(&){1,2}|\|?\||\?|\*|\//gi,
	'ignore' : /&(lt|gt|amp);/gi,
	'punctuation' : /[;[\]()`,.]/g
};
<commit_msg>Added support for MySQL single line comment<commit_after>Prism.languages.sql= {
	'comment': {
		pattern: /(^|[^\\])(\/\*[\w\W]*?\*\/|((--)|(\/\/)|#).*?(\r?\n|$))/g,
		lookbehind: true
	},
	'string' : /("|')(\\?.)*?\1/g,
	'keyword' : /\b(ACTION|ADD|AFTER|ALGORITHM|ALTER|ANALYZE|APPLY|AS|ASC|AUTHORIZATION|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADE|CASCADED|CASE|CHAIN|CHAR VARYING|CHARACTER VARYING|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATA|DATABASE|DATABASES|DATETIME|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DOUBLE PRECISION|DROP|DUMMY|DUMP|DUMPFILE|DUPLICATE KEY|ELSE|ENABLE|ENCLOSED BY|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPE|ESCAPED BY|EXCEPT|EXEC|EXECUTE|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR|FOR EACH ROW|FORCE|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GEOMETRY|GEOMETRYCOLLECTION|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|IDENTITY|IDENTITY_INSERT|IDENTITYCOL|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTO|INVOKER|ISOLATION LEVEL|JOIN|KEY|KEYS|KILL|LANGUAGE SQL|LAST|LEFT|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONGBLOB|LONGTEXT|MATCH|MATCHED|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MERGE|MIDDLEINT|MODIFIES SQL DATA|MODIFY|MULTILINESTRING|MULTIPOINT|MULTIPOLYGON|NATIONAL|NATIONAL CHAR VARYING|NATIONAL CHARACTER|NATIONAL CHARACTER VARYING|NATIONAL VARCHAR|NATURAL|NCHAR|NCHAR VARCHAR|NEXT|NO|NO SQL|NOCHECK|NOCYCLE|NONCLUSTERED|NULLIF|NUMERIC|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPTIMIZE|OPTION|OPTIONALLY|ORDER|OUT|OUTER|OUTFILE|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREV|PRIMARY|PRINT|PRIVILEGES|PROC|PROCEDURE|PUBLIC|PURGE|QUICK|RAISERROR|READ|READS SQL DATA|READTEXT|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEATABLE|REPLICATION|REQUIRE|RESTORE|RESTRICT|RETURN|RETURNS|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROWCOUNT|ROWGUIDCOL|ROWS?|RTREE|RULE|SAVE|SAVEPOINT|SCHEMA|SELECT|SERIAL|SERIALIZABLE|SESSION|SESSION_USER|SET|SETUSER|SHARE MODE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|START|STARTING BY|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLE|TABLES|TABLESPACE|TEMPORARY|TEMPTABLE|TERMINATED BY|TEXT|TEXTSIZE|THEN|TIMESTAMP|TINYBLOB|TINYINT|TINYTEXT|TO|TOP|TRAN|TRANSACTION|TRANSACTIONS|TRIGGER|TRUNCATE|TSEQUAL|TYPE|TYPES|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNPIVOT|UPDATE|UPDATETEXT|USAGE|USE|USER|USING|VALUE|VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH|WITH ROLLUP|WITHIN|WORK|WRITE|WRITETEXT)\b/gi,
	'boolean' : /\b(TRUE|FALSE|NULL)\b/gi,
	'number' : /\b-?(0x)?\d*\.?[\da-f]+\b/g,
	'operator' : /\b(ALL|AND|ANY|BETWEEN|EXISTS|IN|LIKE|NOT|OR|IS|UNIQUE|CHARACTER SET|COLLATE|DIV|OFFSET|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b|[-+]{1}|!|=?<|=?>|={1}|(&){1,2}|\|?\||\?|\*|\//gi,
	'ignore' : /&(lt|gt|amp);/gi,
	'punctuation' : /[;[\]()`,.]/g
};
<|endoftext|>


<commit_before><commit_msg>tweak mongoose schema reader<commit_after><|endoftext|>