<commit_before>
var colors = require('colors');

var cli = exports
  , sys = require('sys')
  , _ = require('underscore')
  , path = require('path')
  , haiku = require('../haiku')
;

cli.help = {
  general: [
    'usage:',
    '  haiku [action] [options]',
    '',
    'actions: ',
    '',
    '  build [options]    # Build the haiku site',
    '  server [options]   # Run the haiku server',
    '  deploy [options]   # Deploy the site!',
    ''
  ].join('\n'),
  build: [
    'usage:',
    '   haiku build [options]',
    '',
    'options: ',
    '   --root PATH         # Path to your haiku\'s root, defaults to the cwd',
    '   --destination PATH  # haiku will build to this directory, defaults to "build"',
    '',
    'description:',
    '   ...',
    '',
    'example:',
    '   ...'
  ].join('\n'),
  server: [
    'usage:',
    '   haiku server [options]',
    '',
    'options: ',
    '   --port PORT     # Defaults to 8080',
    '',
    'description:',
    '   ...',
    '',
    'example:',
    '   ...'
  ].join('\n'),
  deploy: [
    'useage:',
    '   haiku deploy [options]',
    '',
    'options: ',
    '   --source,--root PATH  # Path to your haiku\'s root, defaults to the cwd',
    '   --config,-c PATH      # Path to your haiku config file',
    '   --key KEY             # Your s3 api key',
    '   --secret SECRET       # Your s3 secret',
    '   --bucket BUCKET       # The bucket to beam your site to',
    '',
    'description:',
    '   ...',
    '',
    'example:',
    '   ...'
  ].join('\n')
}

cli.showHelp = function(action){
  var action = action || 'general';
  return sys.puts(cli.help[action]);
};

cli.showErr = function(message){
  return sys.puts(message)
}

// we use defineCommand() to give us back a function
// that will do some standard arg processing and
// the run the command, which is passed in as another
// function ... basically so that the comamnd functions
// can just worry about getting the resulting options
// and doing something interesting

cli.defineCommand = function(name, command) {
  cli[name] = function(args) {
    if (args.help) return cli.showHelp(name);

    var options = _.clone(args)
      , config
    ;

    if (args.config){
      config = require(path.resolve(process.cwd(), args.config));

      _.extend(options, config);
    }

    command(options);
  };
}

// the build command ... read the site in and then
// copy it to a destination directory

cli.defineCommand('build', function(options){
  var site = new haiku.Site(options);

  site.on('ready', function(){ site.build(); }).read();
});

// the server command - the server class does all the real work here
// we just create it and call run

cli.defineCommand('server', function(options){
  new haiku.Server.run(options);
});

// // new will create a site template for you when we implement it
// cli.new = cli.defineCommand(function(options) {
//   cli.showErr("This command has not been implemented yet.");
// });

// deploy will push your site to S3 for you
cli.defineCommand('deploy', function(options) {
  var site = new haiku.Site(options);

  site.on('ready', function(){ site.deploy(); }).read();
});

<commit_msg>automatically pick up the config file - fixes#27<commit_after>
var colors = require('colors');

var cli = exports
  , sys = require('sys')
  , _ = require('underscore')
  , path = require('path')
  , haiku = require('../haiku')
;

cli.help = {
  general: [
    'usage:',
    '  haiku [action] [options]',
    '',
    'actions: ',
    '',
    '  build [options]    # Build the haiku site',
    '  server [options]   # Run the haiku server',
    '  deploy [options]   # Deploy the site!',
    ''
  ].join('\n'),
  build: [
    'usage:',
    '   haiku build [options]',
    '',
    'options: ',
    '   --root PATH         # Path to your haiku\'s root, defaults to the cwd',
    '   --destination PATH  # haiku will build to this directory, defaults to "build"',
    '',
    'description:',
    '   ...',
    '',
    'example:',
    '   ...'
  ].join('\n'),
  server: [
    'usage:',
    '   haiku server [options]',
    '',
    'options: ',
    '   --port PORT     # Defaults to 8080',
    '',
    'description:',
    '   ...',
    '',
    'example:',
    '   ...'
  ].join('\n'),
  deploy: [
    'useage:',
    '   haiku deploy [options]',
    '',
    'options: ',
    '   --source,--root PATH  # Path to your haiku\'s root, defaults to the cwd',
    '   --config,-c PATH      # Path to your haiku config file',
    '   --key KEY             # Your s3 api key',
    '   --secret SECRET       # Your s3 secret',
    '   --bucket BUCKET       # The bucket to beam your site to',
    '',
    'description:',
    '   ...',
    '',
    'example:',
    '   ...'
  ].join('\n')
}

cli.showHelp = function(action){
  var action = action || 'general';
  return sys.puts(cli.help[action]);
};

cli.showErr = function(message){
  return sys.puts(message)
}

// we use defineCommand() to give us back a function
// that will do some standard arg processing and
// the run the command, which is passed in as another
// function ... basically so that the comamnd functions
// can just worry about getting the resulting options
// and doing something interesting

cli.defineCommand = function(name, command) {
  cli[name] = function(args) {
    if (args.help) return cli.showHelp(name);

    var options = _.clone(args)
      , source = options.source || process.cwd()
      , config
    ;

    source = path.resolve(process.cwd(), source);

    // Try and read the config if you find one
    try {
      config = require(path.join(source, 'config', 'haiku.js'));
    } catch(e) {
      config = {};
    }

    if (args.config) config = require(path.resolve(source, args.config));

    _.extend(options, config);

    command(options);
  };
}

// the build command ... read the site in and then
// copy it to a destination directory

cli.defineCommand('build', function(options){
  var site = new haiku.Site(options);

  site.on('ready', function(){ site.build(); }).read();
});

// the server command - the server class does all the real work here
// we just create it and call run

cli.defineCommand('server', function(options){
  new haiku.Server.run(options);
});

// // new will create a site template for you when we implement it
// cli.new = cli.defineCommand(function(options) {
//   cli.showErr("This command has not been implemented yet.");
// });

// deploy will push your site to S3 for you
cli.defineCommand('deploy', function(options) {
  var site = new haiku.Site(options);

  site.on('ready', function(){ site.deploy(); }).read();
});

<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as actions from '../../actions/filemgmt'
import { Button, Icon, Segment } from 'semantic-ui-react'
import SelectedFileShowComponent from './SelectedFileShowComponent'

class FileUploadComponent extends Component {

  constructor(props) {
    super(props);
    this.state = {
      autoUpload : false,
      usbConnected : false,
      isUploadingToUsb : false,
      isDownloadingFromUsb : false,
    }
  }

  handleFileInputChange(fileList)  {
    let files = this.props.filemgmt.uploadableFiles;
    for (let i = 0; i < fileList.length; i++) {
      files.push(fileList[i]);
    }
    this.props.updateUploadableFiles(files);
    //this.setState({files, fileNames});
  }

  enableAutomaticUpload() {
    this.setState({autoUpload : true})
  }

  componentDidMount() {
    this.props.readFolder(this.props.filemgmt.currentDir);
    this.props.verifyConnectedUSB('', (ans) => {
      this.setState({usbConnected : ans});
    });
  }

  componentDidUpdate(newProps, newState) {
    if (newState.autoUpload === true) {
      this.setState({autoUpload : false})
    }
  }

  transferToUSB() {
    this.props.verifyConnectedUSB('', (ans) => {
      if (!ans) {
        alert('USB unplugged!');
        this.setState({usbConnected : false});
      } else {
        this.setState({isUploadingToUsb : true})
        this.props.copyFile(this.props.filemgmt.currentDir, this.props.filemgmt.usbDir, (err, msg) => {
          if (err) {
            alert(msg);
          } else {
            alert('Copy to USB successful!');
          }
          this.props.verifyConnectedUSB((ans) => {
            return;
          })
          this.setState({isUploadingToUsb : false})
        })
      }
    });
  }

  transferFromUSB() {
    this.props.verifyConnectedUSB('', (ans) => {
      if (!ans) {
        alert('USB unplugged!');
        this.setState({usbConnected : false});
      } else {
        this.setState({isDownloadingFromUsb : true})
        this.props.copyBunchOfFiles(this.props.filemgmt.usbDir, this.props.filemgmt.usbDownFiles, this.props.filemgmt.currentDir, (msg) => {
          alert(msg);
          this.props.verifyConnectedUSB((ans) => {
            return;
          })
          this.props.readFolder(this.props.filemgmt.currentDir);
          this.setState({isDownloadingFromUsb : false})
        })
      }
    })
  }

  renderFileUploadComponent() {
    let fileNameDOMs = this.props.filemgmt.uploadableFiles.map((thing, index) =>

          <SelectedFileShowComponent file = {thing} uploadStatus={this.state.autoUpload ? 'UPLOADING' : 'INACTIVE'} key={index}/>

)
    return (

        <Segment>

        <Button animated color='teal' onClick={this.transferToUSB.bind(this)} disabled = {!this.state.usbConnected || this.state.isUploadingToUsb} loading={this.state.isUploadingToUsb}>
          <Button.Content visible>Transfer Folder to USB</Button.Content>
          <Button.Content hidden><Icon name='usb' /><Icon name='up arrow'/></Button.Content>
        </Button>
        <Button animated color='teal' onClick={this.transferFromUSB.bind(this)} disabled = {!this.state.usbConnected || this.state.isDownloadingFromUsb} loading={this.state.isDownloadingFromUsb}>
          <Button.Content visible>Transfer Here From USB</Button.Content>
          <Button.Content hidden><Icon name='usb' /><Icon name='down arrow'/></Button.Content>
        </Button>

        </Segment>
        <Segment>

          <Button animated color='blue' onClick = {() => {
            document.getElementById("fileinput").click();
          }}>
            <Button.Content visible>
              Choose file(s) to upload
            </Button.Content>
            <Button.Content hidden>
              <Icon name='upload' />
            </Button.Content>
          </Button>
          <input type='file' id='fileinput' style = {{display : 'None'}}
          onChange={(e) => this.handleFileInputChange(e.target.files)} multiple/>

        { this.props.filemgmt.uploadableFiles.length > 0 ? <Button animated color='green' onClick = {this.enableAutomaticUpload.bind(this)}>
          <Button.Content visible>
            Upload all files
          </Button.Content>
          <Button.Content hidden>
            <Icon name='checkmark' />
          </Button.Content>
        </Button> : null}

        {this.props.filemgmt.uploadableFiles.length > 0 ?
Selected files for upload
 : null}
        {fileNameDOMs}
        </Segment>

    )
  }

  render() {
    return (

        {this.renderFileUploadComponent()}

    )
  }
}

function mapStateToProps({ filemgmt }) {
  return { filemgmt }
}

export default connect(mapStateToProps, actions)(FileUploadComponent);
<commit_msg>Add button for uploading folders<commit_after>import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as actions from '../../actions/filemgmt'
import { Button, Icon, Segment } from 'semantic-ui-react'
import SelectedFileShowComponent from './SelectedFileShowComponent'

class FileUploadComponent extends Component {

  constructor(props) {
    super(props);
    this.state = {
      autoUpload : false,
      usbConnected : false,
      isUploadingToUsb : false,
      isDownloadingFromUsb : false,
    }
  }

  handleFileInputChange(fileList)  {
    let files = this.props.filemgmt.uploadableFiles;
    for (let i = 0; i < fileList.length; i++) {
      files.push(fileList[i]);
    }
    this.props.updateUploadableFiles(files);
    //this.setState({files, fileNames});
  }

  enableAutomaticUpload() {
    this.setState({autoUpload : true})
  }

  componentDidMount() {
    this.props.readFolder(this.props.filemgmt.currentDir);
    this.props.verifyConnectedUSB('', (ans) => {
      this.setState({usbConnected : ans});
    });
  }

  componentDidUpdate(newProps, newState) {
    if (newState.autoUpload === true) {
      this.setState({autoUpload : false})
    }
  }

  transferToUSB() {
    this.props.verifyConnectedUSB('', (ans) => {
      if (!ans) {
        alert('USB unplugged!');
        this.setState({usbConnected : false});
      } else {
        this.setState({isUploadingToUsb : true})
        this.props.copyFile(this.props.filemgmt.currentDir, this.props.filemgmt.usbDir, (err, msg) => {
          if (err) {
            alert(msg);
          } else {
            alert('Copy to USB successful!');
          }
          this.props.verifyConnectedUSB((ans) => {
            return;
          })
          this.setState({isUploadingToUsb : false})
        })
      }
    });
  }

  transferFromUSB() {
    this.props.verifyConnectedUSB('', (ans) => {
      if (!ans) {
        alert('USB unplugged!');
        this.setState({usbConnected : false});
      } else {
        this.setState({isDownloadingFromUsb : true})
        this.props.copyBunchOfFiles(this.props.filemgmt.usbDir, this.props.filemgmt.usbDownFiles, this.props.filemgmt.currentDir, (msg) => {
          alert(msg);
          this.props.verifyConnectedUSB((ans) => {
            return;
          })
          this.props.readFolder(this.props.filemgmt.currentDir);
          this.setState({isDownloadingFromUsb : false})
        })
      }
    })
  }

  renderFileUploadComponent() {
    let fileNameDOMs = this.props.filemgmt.uploadableFiles.map((thing, index) =>

          <SelectedFileShowComponent file = {thing} uploadStatus={this.state.autoUpload ? 'UPLOADING' : 'INACTIVE'} key={index}/>

)
    return (

        <Segment>

        <Button animated color='teal' onClick={this.transferToUSB.bind(this)} disabled = {!this.state.usbConnected || this.state.isUploadingToUsb} loading={this.state.isUploadingToUsb}>
          <Button.Content visible>Transfer Folder to USB</Button.Content>
          <Button.Content hidden><Icon name='usb' /><Icon name='up arrow'/></Button.Content>
        </Button>
        <Button animated color='teal' onClick={this.transferFromUSB.bind(this)} disabled = {!this.state.usbConnected || this.state.isDownloadingFromUsb} loading={this.state.isDownloadingFromUsb}>
          <Button.Content visible>Transfer Here From USB</Button.Content>
          <Button.Content hidden><Icon name='usb' /><Icon name='down arrow'/></Button.Content>
        </Button>

        </Segment>
        <Segment>

          <Button animated color='blue' onClick = {() => {
            document.getElementById("fileinput").click();
          }}>
            <Button.Content visible>
              Choose file(s) to upload
            </Button.Content>
            <Button.Content hidden>
              <Icon name='upload' />
            </Button.Content>
          </Button>
          <input type='file' id='fileinput' style = {{display : 'None'}}
          onChange={(e) => this.handleFileInputChange(e.target.files)} multiple/>

          <Button animated color='blue' onClick = {() => {
            document.getElementById("folderinput").click();
          }}>
            <Button.Content visible>
              Choose folder(s) to upload
            </Button.Content>
            <Button.Content hidden>
              <Icon name='upload' />
            </Button.Content>
          </Button>
          <input type='file' id='folderinput' style = {{display : 'None'}}
          onChange={(e) => this.handleFileInputChange(e.target.files)} multiple='' webkitdirectory='' mozdirectory='' directory=''/>

        { this.props.filemgmt.uploadableFiles.length > 0 ? <Button animated color='green' onClick = {this.enableAutomaticUpload.bind(this)}>
          <Button.Content visible>
            Upload all files
          </Button.Content>
          <Button.Content hidden>
            <Icon name='checkmark' />
          </Button.Content>
        </Button> : null}

        {this.props.filemgmt.uploadableFiles.length > 0 ?
Selected files for upload
 : null}
        {fileNameDOMs}
        </Segment>

    )
  }

  render() {
    return (

        {this.renderFileUploadComponent()}

    )
  }
}

function mapStateToProps({ filemgmt }) {
  return { filemgmt }
}

export default connect(mapStateToProps, actions)(FileUploadComponent);
<|endoftext|>


<commit_before>var needle = require('needle')
var util = require('util')
var fs = require('fs')
// Constructor
function HODClient(apikey,version) {
  // always initialize all instance properties
  if (apikey=='http://api.havenondemand.com' || apikey=='http://api.havenondemand.com/' || apikey=='https://api.havenondemand.com' || apikey=='https://api.havenondemand.com/') {
    throw new Error("Using an outdated wrapper constructor method. No need to include API URL.\nInclude as such:\n client = new havenondemand.HODClient(API_KEY, VERSION)")
  }
  if (version==undefined) {this.version="v1";}
  else {this.version=version;}
  this.apikey = apikey;
  this.endpoint = "https://api.havenondemand.com/1/api/%s/%s/"+this.version;
}
// class methods

needle.defaults({ timeout: 120000});

parseArgs=function(arg1,arg2,arg3){
  var data,callback,async;
  if (typeof arg1 == "function") {callback = arg1;}
  else if (typeof arg1 == "object") {data=arg1;}
  else {async=arg1;}

  if (typeof arg2 == "function") {callback = arg2;}
  else if (typeof arg2 =="object") {data=arg2;}
  else {async=arg2;}

  if (typeof arg3 == "function") {callback = arg3;}
  else if (typeof arg3 =="object") {data=arg3;}
  else {async=arg3;}

  return {'data':data,'callback':callback,'async':async}
}

HODClient.prototype.call = function(handler,arg1,arg2,arg3) {
  var args=  parseArgs(arg1,arg2,arg3)
  var data= args.data;
  var callback= args.callback;
  var async=args.async;
  if (typeof async =="undefined") async = false;
  if (typeof data =="undefined") data={};

  if (data["file"]){
    data["file"]={'file':data["file"],'content_type':'multipart/form-data'}
  }
//  console.log('incall',data)
  async_string="sync";
  data.apikey=this.apikey;

  if (async) {
    async_string="async";
  }

  var url = util.format(this.endpoint,async_string,handler);
  //console.log(url)

  if (typeof callback == "undefined") {

  return needle.post(url, data, { multipart: true });
  }
  else{

    if (async){
      var callback = callback;
      var callbackmanager=function(err,resp,body){
        body={'async':true,'data':body};
        body.status

        callback(err,resp,body);
      }
    }
    else{
      var callbackmanager= callback;
    }
    needle.post(url, data, { multipart: true }, callbackmanager);

  }

};

HODClient.prototype.createIndex=function(name,arg1,arg2,arg3){
  var args=  parseArgs(arg1,arg2,arg3)
  var data= args.data;
  var callback= args.callback;
  var async=args.async;

  if (typeof data =="undefined") data = {};
  data.index=name
  console.log("setting",data)
  return this.call('createtextindex',callback,data)
}

HODClient.prototype.getIndex=function(name){
    return new HODIndex(this,name)
}

HODClient.prototype.deleteIndex=function(name,arg1,arg2,arg3){
  var args=  parseArgs(arg1,arg2,arg3)
  var data= args.data;
  var callback= args.callback;
  var async=args.async;
  data.index=name
  var callback=callback;
  var self=this;

  var confirmcallback=function(err,resp,body){
    console.log('confirmcall')
    console.log(JSON.stringify(body))
    data.confirm=body.confirm
    self.call('deletetextindex',callback,data)
  }

  this.call('deletetextindex',confirmcallback,data)
}

function HODIndex(client,name){
  this.client=client;
  this.name=name;
}

HODIndex.prototype.retrieveindexfields=function(callback){
  data={'indexes':this.name}
  return this.client.call('retrieveindexfields',callback,data)
}

HODIndex.prototype.status=function(callback){
  data={'indexes':this.name}
  return this.client.call('indexstatus',callback,data)
}

HODIndex.prototype.delete=function(callback){
  data={'indexes':this.name}
  return this.client.call('deletetextindex',callback,data)
}

HODIndex.prototype.deletedocs=function(callback,data){
  data.indexes=this.name
  return this.client.call('deletetextindex',callback,data)
}

HODIndex.prototype.create=function(callback,data){
  if (typeof data == "undefined") data={'flavor':'standard'}
  data.index=this.name
  return this.client.call('createtextindex',callback,data)
}

HODIndex.prototype.adddocs=function(callback,docs,data){
  data.indexes=this.name
  docs={'document':docs}
  data.json=JSON.stringify(docs)
  return this.client.call('addtotextindex',callback,data)
}

function HODConnector(client,name){
  this.client=client;
  this.name=name;
}

HODConnector.prototype.start=function(callback){
  data={'connector':this.name}
  return this.client.call('startconnector',callback,data)
}

HODConnector.prototype.status=function(callback){
  data={'connector':this.name}
  return this.client.call('connectorstatus',callback,data)
}

HODConnector.prototype.retrieveconfig=function(callback){
  data={'connector':this.name}
  return this.client.call('retrieveconfig',callback,data)
}

HODConnector.prototype.updateconnector=function(callback,data){
  data.connector=this.name
  return this.client.call('updateconnector',callback,data)
}

HODConnector.prototype.delete=function(callback){
  data={'connector':this.name}
  return this.client.call('deleteconnector',callback,data)
}

// export the class
exports.HODClient=HODClient
exports.HODConnector=HODConnector
exports.HODIndex=HODIndex
<commit_msg>updated for staging<commit_after>var needle = require('needle')
var util = require('util')
var fs = require('fs')
// Constructor
function HODClient(apikey,version,staging) {
  // always initialize all instance properties
  if (apikey=='http://api.havenondemand.com' || apikey=='http://api.havenondemand.com/' || apikey=='https://api.havenondemand.com' || apikey=='https://api.havenondemand.com/') {
    throw new Error("Using an outdated wrapper constructor method. No need to include API URL.\nInclude as such:\n client = new havenondemand.HODClient(API_KEY, VERSION)")
  }
  if (version==undefined) {this.version="v1";}
  else {this.version=version;}
  this.apikey = apikey;
  if (staging==undefined || staging==false) {
    this.endpoint = "https://api.havenondemand.com/1/api/%s/%s/"+this.version;
  } else if (staging==true) {
      this.endpoint = "https://api.staging.havenondemand.com/1/api/%s/%s/"+this.version;
  }
}
// class methods

needle.defaults({ timeout: 120000});

parseArgs=function(arg1,arg2,arg3){
  var data,callback,async;
  if (typeof arg1 == "function") {callback = arg1;}
  else if (typeof arg1 == "object") {data=arg1;}
  else {async=arg1;}

  if (typeof arg2 == "function") {callback = arg2;}
  else if (typeof arg2 =="object") {data=arg2;}
  else {async=arg2;}

  if (typeof arg3 == "function") {callback = arg3;}
  else if (typeof arg3 =="object") {data=arg3;}
  else {async=arg3;}

  return {'data':data,'callback':callback,'async':async}
}

HODClient.prototype.call = function(handler,arg1,arg2,arg3) {
  var args=  parseArgs(arg1,arg2,arg3)
  var data= args.data;
  var callback= args.callback;
  var async=args.async;
  if (typeof async =="undefined") async = false;
  if (typeof data =="undefined") data={};

  if (data["file"]){
    data["file"]={'file':data["file"],'content_type':'multipart/form-data'}
  }
//  console.log('incall',data)
  async_string="sync";
  data.apikey=this.apikey;

  if (async) {
    async_string="async";
  }

  var url = util.format(this.endpoint,async_string,handler);
  //console.log(url)

  if (typeof callback == "undefined") {

  return needle.post(url, data, { multipart: true });
  }
  else{

    if (async){
      var callback = callback;
      var callbackmanager=function(err,resp,body){
        body={'async':true,'data':body};
        body.status

        callback(err,resp,body);
      }
    }
    else{
      var callbackmanager= callback;
    }
    needle.post(url, data, { multipart: true }, callbackmanager);

  }

};

HODClient.prototype.createIndex=function(name,arg1,arg2,arg3){
  var args=  parseArgs(arg1,arg2,arg3)
  var data= args.data;
  var callback= args.callback;
  var async=args.async;

  if (typeof data =="undefined") data = {};
  data.index=name
  console.log("setting",data)
  return this.call('createtextindex',callback,data)
}

HODClient.prototype.getIndex=function(name){
    return new HODIndex(this,name)
}

HODClient.prototype.deleteIndex=function(name,arg1,arg2,arg3){
  var args=  parseArgs(arg1,arg2,arg3)
  var data= args.data;
  var callback= args.callback;
  var async=args.async;
  data.index=name
  var callback=callback;
  var self=this;

  var confirmcallback=function(err,resp,body){
    console.log('confirmcall')
    console.log(JSON.stringify(body))
    data.confirm=body.confirm
    self.call('deletetextindex',callback,data)
  }

  this.call('deletetextindex',confirmcallback,data)
}

function HODIndex(client,name){
  this.client=client;
  this.name=name;
}

HODIndex.prototype.retrieveindexfields=function(callback){
  data={'indexes':this.name}
  return this.client.call('retrieveindexfields',callback,data)
}

HODIndex.prototype.status=function(callback){
  data={'indexes':this.name}
  return this.client.call('indexstatus',callback,data)
}

HODIndex.prototype.delete=function(callback){
  data={'indexes':this.name}
  return this.client.call('deletetextindex',callback,data)
}

HODIndex.prototype.deletedocs=function(callback,data){
  data.indexes=this.name
  return this.client.call('deletetextindex',callback,data)
}

HODIndex.prototype.create=function(callback,data){
  if (typeof data == "undefined") data={'flavor':'standard'}
  data.index=this.name
  return this.client.call('createtextindex',callback,data)
}

HODIndex.prototype.adddocs=function(callback,docs,data){
  data.indexes=this.name
  docs={'document':docs}
  data.json=JSON.stringify(docs)
  return this.client.call('addtotextindex',callback,data)
}

function HODConnector(client,name){
  this.client=client;
  this.name=name;
}

HODConnector.prototype.start=function(callback){
  data={'connector':this.name}
  return this.client.call('startconnector',callback,data)
}

HODConnector.prototype.status=function(callback){
  data={'connector':this.name}
  return this.client.call('connectorstatus',callback,data)
}

HODConnector.prototype.retrieveconfig=function(callback){
  data={'connector':this.name}
  return this.client.call('retrieveconfig',callback,data)
}

HODConnector.prototype.updateconnector=function(callback,data){
  data.connector=this.name
  return this.client.call('updateconnector',callback,data)
}

HODConnector.prototype.delete=function(callback){
  data={'connector':this.name}
  return this.client.call('deleteconnector',callback,data)
}

// export the class
exports.HODClient=HODClient
exports.HODConnector=HODConnector
exports.HODIndex=HODIndex
<|endoftext|>


<commit_before>// Inserts shortcut buttons after all of the following:
//     <input type="text" class="vDateField">
//     <input type="text" class="vTimeField">

var DateTimeShortcuts = {
    calendars: [],
    calendarInputs: [],
    clockInputs: [],
    calendarDivName1: 'calendarbox', // name of calendar
 that gets toggled
    calendarDivName2: 'calendarin',  // name of
 that contains calendar
    calendarLinkName: 'calendarlink',// name of the link that is used to toggle
    clockDivName: 'clockbox',        // name of clock
 that gets toggled
    clockLinkName: 'clocklink',      // name of the link that is used to toggle
    admin_media_prefix: '',
    init: function() {
        // Deduce admin_media_prefix by looking at the


<commit_before>const fs = require('fs');
const _ = require('lodash');
const readLine = require('readline');
const EventEmitter = require('events');

const functionsList = [
  'Function not known', // 0
  'Port',               // 1
  'Rail Terminal',      // 2
  'Road Terminal',      // 3
  'Airport',            // 4
  'Postal Exchange Office',  // 5
  'Multimodal Function',    // 6
  'Fixed Transport Functions', // 7
  'Inland Port', // 8
  'Border Crossing', // 9
];

/**
 * GetLocations in a library to parse the UNLOCODE ("United Nations Code
 * for Trade and Transport Locations") file(s) available for download as
 * a series of CSV files from https://www.unece.org/cefact/locode/welcome.html
 */
class GetLocations extends EventEmitter {
  /**
   * Not sure why I am forced to put this comment here by ESLINT
   */
  constructor() {
    super();
  }
  /**
 * Method to parse CSV file (fileName) and generate a JSON string for each
 * country and all assocaiated UNLOCODEs within the country
 * @param {string} fileName full name and path to .csv file to be parsed
 * @param {boolean} decode the amount of normaisation/decoding on fields,
 *  i.e. fucnitons & coordinates
 */
  importLocations(fileName, decode = false) {
    let columnNames = [
      'change',
      'country',
      'location',
      'name',
      'nameSansDiacritics',
      'subdivision',
      'status', // swapped status in function due to error on expoering csv
      'function',
      'date',
      'iata',
      'coordinates',
      'remarks',
    ];
    let _this = this;
    let countriesFound = 0;
    let locationsFound = 0;
    let currentCountry = {};
    const readline = readLine.createInterface({
      input: fs.createReadStream(fileName, {encoding: 'utf8'}),
    });

    /* Read a line from the input stream/file */
    readline.on('line', function(line) {
      let lineRead = [];
      // remove double quotes for all values in the CSV file
      line = _.replace(line, /"/g, '');
      // transpose all the values in the file input line with the array of columns
      lineRead = _.fromPairs(_.zip(columnNames, _.split(line, ',')));

      // Check to see if we have found a new country
      if (lineRead.country != currentCountry._id) {
        // Check to see if this is a new country and not just
        // the first country found, if that is the case then
        // send back a complete Object/Array of the country
        if (countriesFound) {
          // emit the now complete UNLOCODE country collection
          _this.emit('recordFound', currentCountry);
          if (currentCountry._id == 'BE') console.log(JSON.stringify(currentCountry, true, 3));
          locationsFound = 0; //  Reset the location index for the new country
        }

        // currentCountry={};
        currentCountry._id = lineRead.country;
        currentCountry.country = lineRead.name.substring(1);
        currentCountry.locations = _.remove(currentCountry.locations, true);
        countriesFound += 1;
      } else {
        // the current input line is not a new country, so we will add
        // a new location to the current country object
        currentCountry.locations.push({});
        currentCountry.locations[locationsFound].location = lineRead.location;
        currentCountry.locations[locationsFound].name = lineRead.name;
        currentCountry.locations[locationsFound].nameSansDiacritics =
          lineRead.nameSansDiacritics;
        // do we want to denormalise the output?
        if (decode) {
          lineRead = deNormalise(lineRead);
        }
        currentCountry.locations[locationsFound].details = _.omit(lineRead, [
          'country',
          'location',
          'name',
          'nameSansDiacritics',
        ]);
        locationsFound += 1;
      }
    });

    readline.on('close', function() {
      _this.emit('recordFound', currentCountry);
      _this.emit('done', countriesFound + ' countries found');
    });
    return;
  }
}
/**
 * function deNormalise
 * @param {string} LineRead is an array of named pairs matcing the UNLOCODE
 * 'change','country'..coordinate, remarks fields
 * @return {string} that has been denormalised
 */
function deNormalise(LineRead) {
  // denormalise the location function into descriptive text
  // remove the '-' chars and swap the B (border crossing) designator to an 8
  let locationFunction = _.replace(LineRead.function, /\W/g, '');
  locationFunction = _.replace(locationFunction, /b/g, '9');

  let normLocationFunction = [];

  // TODO need to change function to OBJECT value pairs and not array

  for (let value of locationFunction) {
    normLocationFunction.push(functionsList[_.toInteger(value)] + ': true');
  }
  LineRead.function = normLocationFunction;
  // now the lat/long coordinate
  // ddmmN dddmmW, ddmmS dddmmE
  if (LineRead.coordinates != '') {
    let coordinates = {
      latitude: 0,
      longitude: 0,
    };
    coordinates.latitude = _.toNumber(
      LineRead.coordinates.substring(0, 2) +
      '.' +
      LineRead.coordinates.substring(2, 4)
    );

    coordinates.longitude = _.toNumber(
      LineRead.coordinates.substring(6, 9) +
      '.' +
      LineRead.coordinates.substring(9, 11)
    );

    if (LineRead.coordinates.includes('S')) {
      coordinates.latitude = coordinates.latitude * -1;
    }
    if (LineRead.coordinates.includes('W')) {
      coordinates.longitude = coordinates.longitude * -1;
    }
    LineRead.coordinates = coordinates;
  } else {
    LineRead.coordinates = null;
  }
  // console.log(LineRead.coordinates);
  return LineRead;
}
module.exports = GetLocations;
<commit_msg>added better comment on CSV sort order<commit_after>const fs = require('fs');
const _ = require('lodash');
const readLine = require('readline');
const EventEmitter = require('events');

const functionsList = [
  'Function not known', // 0
  'Port',               // 1
  'Rail Terminal',      // 2
  'Road Terminal',      // 3
  'Airport',            // 4
  'Postal Exchange Office',  // 5
  'Multimodal Function',    // 6
  'Fixed Transport Functions', // 7
  'Inland Port', // 8
  'Border Crossing', // 9
];

/**
 * GetLocations in a library to parse the UNLOCODE ("United Nations Code
 * for Trade and Transport Locations") file(s) available for download as
 * a series of CSV files from https://www.unece.org/cefact/locode/welcome.html
 */
class GetLocations extends EventEmitter {
  /**
   * Not sure why I am forced to put this comment here by ESLINT
   */
  constructor() {
    super();
  }
  /**
 * Method to parse CSV file (fileName) and generate a JSON string for each
 * country and all assocaiated UNLOCODEs within the country
 * @param {string} fileName full name and path to .csv file to be parsed
 * @param {boolean} decode the amount of normaisation/decoding on fields,
 *  i.e. fucnitons & coordinates
 */
  importLocations(fileName, decode = false) {
    let columnNames = [
      'change',
      'country',
      'location',
      'name',
      'nameSansDiacritics',
      'subdivision',
      'status', // swapped status in function due to error on expoering csv
      'function',
      'date',
      'iata',
      'coordinates',
      'remarks',
    ];
    let _this = this;
    let countriesFound = 0;
    let locationsFound = 0;
    let currentCountry = {};
    const readline = readLine.createInterface({
      input: fs.createReadStream(fileName, {encoding: 'utf8'}),
    });

    /* Read a line from the input stream/file */
    readline.on('line', function(line) {
      let lineRead = [];
      // remove double quotes for all values in the CSV file
      line = _.replace(line, /"/g, '');
      // transpose all the values in the file input line with the array of columns
      lineRead = _.fromPairs(_.zip(columnNames, _.split(line, ',')));

      // Check to see if we have found a new country (the default sort order for the CSV file is Country, Name)
      if (lineRead.country != currentCountry._id) {
        // Check to see if this is a new country and not just
        // the first country found, if that is the case then
        // send back a complete Object/Array of the country
        if (countriesFound) {
          // emit the now complete UNLOCODE country collection
          _this.emit('recordFound', currentCountry);
          if (currentCountry._id == 'BE') console.log(JSON.stringify(currentCountry, true, 3));
          locationsFound = 0; //  Reset the location index for the new country
        }

        // currentCountry={};
        currentCountry._id = lineRead.country;
        currentCountry.country = lineRead.name.substring(1);
        currentCountry.locations = _.remove(currentCountry.locations, true);
        countriesFound += 1;
      } else {
        // the current input line is not a new country, so we will add
        // a new location to the current country object
        currentCountry.locations.push({});
        currentCountry.locations[locationsFound].location = lineRead.location;
        currentCountry.locations[locationsFound].name = lineRead.name;
        currentCountry.locations[locationsFound].nameSansDiacritics =
          lineRead.nameSansDiacritics;
        // do we want to denormalise the output?
        if (decode) {
          lineRead = deNormalise(lineRead);
        }
        currentCountry.locations[locationsFound].details = _.omit(lineRead, [
          'country',
          'location',
          'name',
          'nameSansDiacritics',
        ]);
        locationsFound += 1;
      }
    });

    readline.on('close', function() {
      _this.emit('recordFound', currentCountry);
      _this.emit('done', countriesFound + ' countries found');
    });
    return;
  }
}
/**
 * function deNormalise
 * @param {string} LineRead is an array of named pairs matcing the UNLOCODE
 * 'change','country'..coordinate, remarks fields
 * @return {string} that has been denormalised
 */
function deNormalise(LineRead) {
  // denormalise the location function into descriptive text
  // remove the '-' chars and swap the B (border crossing) designator to an 8
  let locationFunction = _.replace(LineRead.function, /\W/g, '');
  locationFunction = _.replace(locationFunction, /b/g, '9');

  let normLocationFunction = [];

  // TODO need to change function to OBJECT value pairs and not array

  for (let value of locationFunction) {
    normLocationFunction.push(functionsList[_.toInteger(value)] + ': true');
  }
  LineRead.function = normLocationFunction;
  // now the lat/long coordinate
  // ddmmN dddmmW, ddmmS dddmmE
  if (LineRead.coordinates != '') {
    let coordinates = {
      latitude: 0,
      longitude: 0,
    };
    coordinates.latitude = _.toNumber(
      LineRead.coordinates.substring(0, 2) +
      '.' +
      LineRead.coordinates.substring(2, 4)
    );

    coordinates.longitude = _.toNumber(
      LineRead.coordinates.substring(6, 9) +
      '.' +
      LineRead.coordinates.substring(9, 11)
    );

    if (LineRead.coordinates.includes('S')) {
      coordinates.latitude = coordinates.latitude * -1;
    }
    if (LineRead.coordinates.includes('W')) {
      coordinates.longitude = coordinates.longitude * -1;
    }
    LineRead.coordinates = coordinates;
  } else {
    LineRead.coordinates = null;
  }
  // console.log(LineRead.coordinates);
  return LineRead;
}
module.exports = GetLocations;
<|endoftext|>


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

_.mixin({
	pickTruthy: function (obj, props) {
		return _.spread(_.cond([
			[_.negate(_.isObject), function () {throw new Error('Invalid params');}],
			[this.argsLength(_.partial(_.isEqual, 1)), _.constant(_.pickBy(obj, this.isTruthy))],
			[_.flow(_.nthArg(1), _.isString), _.constant((this.isTruthy(obj[props]) ? _.pick(obj, props) : {}))],
			[_.flow(_.nthArg(1), _.isArray), _.constant(_.pickBy(_.pick(obj, props), this.isTruthy))],
			[_.honesty(), function () {throw new Error('Invalid params');}],
		]))(_.reject([obj, props], _.isUndefined));
	}
});

_.mixin({
	isFalsy: function (val) {
		return !Boolean(val);
	},
	isTruthy: function (val) {
		return Boolean(val);
	},
	isNullOrUndefined: function (val) {
		return _.overSome(_.isNull, _.isUndefined)(val);
	},
	isIntable: function (val) {
		// TODO: extract flowover, identity spread, sizeIs, to functions
		return _.bind(_.cond([
			[
				_.overSome(_.isString, _.isNumber),
				_.flow(
					_.over(_.identity, _.identity),
					_.spread(_.overArgs(_.eq, _.toInteger, _.toNumber)))
				],
			[
				_.overEvery(
					_.isArray,
					_.flow(_.tail, _.isEmpty),
					_.overSome(_.isEvery('Integer'), _.isEvery('String'))
				),
				_.flow(_.first, this.isIntable)
			],
			[_.honesty(), _.falsehood()]
		]), this)(val);
	},
	isBare: function (val) {
		return _.bind(_.cond([
			[_.isUndefined, _.honesty()],
			[_.overEvery(_.overSome(_.isPlainObject, _.isArrayLikeObject), _.isEmpty), _.honesty()],
			[_.overSome(_.isPlainObject, _.isArrayLikeObject), _.flow(_.values, _.isEvery('Bare'))],
			[_.honesty(), _.falsehood()]
		]), this)(val);
	},
	honesty: function () {
		return _.constant(true);
	},
	falsehood: function () {
		return _.constant(false);
	}
});

_.mixin({
	argsLength: function (callback) {
		return _.flow(_.rest(_.size), _.find([callback, _.identity]));
	},
	fullSize: function(obj) {
		return _.reduce(
			_.pickBy(obj, _.isPlainObject),
			_.overArgs(_.add, _.identity, _.thisBind('fullSize')),
			_.size(obj)
		);
	},
	allPaths: function (obj, parentPath) {
		var parentPathPrepend = _.partial(_.overTern(_.isTruthy, _.concat, _.rearg(_.arrayWrap, 1)), parentPath);
		return _.reduce(
			_.pickBy(obj, _.isPlainObject),
			_.overTern(
				_.rearg(_.isPlainObject, 1),
				_.flow(
					_.over(_.identity, _.rearg(_.overArg(_.thisBind('allPaths'), parentPathPrepend, 1), 1, 2)),
					_.spread(_.concat)
				)
			),
			_.map(_.keys(obj), _.unary(parentPathPrepend))
		);
	}
});

_.mixin({
	collCloner: function (callback, self) {
		return _.overArg(callback, _.cloneDeep);
	}
});

_.mixin({
	hasAll: function (obj, array) {
		return _.every(array, _.partial(_.has, obj));
	},
	hasAny: function (obj, array) {
		return _.some(array, _.partial(_.has, obj));
	},
	includesAny: function (searchIn, searchFor) {
		return _.cond([
			[
				_.rest(_.isEvery('String')),
				_.rearg(_.overArgs(_.some, _.identity, _.partial(_.partial, _.includes)), 1, 0)
			],
			[
				_.overEvery(
					_.unary(_.isEvery('PlainObject')),
					_.rearg(_.unary(_.isEvery('String')), 1)
				),
				_.negate(_.overArg(_.disjoint, _.partialRight(_.flatMap, _.keys)))
			],
			[_.rest(_.isEvery('Array')), _.negate(_.disjoint)],
			[_.isPlainObject, _.negate(_.overArg(_.disjoint, _.values))],
			[_.honesty(), _.falsehood()]
		])(searchIn, searchFor);
	},
	disjoint: function (arrayA, arrayB) {
		return _.isEmpty(_.intersection(arrayA, arrayB));
	},
	isEvery: function (predicate) {
		// TODO: refactor to remove assignment operator and create lodash error throwing function
		if (_.isString(predicate)) {
			predicate = _['is' + _.upperFirst(predicate)];
			if (_.isUndefined(predicate)) {
				throw new Error('No such lodash function');
			}
		}
		return _.partialRight(_.every, predicate);
	}
});

_.mixin({
	extendAll: function (collection, sources) {
		return _.overTern(
			_.flow(_.flatten, _.union, _.negate(_.isEvery('PlainObject'))),
			function () {throw new Error('Invalid params');},
			_.spread(_.rest(_.overArgs(
				_.each,
				_.identity,
				_.flow(_.partial(_.partial, _.each), _.partial(_.flow, _.curry(_.extend, 2)), _.unary)
			), 1))
		)(arguments);
	}
});

_.mixin({
	getAll: function (object, paths, default_) {
		return _.map(paths, _.partial(_.rearg(_.get, 0, 2, 1), object, default_));
	},
	getFirst: function (object, paths, default_) {
		return _.get(object, _.find(paths, _.partial(_.has, object)), default_);
	},
	setDefinite: function (object, path, value) {
		return _.overTern(
			_.flow(_.negate, _.unary, _.flip)(_.isUndefined),
			_.set,
			_.noop
		)(object, path, value);
	},
	setEach: function (object, paths, values) {
		return _.reduce(_.zip(paths, values), _.spread(_.set, 1), object);
	}
});

_.mixin({
	// TODO: Rename as 'until' to match ruby inspiration?
	eachUntil: function (collection, callback, predicate) {
		_.each(collection, _.ary(
			_.overTern(_.find([predicate, _.identity]), _.falsehood(), callback), 3
		));
	},
	overTern: function (cond, ifCond, ifNotCond) {
		return _.cond([[cond, ifCond],[_.honesty(), _.find([ifNotCond, _.identity])]]);
	}
});

_.mixin({
	pathsEqual: function (pair1, pair2) {
		return _.every([
			_.has(pair1[0], pair1[1]),
			_.has(pair2[0], pair2[1]),
			_.isEqual(_.get(pair1[0], pair1[1]), _.get(pair2[0], pair2[1]))
		]);
	},
	innerJoin: function (object1, object2) {
		// TODO: does not work for NaN
		// TODO: simplify the below
		return _.reduce(
			_.sortBy(_.allPaths(object1)),
			_.ary(
				_.overTern(
					// TODO: extract below _.flow(_.flow...) to function, as well as _.rearg(_.unary)
					_.flow(
						_.flow,
						_.unary,
						_.partialRight(_.rearg, 1)
					)(
						_.arrayWrap,
						_.partial(_.partialRight, _.concat),
						_.unary,
						_.partial(_.map, [object1, object2]),
						_.spread(_.pathsEqual)
					),
					_.flow(
						_.over(
							_.rest(_.identity),
							_.flow(
								_.flow,
								_.unary,
								_.partialRight(_.rearg, 1)
							)(
								_.partial(_.get, object1),
								_.cloneDeep
							)
						),
						_.flatten,
						_.spread(_.set)
					)
			), 2),
			{}
		);
	}
});

_.mixin({
	arrayWrap: function (val) {
		return [val];
	},
	defaultZero: function (val) {
		return _.cond([
			[_.isTruthy, _.identity],
			[_.honesty, _.constant(0)]
		])(val);
	}
})

_.mixin({
	overArg: function (func, transform, argIndex) {
		return _.overArgs(func,
			_.set(_.fill([], _.identity, _.size(func)), _.defaultZero(argIndex), transform)
		);
	},
	filtration: function (collection, filterArray) {
		// TODO: make sure 3rd param does not affect anything
		// TODO: handle matches, matchesProperty and property iteratee shorthands
		return _.reduce(filterArray, _.filter, collection);
	},
	setBySelf: function (obj, atPath, toPath) {
		return _.set(obj, atPath, _.get(obj, toPath));
	},
	applyToNested: function (func, nestedPath, argIndex) {
		return _.overArg(func, _.partialRight(_.get, nestedPath), argIndex);
	},
	thisBind: function (func) {
		return _.bind(this[func], this);
	},
	mapKeysAndValues: function (object, valueMap, keyMap) {
		if (arguments.length === 1) {
			return object;
		}
		if (arguments.length === 2) {
			var mappedObject = {}
			_.each(object, function (value, key) {
				var x = valueMap(value, key);
				if (_.isArray(x)) {
					_.set(mappedObject, x[1], x[0])
				}
				else if (_.isPlainObject(x)) {
					_.extend(mappedObject, x)
				}
			});
			return mappedObject;
		}
		else if (arguments.length === 3) {
			var mappedObject = {}
			_.each(object, function (value, key) {
				var x = valueMap(value);
				var y = keyMap(key);
				_.set(mappedObject, y, x)
			});
			return mappedObject;
		}
	}
});

module.exports = _;<commit_msg>Almost complete functional refactor of mapValuesAndKeys.<commit_after>var _ = require('lodash');

_.mixin({
	pickTruthy: function (obj, props) {
		return _.spread(_.cond([
			[_.negate(_.isObject), function () {throw new Error('Invalid params');}],
			[this.argsLength(_.partial(_.isEqual, 1)), _.constant(_.pickBy(obj, this.isTruthy))],
			[_.flow(_.nthArg(1), _.isString), _.constant((this.isTruthy(obj[props]) ? _.pick(obj, props) : {}))],
			[_.flow(_.nthArg(1), _.isArray), _.constant(_.pickBy(_.pick(obj, props), this.isTruthy))],
			[_.honesty(), function () {throw new Error('Invalid params');}],
		]))(_.reject([obj, props], _.isUndefined));
	}
});

_.mixin({
	isFalsy: function (val) {
		return !Boolean(val);
	},
	isTruthy: function (val) {
		return Boolean(val);
	},
	isNullOrUndefined: function (val) {
		return _.overSome(_.isNull, _.isUndefined)(val);
	},
	isIntable: function (val) {
		// TODO: extract flowover, identity spread, sizeIs, to functions
		return _.bind(_.cond([
			[
				_.overSome(_.isString, _.isNumber),
				_.flow(
					_.over(_.identity, _.identity),
					_.spread(_.overArgs(_.eq, _.toInteger, _.toNumber)))
				],
			[
				_.overEvery(
					_.isArray,
					_.flow(_.tail, _.isEmpty),
					_.overSome(_.isEvery('Integer'), _.isEvery('String'))
				),
				_.flow(_.first, this.isIntable)
			],
			[_.honesty(), _.falsehood()]
		]), this)(val);
	},
	isBare: function (val) {
		return _.bind(_.cond([
			[_.isUndefined, _.honesty()],
			[_.overEvery(_.overSome(_.isPlainObject, _.isArrayLikeObject), _.isEmpty), _.honesty()],
			[_.overSome(_.isPlainObject, _.isArrayLikeObject), _.flow(_.values, _.isEvery('Bare'))],
			[_.honesty(), _.falsehood()]
		]), this)(val);
	},
	honesty: function () {
		return _.constant(true);
	},
	falsehood: function () {
		return _.constant(false);
	}
});

_.mixin({
	argsLength: function (callback) {
		return _.flow(_.rest(_.size), _.find([callback, _.identity]));
	},
	fullSize: function(obj) {
		return _.reduce(
			_.pickBy(obj, _.isPlainObject),
			_.overArgs(_.add, _.identity, _.thisBind('fullSize')),
			_.size(obj)
		);
	},
	allPaths: function (obj, parentPath) {
		var parentPathPrepend = _.partial(_.overTern(_.isTruthy, _.concat, _.rearg(_.arrayWrap, 1)), parentPath);
		return _.reduce(
			_.pickBy(obj, _.isPlainObject),
			_.overTern(
				_.rearg(_.isPlainObject, 1),
				_.flow(
					_.over(_.identity, _.rearg(_.overArg(_.thisBind('allPaths'), parentPathPrepend, 1), 1, 2)),
					_.spread(_.concat)
				)
			),
			_.map(_.keys(obj), _.unary(parentPathPrepend))
		);
	}
});

_.mixin({
	collCloner: function (callback, self) {
		return _.overArg(callback, _.cloneDeep);
	}
});

_.mixin({
	hasAll: function (obj, array) {
		return _.every(array, _.partial(_.has, obj));
	},
	hasAny: function (obj, array) {
		return _.some(array, _.partial(_.has, obj));
	},
	includesAny: function (searchIn, searchFor) {
		return _.cond([
			[
				_.rest(_.isEvery('String')),
				_.rearg(_.overArgs(_.some, _.identity, _.partial(_.partial, _.includes)), 1, 0)
			],
			[
				_.overEvery(
					_.unary(_.isEvery('PlainObject')),
					_.rearg(_.unary(_.isEvery('String')), 1)
				),
				_.negate(_.overArg(_.disjoint, _.partialRight(_.flatMap, _.keys)))
			],
			[_.rest(_.isEvery('Array')), _.negate(_.disjoint)],
			[_.isPlainObject, _.negate(_.overArg(_.disjoint, _.values))],
			[_.honesty(), _.falsehood()]
		])(searchIn, searchFor);
	},
	disjoint: function (arrayA, arrayB) {
		return _.isEmpty(_.intersection(arrayA, arrayB));
	},
	isEvery: function (predicate) {
		// TODO: refactor to remove assignment operator and create lodash error throwing function
		if (_.isString(predicate)) {
			predicate = _['is' + _.upperFirst(predicate)];
			if (_.isUndefined(predicate)) {
				throw new Error('No such lodash function');
			}
		}
		return _.partialRight(_.every, predicate);
	}
});

_.mixin({
	extendAll: function (collection, sources) {
		return _.overTern(
			_.flow(_.flatten, _.union, _.negate(_.isEvery('PlainObject'))),
			function () {throw new Error('Invalid params');},
			_.spread(_.rest(_.overArgs(
				_.each,
				_.identity,
				_.flow(_.partial(_.partial, _.each), _.partial(_.flow, _.curry(_.extend, 2)), _.unary)
			), 1))
		)(arguments);
	}
});

_.mixin({
	getAll: function (object, paths, default_) {
		return _.map(paths, _.partial(_.rearg(_.get, 0, 2, 1), object, default_));
	},
	getFirst: function (object, paths, default_) {
		return _.get(object, _.find(paths, _.partial(_.has, object)), default_);
	},
	setDefinite: function (object, path, value) {
		return _.overTern(
			_.flow(_.negate, _.unary, _.flip)(_.isUndefined),
			_.set,
			_.noop
		)(object, path, value);
	},
	setEach: function (object, paths, values) {
		return _.reduce(_.zip(paths, values), _.spread(_.set, 1), object);
	}
});

_.mixin({
	// TODO: Rename as 'until' to match ruby inspiration?
	eachUntil: function (collection, callback, predicate) {
		_.each(collection, _.ary(
			_.overTern(_.find([predicate, _.identity]), _.falsehood(), callback), 3
		));
	},
	overTern: function (cond, ifCond, ifNotCond) {
		return _.cond([[cond, ifCond],[_.honesty(), _.find([ifNotCond, _.identity])]]);
	}
});

_.mixin({
	pathsEqual: function (pair1, pair2) {
		return _.every([
			_.has(pair1[0], pair1[1]),
			_.has(pair2[0], pair2[1]),
			_.isEqual(_.get(pair1[0], pair1[1]), _.get(pair2[0], pair2[1]))
		]);
	},
	innerJoin: function (object1, object2) {
		// TODO: does not work for NaN
		// TODO: simplify the below
		return _.reduce(
			_.sortBy(_.allPaths(object1)),
			_.ary(
				_.overTern(
					// TODO: extract below _.flow(_.flow...) to function, as well as _.rearg(_.unary)
					_.flow(
						_.flow,
						_.unary,
						_.partialRight(_.rearg, 1)
					)(
						_.arrayWrap,
						_.partial(_.partialRight, _.concat),
						_.unary,
						_.partial(_.map, [object1, object2]),
						_.spread(_.pathsEqual)
					),
					_.flow(
						_.over(
							_.rest(_.identity),
							_.flow(
								_.flow,
								_.unary,
								_.partialRight(_.rearg, 1)
							)(
								_.partial(_.get, object1),
								_.cloneDeep
							)
						),
						_.flatten,
						_.spread(_.set)
					)
			), 2),
			{}
		);
	}
});

_.mixin({
	arrayWrap: function (val) {
		return [val];
	},
	defaultZero: function (val) {
		return _.cond([
			[_.isTruthy, _.identity],
			[_.honesty, _.constant(0)]
		])(val);
	}
})

_.mixin({
	overArg: function (func, transform, argIndex) {
		return _.overArgs(func,
			_.set(_.fill([], _.identity, _.size(func)), _.defaultZero(argIndex), transform)
		);
	},
	filtration: function (collection, filterArray) {
		// TODO: make sure 3rd param does not affect anything
		// TODO: handle matches, matchesProperty and property iteratee shorthands
		return _.reduce(filterArray, _.filter, collection);
	},
	setBySelf: function (obj, atPath, toPath) {
		return _.set(obj, atPath, _.get(obj, toPath));
	},
	applyToNested: function (func, nestedPath, argIndex) {
		return _.overArg(func, _.partialRight(_.get, nestedPath), argIndex);
	},
	thisBind: function (func) {
		return _.bind(this[func], this);
	},
	mapKeysAndValues: function (object, valueMap, keyMap) {
		if (arguments.length === 1) {
			return object;
		}
		if (arguments.length === 2) {
			return _.reduce(
				object,
				_.flow(
					_.over(_.identity, _.rearg(valueMap, 1, 2)),
					_.spread(_.cond([
						[_.rearg(_.isArray, 1), _.spread(_.rearg(_.set, 0, 2, 1), 1)],
						[_.rearg(_.isPlainObject, 1), _.extend]
					]))
				),
				{}
			);
		}
		else if (arguments.length === 3) {
			return _.reduce(
				object,
				_.rearg(_.overArgs(_.set, _.identity, keyMap, valueMap), 0, 2, 1),
				{}
			);
		}
	}
});

module.exports = _;<|endoftext|>


<commit_before>import Sortable from "sortablejs"
import {EditorState, Plugin} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {keymap} from "prosemirror-keymap"
import {history} from "prosemirror-history"
import {baseKeymap} from "prosemirror-commands"
import {gapCursor} from "prosemirror-gapcursor"
import {menuBar} from "prosemirror-menu"
import {buildKeymap, buildInputRules} from "prosemirror-example-setup"
import {tableEditing} from "prosemirror-tables"

import {randomHeadingId} from "../schema/common"
import {TagsView, ContributorsView} from "../editor/state_plugins"
import {
    documentConstructorTemplate,
    templateEditorValueTemplate,
    toggleEditorButtonTemplate,
    footnoteTemplate,
    languagesTemplate,
    papersizesTemplate
} from "./templates"
import {whenReady, ensureCSS, findTarget} from "../common"
import {
    helpSchema,
    helpMenuContent,
    richtextPartSchema,
    richtextMenuContent,
    tablePartSchema,
    tableMenuContent,
    headingPartSchema,
    headingMenuContent,
    tagsPartSchema,
    contributorsPartSchema,
    templateHash
} from "./schema"

// from https://codeburst.io/throttling-and-debouncing-in-javascript-646d076d0a44
function debounced(delay, fn) {
    let timerId
    return function(...args) {
        if (timerId) {
            clearTimeout(timerId)
        }
        timerId = setTimeout(() => {
            fn(...args)
            timerId = null
        }, delay)
    }
}

export class DocumentTemplateDesigner {
    constructor({staticUrl}) {
        this.staticUrl = staticUrl
        this.definitionTextarea = false
        this.templateEditor = false
        this.errors = {}
        this.value = []

        this.editors = []
        this.listeners = {
            onScroll: debounced(200, () => this.onScroll())
        }
    }

    init() {
        ensureCSS([
            'common.css',
            'dialog.css',
            'prosemirror.css',
            'prosemirror-menu.css',
            'prosemirror-example-setup.css',
            'document_template_designer.css',
            'tags.css',
            'contributors.css'
        ], this.staticUrl)
        whenReady().then(() => {
            this.definitionTextarea = document.querySelector('textarea[name=definition]')
            this.definitionHashInput = document.querySelector('#id_definition_hash')
            this.definitionHashInputBlock = document.querySelector('div.field-definition_hash')
            this.getInitialValue()
            this.modifyDOM()
            this.bind()
        })

    }

    modifyDOM() {
        this.definitionTextarea.style.display='none'
        this.definitionHashInputBlock.style.display='none'
        this.definitionTextarea.insertAdjacentHTML(
            'beforebegin',
            toggleEditorButtonTemplate()
        )

        this.definitionTextarea.insertAdjacentHTML(
            'afterend',
            documentConstructorTemplate({value: this.value})
        )
        this.templateEditor = document.getElementById('template-editor')
        this.setupInitialEditors()
    }

    getInitialValue() {
        this.value = JSON.parse(this.definitionTextarea.value)
    }

    setCurrentValue() {
        let valid = true
        const ids = []
        this.errors = {}
        this.value = {
            type: 'article',
            content: [{type: 'title'}].concat(
                Array.from(document.querySelectorAll('.to-container .doc-part:not(.fixed)')).map(
                    el => {
                        const type = el.dataset.type,
                            id = el.querySelector('input.id').value,
                            title = el.querySelector('input.title').value,
                            help = this.getEditorValue(el.querySelector('.instructions')),
                            initial = this.getEditorValue(
                                el.querySelector('.initial'),
                                true
                            ),
                            locking = el.querySelector('.locking option:checked') ? el.querySelector('.locking option:checked').value : 'false',
                            optional = el.querySelector('.optional option:checked').value,
                            attrs = {id, title},
                            node = {type, attrs}
                        if (help) {
                            attrs.help = help
                        }
                        if (initial) {
                            attrs.initial = initial
                            node.content = JSON.parse(JSON.stringify(initial))
                        }
                        if (optional !== 'false') {
                            attrs.optional = optional
                        }
                        if (optional === 'hidden') {
                            attrs.hidden = true
                        }
                        if (locking !== 'false') {
                            attrs.locking = locking
                        }
                        switch (type) {
                            case 'richtext_part':
                            case 'heading_part': {
                                attrs.elements = Array.from(el.querySelectorAll('.elements:checked')).map(el => el.value)
                                if (!attrs.elements.length) {
                                    attrs.elements = ['paragraph']
                                }
                                attrs.marks = Array.from(el.querySelectorAll('.marks:checked')).map(el => el.value)
                                const language = el.querySelector('.language').value
                                if (language !== 'false') {
                                    attrs.language = language
                                }
                                if (!node.content) {
                                    node.content = [{type: attrs.elements[0]}]
                                }
                                const metadata = el.querySelector('select.metadata').value
                                if (metadata !== 'false') {
                                    attrs.metadata = metadata
                                }
                                break
                            }
                            case 'table_part': {
                                attrs.elements = Array.from(el.querySelectorAll('.elements:checked')).map(el => el.value)
                                if (!attrs.elements.includes('paragraph')) {
                                    // tables need to allow paragraphs
                                    attrs.elements.push('paragraph')
                                }
                                attrs.marks = Array.from(el.querySelectorAll('.marks:checked')).map(el => el.value)
                                const language = el.querySelector('.language').value
                                if (language !== 'false') {
                                    attrs.language = language
                                }
                                if (!node.content) {
                                    node.content = [{type: 'table', content: [{type: 'table_row', content: [{type: 'table_cell', content: [{type: 'paragraph'}]}]}]}]
                                }
                                break
                            }
                            case 'contributors_part':
                            case 'tags_part': {
                                attrs.item_title = el.querySelector('input.item_title').value
                                const metadata = el.querySelector('select.metadata').value
                                if (metadata !== 'false') {
                                    attrs.metadata = metadata
                                }
                                break
                            }
                            default:
                                break
                        }

                        if (!id.length) {
                            valid = false
                            this.errors.missing_id = gettext('All document parts need an ID.')
                        }
                        if (/\s/.test(id)) {
                            valid = false
                            this.errors.no_spaces = gettext('IDs cannot contain spaces.')
                        }
                        if (ids.includes(id)) {
                            valid = false
                            this.errors.unique_id = gettext('IDs have to be unique.')
                        }
                        ids.push(id)
                        return node
                    }
                )
            ),
            attrs: {
                footnote_elements: Array.from(document.querySelectorAll('.footnote-value .elements:checked')).map(el => el.value),
                footnote_marks: Array.from(document.querySelectorAll('.footnote-value .marks:checked')).map(el => el.value),
                languages: Array.from(document.querySelectorAll('.languages-value option:checked')).map(el => el.value),
                papersizes: Array.from(document.querySelectorAll('.papersizes-value option:checked')).map(el => el.value),
                template: document.querySelector('#id_title').value
            }
        }
        if (!this.value.attrs.papersizes.length) {
            this.value.attrs.papersizes = ['A4']
        }
        this.value.attrs.papersize = this.value.attrs.papersizes[0]
        if (!this.value.attrs.footnote_elements.length) {
            this.value.attrs.footnote_elements = ['paragraph']
        }
        if (!this.value.attrs.languages.length) {
            this.value.attrs.languages = ['en-US']
        }
        this.value.attrs.language = this.value.attrs.languages[0]

        this.definitionTextarea.value = JSON.stringify(this.value)
        this.definitionHashInput.value = templateHash(this.value)
        this.showErrors()
        return valid
    }

    showErrors() {
        this.definitionTextarea.parentElement.querySelector('ul.errorlist').innerHTML =
            Object.values(this.errors).map(error => `<li>${error}</li>`).join('')
    }

    setupInitialEditors() {
        Array.from(document.querySelectorAll('.to-container .doc-part:not(.fixed)')).forEach((el, index) => {
            const value = this.value.content[index+1], // offset by title
                help = value.attrs.help,
                initial = value.attrs.initial,
                type = value.type
            this.setupEditors(el, type, help, initial)
        })
    }

    setupEditors(el, type, help = false, initial = false) {
        const helpEl = el.querySelector('.instructions')
        if (!helpEl) {
            return
        }
        const helpDoc = help ?
                helpSchema.nodeFromJSON({type:'doc', content: help}) :
                helpSchema.nodes.doc.createAndFill(),
            helpView = new EditorView(helpEl, {
                state: EditorState.create({
                    doc: helpDoc,
                    plugins: [
                        buildInputRules(helpSchema),
                        keymap(buildKeymap(helpSchema)),
                        keymap(baseKeymap),
                        gapCursor(),
                        menuBar({
                            floating: false,
                            content: helpMenuContent
                        }),
                        history()
                    ]
                })
            })
        this.editors.push([helpEl, helpView])
        const plugins = [new Plugin({
            // Adding heading IDs to all new headings.
            appendTransaction: (trs, oldState, newState) => {
                if (trs.every(tr => !tr.steps.length)) {
                    return
                }
                const newHeadings = [],
                    usedHeadingIds = []

                this.editors.forEach(([_el, view]) => {
                    view.state.doc.descendants(node => {
                        if (node.type.groups.includes('heading')) {
                            usedHeadingIds.push(node.attrs.id)
                        }
                    })
                })
                newState.doc.descendants((node, pos) => {
                    if (node.type.groups.includes('heading')) {
                        if (node.attrs.id === false || usedHeadingIds.includes(node.attrs.id)) {
                            newHeadings.push({pos, node})
                        } else {
                            usedHeadingIds.push(node.attrs.id)
                        }

                    }
                })
                if (newHeadings.length) {
                    const newTr = newState.tr
                    newHeadings.forEach(
                        newHeading => {
                            let id
                            while (!id || usedHeadingIds.includes(id)) {
                                id = randomHeadingId()
                            }
                            usedHeadingIds.push(id)
                            newTr.setNodeMarkup(
                                newHeading.pos,
                                null,
                                Object.assign({}, newHeading.node.attrs, {id})
                            )
                        }
                    )
                    return newTr
                }
            }
        })]
        let menuContent = [], schema
        switch (type) {
            case 'richtext_part':
                schema = richtextPartSchema
                menuContent = richtextMenuContent
                break
            case 'table_part':
                schema = tablePartSchema
                menuContent = tableMenuContent
                plugins.push(tableEditing())
                break
            case 'heading_part':
                schema = headingPartSchema
                menuContent = headingMenuContent
                break
            case 'tags_part':
                schema = tagsPartSchema
                plugins.push(new Plugin({
                    props: {
                        nodeViews: {
                            tags_part: (node, view, getPos) => new TagsView(node, view, getPos)
                        }
                    }
                }))
                break
            case 'contributors_part':
                schema = contributorsPartSchema
                plugins.push(new Plugin({
                    props: {
                        nodeViews: {
                            contributors_part: (node, view, getPos) => new ContributorsView(node, view, getPos)
                        }
                    }
                }))
                break
            default:
                break
        }

        if (!schema) {
           return
        }
        plugins.unshift(
            buildInputRules(schema),
            keymap(buildKeymap(schema)),
            keymap(baseKeymap),
            gapCursor(),
            menuBar({
                floating: false,
                content: menuContent
            }),
            history()
        )
        const initialEl = el.querySelector('.initial'),
            doc = initial ?
                schema.nodeFromJSON({
                    type:'doc',
                    content: [{
                        type: type,
                        content: initial
                    }]
                }) :
                schema.nodes.doc.createAndFill(),
            initialView = new EditorView(initialEl, {
                state: EditorState.create({
                    doc,
                    plugins
                })
            })
        this.editors.push([initialEl, initialView])

    }

    getEditorValue(el, inline = false) {
        const editor = this.editors.find(editor => editor[0]===el)
        if (!editor) {
            return false
        }
        const state = editor[1].state
        // Only return if there is more content that a recently initiated doc
        // would have. The number varies between part types.
        if (state.doc.nodeSize > state.schema.nodes.doc.createAndFill().nodeSize) {
            return inline ? state.doc.firstChild.toJSON().content : state.doc.toJSON().content
        }
        return false
    }

    bind() {
        new Sortable(
            document.querySelector('.from-container'),
            {
                group: {
                    name: 'document',
                    pull: 'clone',
                    put: false
                },
                sort: false,
                handle: '.doc-part-header'
            }
        )
        new Sortable(
            document.querySelector('.to-container'),
            {
                group: {
                    name: 'document',
                    pull: true,
                    put: true
                },
                handle: '.doc-part-header',
                onAdd: event => {
                    this.setupEditors(
                        event.item,
                        event.item.dataset.type
                    )
                }
            }
        )
        new Sortable(
            document.querySelector('.trash'),
            {
                group: {
                    name: 'document',
                    put: true
                },
                handle: '.doc-part-header',
                onAdd: event => event.to.removeChild(event.to.firstElementChild) // Remove the item that was just added
            }
        )

        document.body.addEventListener('click', event => {
            const el = {}
            switch (true) {
                case findTarget(event, '#toggle-editor', el):
                    event.preventDefault()
                    if (this.definitionTextarea.style.display==='none') {
                        this.definitionTextarea.style.display=''
                        this.definitionHashInputBlock.style.display=''
                        this.templateEditor.style.display='none'
                        this.setCurrentValue()
                    } else {
                        this.definitionTextarea.style.display='none'
                        this.definitionHashInputBlock.style.display='none'
                        this.templateEditor.style.display=''
                        this.getInitialValue()
                        this.templateEditor.querySelector('.to-container').innerHTML =
                            templateEditorValueTemplate({content: this.value.content.slice(1) || []})
                        this.templateEditor.querySelector('.footnote-value').innerHTML =
                            footnoteTemplate(this.value.attrs)
                        this.templateEditor.querySelector('.languages-value').innerHTML =
                            languagesTemplate(this.value.attrs)
                        this.templateEditor.querySelector('.papersizes-value').innerHTML =
                            papersizesTemplate(this.value.attrs)
                        this.setupInitialEditors()
                    }
                    break
                case findTarget(event, 'div.submit-row input[type=submit]', el):
                    if (this.definitionTextarea.style.display==='none' && !this.setCurrentValue()) {
                        event.preventDefault()
                    }
                    break
                case findTarget(event, '.doc-part .configure', el):
                    event.preventDefault()
                    el.target.closest('.doc-part').querySelector('.attrs').classList.toggle('hidden')
                    break
                default:
                    break
            }
        })

        document.addEventListener('scroll', this.listeners.onScroll)
    }

    onScroll() {
        const fromContainer = this.templateEditor.querySelector('.from-container'),
            rect = fromContainer.getBoundingClientRect()

        if (rect.height + rect.top > 0) {
            const contentSize = 6 * 61, // 61px for each content type.
                maxPadding = rect.height - contentSize - 10 // 10px for padding bottom
            fromContainer.style.paddingTop = `${Math.min(8-Math.min(rect.top, 0), maxPadding)}px`
        }

    }
}
<commit_msg>don;t redo dt heading IDs if they work<commit_after>import Sortable from "sortablejs"
import {EditorState, Plugin} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {keymap} from "prosemirror-keymap"
import {history} from "prosemirror-history"
import {baseKeymap} from "prosemirror-commands"
import {gapCursor} from "prosemirror-gapcursor"
import {menuBar} from "prosemirror-menu"
import {buildKeymap, buildInputRules} from "prosemirror-example-setup"
import {tableEditing} from "prosemirror-tables"

import {randomHeadingId} from "../schema/common"
import {TagsView, ContributorsView} from "../editor/state_plugins"
import {
    documentConstructorTemplate,
    templateEditorValueTemplate,
    toggleEditorButtonTemplate,
    footnoteTemplate,
    languagesTemplate,
    papersizesTemplate
} from "./templates"
import {whenReady, ensureCSS, findTarget} from "../common"
import {
    helpSchema,
    helpMenuContent,
    richtextPartSchema,
    richtextMenuContent,
    tablePartSchema,
    tableMenuContent,
    headingPartSchema,
    headingMenuContent,
    tagsPartSchema,
    contributorsPartSchema,
    templateHash
} from "./schema"

// from https://codeburst.io/throttling-and-debouncing-in-javascript-646d076d0a44
function debounced(delay, fn) {
    let timerId
    return function(...args) {
        if (timerId) {
            clearTimeout(timerId)
        }
        timerId = setTimeout(() => {
            fn(...args)
            timerId = null
        }, delay)
    }
}

export class DocumentTemplateDesigner {
    constructor({staticUrl}) {
        this.staticUrl = staticUrl
        this.definitionTextarea = false
        this.templateEditor = false
        this.errors = {}
        this.value = []

        this.editors = []
        this.listeners = {
            onScroll: debounced(200, () => this.onScroll())
        }
    }

    init() {
        ensureCSS([
            'common.css',
            'dialog.css',
            'prosemirror.css',
            'prosemirror-menu.css',
            'prosemirror-example-setup.css',
            'document_template_designer.css',
            'tags.css',
            'contributors.css'
        ], this.staticUrl)
        whenReady().then(() => {
            this.definitionTextarea = document.querySelector('textarea[name=definition]')
            this.definitionHashInput = document.querySelector('#id_definition_hash')
            this.definitionHashInputBlock = document.querySelector('div.field-definition_hash')
            this.getInitialValue()
            this.modifyDOM()
            this.bind()
        })

    }

    modifyDOM() {
        this.definitionTextarea.style.display='none'
        this.definitionHashInputBlock.style.display='none'
        this.definitionTextarea.insertAdjacentHTML(
            'beforebegin',
            toggleEditorButtonTemplate()
        )

        this.definitionTextarea.insertAdjacentHTML(
            'afterend',
            documentConstructorTemplate({value: this.value})
        )
        this.templateEditor = document.getElementById('template-editor')
        this.setupInitialEditors()
    }

    getInitialValue() {
        this.value = JSON.parse(this.definitionTextarea.value)
    }

    setCurrentValue() {
        let valid = true
        const ids = []
        this.errors = {}
        this.value = {
            type: 'article',
            content: [{type: 'title'}].concat(
                Array.from(document.querySelectorAll('.to-container .doc-part:not(.fixed)')).map(
                    el => {
                        const type = el.dataset.type,
                            id = el.querySelector('input.id').value,
                            title = el.querySelector('input.title').value,
                            help = this.getEditorValue(el.querySelector('.instructions')),
                            initial = this.getEditorValue(
                                el.querySelector('.initial'),
                                true
                            ),
                            locking = el.querySelector('.locking option:checked') ? el.querySelector('.locking option:checked').value : 'false',
                            optional = el.querySelector('.optional option:checked').value,
                            attrs = {id, title},
                            node = {type, attrs}
                        if (help) {
                            attrs.help = help
                        }
                        if (initial) {
                            attrs.initial = initial
                            node.content = JSON.parse(JSON.stringify(initial))
                        }
                        if (optional !== 'false') {
                            attrs.optional = optional
                        }
                        if (optional === 'hidden') {
                            attrs.hidden = true
                        }
                        if (locking !== 'false') {
                            attrs.locking = locking
                        }
                        switch (type) {
                            case 'richtext_part':
                            case 'heading_part': {
                                attrs.elements = Array.from(el.querySelectorAll('.elements:checked')).map(el => el.value)
                                if (!attrs.elements.length) {
                                    attrs.elements = ['paragraph']
                                }
                                attrs.marks = Array.from(el.querySelectorAll('.marks:checked')).map(el => el.value)
                                const language = el.querySelector('.language').value
                                if (language !== 'false') {
                                    attrs.language = language
                                }
                                if (!node.content) {
                                    node.content = [{type: attrs.elements[0]}]
                                }
                                const metadata = el.querySelector('select.metadata').value
                                if (metadata !== 'false') {
                                    attrs.metadata = metadata
                                }
                                break
                            }
                            case 'table_part': {
                                attrs.elements = Array.from(el.querySelectorAll('.elements:checked')).map(el => el.value)
                                if (!attrs.elements.includes('paragraph')) {
                                    // tables need to allow paragraphs
                                    attrs.elements.push('paragraph')
                                }
                                attrs.marks = Array.from(el.querySelectorAll('.marks:checked')).map(el => el.value)
                                const language = el.querySelector('.language').value
                                if (language !== 'false') {
                                    attrs.language = language
                                }
                                if (!node.content) {
                                    node.content = [{type: 'table', content: [{type: 'table_row', content: [{type: 'table_cell', content: [{type: 'paragraph'}]}]}]}]
                                }
                                break
                            }
                            case 'contributors_part':
                            case 'tags_part': {
                                attrs.item_title = el.querySelector('input.item_title').value
                                const metadata = el.querySelector('select.metadata').value
                                if (metadata !== 'false') {
                                    attrs.metadata = metadata
                                }
                                break
                            }
                            default:
                                break
                        }

                        if (!id.length) {
                            valid = false
                            this.errors.missing_id = gettext('All document parts need an ID.')
                        }
                        if (/\s/.test(id)) {
                            valid = false
                            this.errors.no_spaces = gettext('IDs cannot contain spaces.')
                        }
                        if (ids.includes(id)) {
                            valid = false
                            this.errors.unique_id = gettext('IDs have to be unique.')
                        }
                        ids.push(id)
                        return node
                    }
                )
            ),
            attrs: {
                footnote_elements: Array.from(document.querySelectorAll('.footnote-value .elements:checked')).map(el => el.value),
                footnote_marks: Array.from(document.querySelectorAll('.footnote-value .marks:checked')).map(el => el.value),
                languages: Array.from(document.querySelectorAll('.languages-value option:checked')).map(el => el.value),
                papersizes: Array.from(document.querySelectorAll('.papersizes-value option:checked')).map(el => el.value),
                template: document.querySelector('#id_title').value
            }
        }
        if (!this.value.attrs.papersizes.length) {
            this.value.attrs.papersizes = ['A4']
        }
        this.value.attrs.papersize = this.value.attrs.papersizes[0]
        if (!this.value.attrs.footnote_elements.length) {
            this.value.attrs.footnote_elements = ['paragraph']
        }
        if (!this.value.attrs.languages.length) {
            this.value.attrs.languages = ['en-US']
        }
        this.value.attrs.language = this.value.attrs.languages[0]

        this.definitionTextarea.value = JSON.stringify(this.value)
        this.definitionHashInput.value = templateHash(this.value)
        this.showErrors()
        return valid
    }

    showErrors() {
        this.definitionTextarea.parentElement.querySelector('ul.errorlist').innerHTML =
            Object.values(this.errors).map(error => `<li>${error}</li>`).join('')
    }

    setupInitialEditors() {
        Array.from(document.querySelectorAll('.to-container .doc-part:not(.fixed)')).forEach((el, index) => {
            const value = this.value.content[index+1], // offset by title
                help = value.attrs.help,
                initial = value.attrs.initial,
                type = value.type
            this.setupEditors(el, type, help, initial)
        })
    }

    setupEditors(el, type, help = false, initial = false) {
        const helpEl = el.querySelector('.instructions')
        if (!helpEl) {
            return
        }
        const helpDoc = help ?
                helpSchema.nodeFromJSON({type:'doc', content: help}) :
                helpSchema.nodes.doc.createAndFill(),
            helpView = new EditorView(helpEl, {
                state: EditorState.create({
                    doc: helpDoc,
                    plugins: [
                        buildInputRules(helpSchema),
                        keymap(buildKeymap(helpSchema)),
                        keymap(baseKeymap),
                        gapCursor(),
                        menuBar({
                            floating: false,
                            content: helpMenuContent
                        }),
                        history()
                    ]
                })
            })
        this.editors.push([helpEl, helpView])
        const plugins = [new Plugin({
            // Adding heading IDs to all new headings.
            appendTransaction: (trs, oldState, newState) => {
                if (trs.every(tr => !tr.steps.length)) {
                    return
                }
                const newHeadings = [],
                    usedHeadingIds = []

                this.editors.forEach(([_el, view]) => {
                    if (view.state === oldState) {
                        return
                    }
                    view.state.doc.descendants(node => {
                        if (node.type.groups.includes('heading')) {
                            usedHeadingIds.push(node.attrs.id)
                        }
                    })
                })
                newState.doc.descendants((node, pos) => {
                    if (node.type.groups.includes('heading')) {
                        if (node.attrs.id === false || usedHeadingIds.includes(node.attrs.id)) {
                            newHeadings.push({pos, node})
                        } else {
                            usedHeadingIds.push(node.attrs.id)
                        }

                    }
                })
                if (newHeadings.length) {
                    const newTr = newState.tr
                    newHeadings.forEach(
                        newHeading => {
                            let id
                            while (!id || usedHeadingIds.includes(id)) {
                                id = randomHeadingId()
                            }
                            usedHeadingIds.push(id)
                            newTr.setNodeMarkup(
                                newHeading.pos,
                                null,
                                Object.assign({}, newHeading.node.attrs, {id})
                            )
                        }
                    )
                    return newTr
                }
            }
        })]
        let menuContent = [], schema
        switch (type) {
            case 'richtext_part':
                schema = richtextPartSchema
                menuContent = richtextMenuContent
                break
            case 'table_part':
                schema = tablePartSchema
                menuContent = tableMenuContent
                plugins.push(tableEditing())
                break
            case 'heading_part':
                schema = headingPartSchema
                menuContent = headingMenuContent
                break
            case 'tags_part':
                schema = tagsPartSchema
                plugins.push(new Plugin({
                    props: {
                        nodeViews: {
                            tags_part: (node, view, getPos) => new TagsView(node, view, getPos)
                        }
                    }
                }))
                break
            case 'contributors_part':
                schema = contributorsPartSchema
                plugins.push(new Plugin({
                    props: {
                        nodeViews: {
                            contributors_part: (node, view, getPos) => new ContributorsView(node, view, getPos)
                        }
                    }
                }))
                break
            default:
                break
        }

        if (!schema) {
           return
        }
        plugins.unshift(
            buildInputRules(schema),
            keymap(buildKeymap(schema)),
            keymap(baseKeymap),
            gapCursor(),
            menuBar({
                floating: false,
                content: menuContent
            }),
            history()
        )
        const initialEl = el.querySelector('.initial'),
            doc = initial ?
                schema.nodeFromJSON({
                    type:'doc',
                    content: [{
                        type: type,
                        content: initial
                    }]
                }) :
                schema.nodes.doc.createAndFill(),
            initialView = new EditorView(initialEl, {
                state: EditorState.create({
                    doc,
                    plugins
                })
            })
        this.editors.push([initialEl, initialView])

    }

    getEditorValue(el, inline = false) {
        const editor = this.editors.find(editor => editor[0]===el)
        if (!editor) {
            return false
        }
        const state = editor[1].state
        // Only return if there is more content that a recently initiated doc
        // would have. The number varies between part types.
        if (state.doc.nodeSize > state.schema.nodes.doc.createAndFill().nodeSize) {
            return inline ? state.doc.firstChild.toJSON().content : state.doc.toJSON().content
        }
        return false
    }

    bind() {
        new Sortable(
            document.querySelector('.from-container'),
            {
                group: {
                    name: 'document',
                    pull: 'clone',
                    put: false
                },
                sort: false,
                handle: '.doc-part-header'
            }
        )
        new Sortable(
            document.querySelector('.to-container'),
            {
                group: {
                    name: 'document',
                    pull: true,
                    put: true
                },
                handle: '.doc-part-header',
                onAdd: event => {
                    this.setupEditors(
                        event.item,
                        event.item.dataset.type
                    )
                }
            }
        )
        new Sortable(
            document.querySelector('.trash'),
            {
                group: {
                    name: 'document',
                    put: true
                },
                handle: '.doc-part-header',
                onAdd: event => event.to.removeChild(event.to.firstElementChild) // Remove the item that was just added
            }
        )

        document.body.addEventListener('click', event => {
            const el = {}
            switch (true) {
                case findTarget(event, '#toggle-editor', el):
                    event.preventDefault()
                    if (this.definitionTextarea.style.display==='none') {
                        this.definitionTextarea.style.display=''
                        this.definitionHashInputBlock.style.display=''
                        this.templateEditor.style.display='none'
                        this.setCurrentValue()
                    } else {
                        this.definitionTextarea.style.display='none'
                        this.definitionHashInputBlock.style.display='none'
                        this.templateEditor.style.display=''
                        this.getInitialValue()
                        this.templateEditor.querySelector('.to-container').innerHTML =
                            templateEditorValueTemplate({content: this.value.content.slice(1) || []})
                        this.templateEditor.querySelector('.footnote-value').innerHTML =
                            footnoteTemplate(this.value.attrs)
                        this.templateEditor.querySelector('.languages-value').innerHTML =
                            languagesTemplate(this.value.attrs)
                        this.templateEditor.querySelector('.papersizes-value').innerHTML =
                            papersizesTemplate(this.value.attrs)
                        this.setupInitialEditors()
                    }
                    break
                case findTarget(event, 'div.submit-row input[type=submit]', el):
                    if (this.definitionTextarea.style.display==='none' && !this.setCurrentValue()) {
                        event.preventDefault()
                    }
                    break
                case findTarget(event, '.doc-part .configure', el):
                    event.preventDefault()
                    el.target.closest('.doc-part').querySelector('.attrs').classList.toggle('hidden')
                    break
                default:
                    break
            }
        })

        document.addEventListener('scroll', this.listeners.onScroll)
    }

    onScroll() {
        const fromContainer = this.templateEditor.querySelector('.from-container'),
            rect = fromContainer.getBoundingClientRect()

        if (rect.height + rect.top > 0) {
            const contentSize = 6 * 61, // 61px for each content type.
                maxPadding = rect.height - contentSize - 10 // 10px for padding bottom
            fromContainer.style.paddingTop = `${Math.min(8-Math.min(rect.top, 0), maxPadding)}px`
        }

    }
}
<|endoftext|>