<commit_before>import React from 'react';
import Dropzone from 'react-dropzone';
import FocusTrap from 'focus-trap-react';
import {Button, Icon, Image, Input, Modal, Divider, TextArea, Dropdown, Popup} from 'semantic-ui-react';
import uploadMediaFiles from '../../actions/media/uploadMediaFiles';
import { connectToStores, provideContext } from 'fluxible-addons-react';

class UploadMediaModal extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            openModal: false,
            activeTrap: false,
            active: true,
            files: [],
            license: false,
            licenseValue: 'CC0',
            copyrightHolder: '',
            alt: '',
            title: '',
            isLoading: false
        };

        this.handleOpen = this.handleOpen.bind(this);
        this.handleClose = this.handleClose.bind(this);
        this.unmountTrap = this.unmountTrap.bind(this);
        this.showLicense = this.showLicense.bind(this);
        this.submitPressed = this.submitPressed.bind(this);
    }

    handleChange(e) {
        this.setState({ [e.target.name]: e.target.value });
    }

    handleOpen(){
        $('#app').attr('aria-hidden','true');
        this.setState({
            modalOpen:true,
            activeTrap:true,
            isLoading: false
        });
    }

    handleClose(){
        $('#app').attr('aria-hidden','false');
        this.setState({
            modalOpen:false,
            activeTrap: false,
            files: [],
            license: false,
            licenseValue: 'CC0',
            copyrightHolder: '',
            alt: '',
            title: '',
            isLoading: false
        });
    }

    unmountTrap(){
        if(this.state.activeTrap){
            this.setState({ activeTrap: false });
            $('#app').attr('aria-hidden','false');
        }
    }

    showLicense() {
        this.setState({
            license: true
        });
    }

    onDrop(files) {
        this.setState({
            files
        });
    }

    changeLicense(event, data) {
        this.setState({
            licenseValue: data.value
        });
    }

    submitPressed(e) {
        e.preventDefault();
        let that = this;
        if(this.state.copyrightHolder === undefined || this.state.copyrightHolder === ''){this.state.copyrightHolder = this.props.userFullName;}
        console.log('copyrighthodler: ' + this.state.copyrightHolder);
        let payload = {
            type: this.state.files[0].type,
            license: this.state.licenseValue,
            copyrightHolder: this.state.copyrightHolder,
            title: this.state.title || this.state.files[0].name,
            text: this.state.alt,
            filesize: this.state.files[0].size,
            filename: this.state.files[0].name,
            bytes: null
        };
        console.log(this.state, payload);

        let reader = new FileReader();

        reader.onloadend = function (evt) {
            console.log('read total length from file: ', reader.result.length, evt.target.readyState);

            if (evt.target.readyState === FileReader.DONE) {
                payload.bytes = reader.result;
                that.context.executeAction(uploadMediaFiles, payload);

                that.setState({
                    isLoading: true
                });
            }
        };

        reader.onerror = (err) => {
            swal({
                title: 'Error',
                text: 'Reading the selected file failed. Check you privileges and try again.',
                type: 'error',
                confirmButtonText: 'Close',
                confirmButtonClass: 'negative ui button',
                allowEscapeKey: false,
                allowOutsideClick: false,
                buttonsStyling: false
            })
            .then(() => {
                return true;
            });
        };

        reader.readAsDataURL(this.state.files[0]);

        return false;
    }

    render() {
        this.context.getUser().username;
        let dropzone = '';
        if(this.state.files.length < 1){
            dropzone =

              <Dropzone onDrop={this.onDrop.bind(this)} accept="image/*" multiple={false} className="ui grey inverted center aligned padded raised segment">
                <Icon name="cloud upload" size="massive"/>

Try drop a file here, or click to select a file to upload.

              </Dropzone>

;
        } else { //TODO Implement a switch-case statement for other media files. Currently only works for images.
            dropzone =

              <Dropzone onDrop={this.onDrop.bind(this)} accept="image/*" multiple={false} className="ui secondary clearing segment">
                <Image src={this.state.files[0].preview} size="large" centered={true}/>
              </Dropzone>

Not the right image? Click on the image to upload another one.

;
        }
        //let heading = 'Upload a media file';
        let heading = 'Add image - upload image file from your computer';
        let content =

          <TextArea className="sr-only" id="UploadMediaModalDescription" value="This modal is used to upload media files and to provide additional information about these." />
          {dropzone}

;
        let saveHandler= this.showLicense;
        let licenseBoxes = '';
        let submitButtonText = 'Next';
        let submitButtonIcon = 'arrow right';
        if(this.state.license){
            heading = 'License information';
            licenseBoxes = (this.state.licenseValue !== 'CC0') ?
Image created by/ attributed to:<Input ref="copyrightHolder" name="copyrightHolder" onChange={this.handleChange.bind(this)} required defaultValue={this.props.userFullName}/>
 : '';
            content =

              <Image src={this.state.files[0].preview} size="large" centered={true}/>
              <Divider/>
              <form className="ui form" onSubmit={this.submitPressed.bind(this)}>

                  Title:
                  <Input defaultValue={this.state.files[0].name} ref="mediaTitle" name="title" onChange={this.handleChange.bind(this)} required/>

                  Description/Alt Text:
                  <Popup trigger={<input ref="mediaAltText" id="UploadMediaModal_input_mediaAltText" name="alt" onChange={this.handleChange.bind(this)} required/>} content='What does the picture mean?' position='top center'/>

                  Choose a license:
                  <Dropdown selection options={[{text: 'CC0 Public Domain', value: 'CC0'},{text: 'CC-BY Creative Commons Attribution 4.0', value: 'CC BY 4.0'},{text: 'CC-BY-SA Creative Common Attribution Share-Alike 4.0', value: 'CC BY SA 4.0'}]} defaultValue='CC0' onChange={this.changeLicense.bind(this)} ref="mediaLicense" required/>

                {licenseBoxes}

                    <input type="checkbox" required/>
                    I confirm that I have the rights to upload this image as per the SlideWiki terms and conditions and that the license information I have provided is correct.{/*TODO Add a link to the slidewiki terms/cond site, currently not exising*/}

                <Button type='submit' id="UploadFormSubmitButton" style={{display: 'none'}}>Submit</Button> {/*black magic hack to trigger the form from the outside*/}
              </form>

;
            saveHandler = (() => {$('#UploadFormSubmitButton').click();});
            submitButtonText = 'Upload';
            submitButtonIcon = 'upload';
        }

        const buttonColorBlack = {
            color: 'black'
        };

        return (
          <Modal trigger={
                  <Button className="ui orange button" tabIndex='0' id="ChangePictureModalOpenButton" aria-hidden={this.state.modalOpen} onClick={this.handleOpen} value="">

                    Add Image
                  </Button>
                 }
              open={this.state.modalOpen}
              onClose={this.handleClose}
              size="small"
              role="dialog"
              id="UploadMediaModal"
              aria-labelledby="UploadMediaModalHeader"
              aria-describedby="UploadMediaModalDescription"
              tabIndex="0">
              <FocusTrap
                  id='focus-trap-UploadMediaModal'
                  className = "header"
                  active={this.state.activeTrap}
                  focusTrapOptions={{
                      onDeactivate: this.unmountTrap,
                      clickOutsideDeactivates: false,
                      initialFocus: '#UploadMediaModalSaveButton',
                  }}>
                  <Modal.Header className="ui left aligned" as="h1" id="UploadMediaModalHeader">
                      {heading}
                  </Modal.Header>
                  <Modal.Content>
                    <Divider/>
                    {content}
                    {(this.state.isLoading === true) ?

Loading

 : ''}
                    <Divider />
                    <Modal.Actions className="ui center aligned" as="div" style={{'textAlign': 'right'}}>
                      <Button color='red' tabIndex="0" type="button" aria-label="Cancel" onClick={this.handleClose} icon="minus circle" labelPosition='left' content="Cancel"/>
                      <Button id="UploadMediaModalSaveButton" color="green" tabIndex="0" type="button" aria-label="Upload" onClick={saveHandler} icon={submitButtonIcon} labelPosition='left' content={submitButtonText}/>
                    </Modal.Actions>
                  </Modal.Content>
              </FocusTrap>
          </Modal>
        );
    }
}

UploadMediaModal.contextTypes = {
    executeAction: React.PropTypes.func.isRequired,
    getUser: React.PropTypes.func
};

export default UploadMediaModal;
<commit_msg>Implemented some accessibility improvements<commit_after>import React from 'react';
import Dropzone from 'react-dropzone';
import FocusTrap from 'focus-trap-react';
import {Button, Icon, Image, Input, Modal, Divider, TextArea, Dropdown, Popup} from 'semantic-ui-react';
import uploadMediaFiles from '../../actions/media/uploadMediaFiles';
import { connectToStores, provideContext } from 'fluxible-addons-react';

class UploadMediaModal extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            openModal: false,
            activeTrap: false,
            active: true,
            files: [],
            license: false,
            licenseValue: 'CC0',
            copyrightHolder: '',
            alt: '',
            title: '',
            isLoading: false
        };

        this.handleOpen = this.handleOpen.bind(this);
        this.handleClose = this.handleClose.bind(this);
        this.unmountTrap = this.unmountTrap.bind(this);
        this.showLicense = this.showLicense.bind(this);
        this.submitPressed = this.submitPressed.bind(this);
    }

    handleChange(e) {
        this.setState({ [e.target.name]: e.target.value });
    }

    handleOpen(){
        $('#app').attr('aria-hidden','true');
        this.setState({
            modalOpen:true,
            activeTrap:true,
            isLoading: false
        });
    }

    handleClose(){
        $('#app').attr('aria-hidden','false');
        this.setState({
            modalOpen:false,
            activeTrap: false,
            files: [],
            license: false,
            licenseValue: 'CC0',
            copyrightHolder: '',
            alt: '',
            title: '',
            isLoading: false
        });
    }

    unmountTrap(){
        if(this.state.activeTrap){
            this.setState({ activeTrap: false });
            $('#app').attr('aria-hidden','false');
        }
    }

    showLicense() {
        this.setState({
            license: true
        });
    }

    onDrop(files) {
        this.setState({
            files
        });
    }

    changeLicense(event, data) {
        this.setState({
            licenseValue: data.value
        });
    }

    submitPressed(e) {
        e.preventDefault();
        let that = this;
        if(this.state.copyrightHolder === undefined || this.state.copyrightHolder === ''){this.state.copyrightHolder = this.props.userFullName;}
        console.log('copyrighthodler: ' + this.state.copyrightHolder);
        let payload = {
            type: this.state.files[0].type,
            license: this.state.licenseValue,
            copyrightHolder: this.state.copyrightHolder,
            title: this.state.title || this.state.files[0].name,
            text: this.state.alt,
            filesize: this.state.files[0].size,
            filename: this.state.files[0].name,
            bytes: null
        };
        console.log(this.state, payload);

        let reader = new FileReader();

        reader.onloadend = function (evt) {
            console.log('read total length from file: ', reader.result.length, evt.target.readyState);

            if (evt.target.readyState === FileReader.DONE) {
                payload.bytes = reader.result;
                that.context.executeAction(uploadMediaFiles, payload);

                that.setState({
                    isLoading: true
                });
            }
        };

        reader.onerror = (err) => {
            swal({
                title: 'Error',
                text: 'Reading the selected file failed. Check you privileges and try again.',
                type: 'error',
                confirmButtonText: 'Close',
                confirmButtonClass: 'negative ui button',
                allowEscapeKey: false,
                allowOutsideClick: false,
                buttonsStyling: false
            })
            .then(() => {
                return true;
            });
        };

        reader.readAsDataURL(this.state.files[0]);

        return false;
    }

    render() {
        this.context.getUser().username;
        let dropzone = '';
        if(this.state.files.length < 1){
            dropzone =

              <Dropzone onDrop={this.onDrop.bind(this)} accept="image/*" multiple={false} className="ui grey inverted center aligned padded raised segment">
                <Icon name="cloud upload" size="massive"/>

Try drop a file here, or click to select a file to upload.

              </Dropzone>

;
        } else { //TODO Implement a switch-case statement for other media files. Currently only works for images.
            dropzone =

              <Dropzone onDrop={this.onDrop.bind(this)} accept="image/*" multiple={false} className="ui secondary clearing segment">
                <Image src={this.state.files[0].preview} size="large" centered={true}/>
              </Dropzone>

Not the right image? Click on the image to upload another one.

;
        }
        //let heading = 'Upload a media file';
        let heading = 'Add image - upload image file from your computer';
        let content =

          <TextArea className="sr-only" id="UploadMediaModalDescription" value="This modal is used to upload media files and to provide additional information about these." />
          {dropzone}

;
        let saveHandler= this.showLicense;
        let licenseBoxes = '';
        let submitButtonText = 'Next';
        let submitButtonIcon = 'arrow right';
        if(this.state.license){
            heading = 'License information';
            licenseBoxes = (this.state.licenseValue !== 'CC0') ?
Image created by/ attributed to:<Input id="copyrightHolder" ref="copyrightHolder" name="copyrightHolder" onChange={this.handleChange.bind(this)} aria-label="Copyrightholder" aria-required="true" required defaultValue={this.props.userFullName}/>
 : '';
            content =

              <Image src={this.state.files[0].preview} size="large" centered={true}/>
              <Divider/>
              <form className="ui form" onSubmit={this.submitPressed.bind(this)}>

                  Title:
                  <Input defaultValue={this.state.files[0].name} id="mediaTitle" ref="mediaTitle" name="title" onChange={this.handleChange.bind(this)} aria-label="Title of the image" aria-required="true"required/>

                  Description/Alt Text:
                  <Popup trigger={<input id="mediaAltText" ref="mediaAltText" id="UploadMediaModal_input_mediaAltText" name="alt" onChange={this.handleChange.bind(this)} aria-label="Description of the image" aria-required="true" required/>} content='What does the picture mean?' position='top center'/>

                  Choose a license:
                  <Dropdown id="mediaLicense" selection options={[{text: 'CC0 Public Domain', value: 'CC0'},{text: 'CC-BY Creative Commons Attribution 4.0', value: 'CC BY 4.0'},{text: 'CC-BY-SA Creative Common Attribution Share-Alike 4.0', value: 'CC BY SA 4.0'}]} defaultValue='CC0' onChange={this.changeLicense.bind(this)} ref="mediaLicense" aria-label="Select a license" aria-required="true" required/>

                {licenseBoxes}

                    <input id="terms" type="checkbox" aria-label="Agree to terms and conditions" aria-required="true" required/>
                    I confirm that I have the rights to upload this image as per the SlideWiki terms and conditions and that the license information I have provided is correct.{/*TODO Add a link to the slidewiki terms/cond site, currently not exising*/}

                <Button type='submit' id="UploadFormSubmitButton" style={{display: 'none'}}>Submit</Button> {/*black magic hack to trigger the form from the outside*/}
              </form>

;
            saveHandler = (() => {$('#UploadFormSubmitButton').click();});
            submitButtonText = 'Upload';
            submitButtonIcon = 'upload';
        }

        const buttonColorBlack = {
            color: 'black'
        };

        return (
          <Modal trigger={
                  <Button className="ui orange button" tabIndex='0' id="ChangePictureModalOpenButton" aria-hidden={this.state.modalOpen} onClick={this.handleOpen} value="">

                    Add Image
                  </Button>
                 }
              open={this.state.modalOpen}
              onClose={this.handleClose}
              size="small"
              role="dialog"
              id="UploadMediaModal"
              aria-labelledby="UploadMediaModalHeader"
              aria-describedby="UploadMediaModalDescription"
              tabIndex="0">
              <FocusTrap
                  id='focus-trap-UploadMediaModal'
                  className = "header"
                  active={this.state.activeTrap}
                  focusTrapOptions={{
                      onDeactivate: this.unmountTrap,
                      clickOutsideDeactivates: false,
                      initialFocus: '#UploadMediaModalSaveButton',
                  }}>
                  <Modal.Header className="ui left aligned" as="h1" id="UploadMediaModalHeader">
                      {heading}
                  </Modal.Header>
                  <Modal.Content>
                    <Divider/>
                    {content}
                    {(this.state.isLoading === true) ?

Loading

 : ''}
                    <Divider />
                    <Modal.Actions className="ui center aligned" as="div" style={{'textAlign': 'right'}}>
                      <Button color='red' tabIndex="0" type="button" aria-label="Cancel" onClick={this.handleClose} icon="minus circle" labelPosition='left' content="Cancel"/>
                      <Button id="UploadMediaModalSaveButton" color="green" tabIndex="0" type="button" aria-label="Upload" onClick={saveHandler} icon={submitButtonIcon} labelPosition='left' content={submitButtonText}/>
                    </Modal.Actions>
                  </Modal.Content>
              </FocusTrap>
          </Modal>
        );
    }
}

UploadMediaModal.contextTypes = {
    executeAction: React.PropTypes.func.isRequired,
    getUser: React.PropTypes.func
};

export default UploadMediaModal;
<|endoftext|>


<commit_before>/*jslint node: true */
"use strict";

var utils = require("../utils");
var log = require("npmlog");

module.exports = function(mergeWithDefaults, api, ctx) {
  return function deleteMessage(messageOrMessages, callback) {
    var message_id = message.message_id || message;

    var form = mergeWithDefaults();
    form['client'] = "mercury";

    if(Array.isArray(messageOrMessages)) {
      for (var i = 0; i < messageOrMessages.length; i++) {
        form['message_ids['+i+']'] = messageOrMessages[i];
      }
    } else {
      form['message_ids[0]'] = messageOrMessages;
    }

    utils.post("https://www.facebook.com/ajax/mercury/delete_messages.php", ctx.jar, form)
      .then(utils.parseResponse)
      .then(function(resData) {
        if (resData.error) {
          callback(resData);
        } else callback(null);
      })
      .catch(function(err) {
        log.error("Error in deleteMessage", err);
        return callback(err);
      });
    return ctx.access_token;
  };
};
<commit_msg>Fix support for message objects<commit_after>/*jslint node: true */
"use strict";

var utils = require("../utils");
var log = require("npmlog");

module.exports = function(mergeWithDefaults, api, ctx) {
  return function deleteMessage(messageOrMessages, callback) {
    // this allows also passing a message object
    var message_id = messageOrMessages.message_id || null;
    if(message_id != null) {
      messageOrMessages = message_id;
    }

    var form = mergeWithDefaults();
    form['client'] = "mercury";

    if(Array.isArray(messageOrMessages)) {
      for (var i = 0; i < messageOrMessages.length; i++) {
        form['message_ids['+i+']'] = messageOrMessages[i];
      }
    } else {
      form['message_ids[0]'] = messageOrMessages;
    }

    utils.post("https://www.facebook.com/ajax/mercury/delete_messages.php", ctx.jar, form)
      .then(utils.parseResponse)
      .then(function(resData) {
        if (resData.error) {
          callback(resData);
        } else callback(null);
      })
      .catch(function(err) {
        log.error("Error in deleteMessage", err);
        return callback(err);
      });
    return ctx.access_token;
  };
};
<|endoftext|>


<commit_before>var timestamp = require('monotonic-timestamp')
var pull      = require('pull-stream')
var ClassicStream
              = require('pull-stream-to-stream')
var window    = require('pull-window')
var pl        = require('pull-level')
var merge     = require('pull-stream-merge')
var cat       = require('pull-cat')

function each (obj, iter) {
  for(var key in obj) {
    iter(obj[key], key, obj)
  }
}

function map (obj, iter) {
  var a = []
  for(var k in obj)
    a.push(iter(obj[k], k, obj))
  return a
}

function find (ary, test) {
  for(var i in ary)
    if(test(ary[i], i, ary)) return ary[i]
}

//function filterReverse (ary, test) {
//  var l = ary.length, a = []
//  while(l--)
//    if(test(ary[l], l, ary)) a.unshift(ary[l])
//
//  return a
//}

function flatten (ary) {
  return ary.reduce(function (ary, _ary) {
    return ary.concat(_ary)
  }, [])
}

function cmp (a, b) {
  return a === b ? 0 : a < b ? -1 : 1
}

function comparator(a, b) {
  return cmp(a.ts, b.ts) || cmp(a.id, b.id)
}

function prep () {
  return pull.map(function (data) {
    var key = data.key.split('\x00')
    return {
      key: data.value,
      value: null,
      type: null,
      id: key.shift(),
      ts: key.pop()
    }
  })
}

function pCont (continuable) {
  var n = -1
  return function (abort, cb) {
    if(abort) return cb(true)
    if(++n)   return cb(n === 1 ? true : new Error('only call once'))
    continuable(cb)
  }
}

module.exports = function (serialize) {

return function (db, masterDb, id) {

  var clock = {} //remember latest version from each dep.

  masterDb = masterDb || 'master'
  if('string' === typeof masterDb)
    masterDb = db.sublevel(masterDb)
  var clockDb = masterDb.sublevel('clock')

  //on insert, remember which keys where updated when.
  db.pre(function (op, add, batch) {
    if(!find(batch, function (_op) {
        return _op.value === op.key && _op.prefix === masterDb
      })
    ) {
      var ts = timestamp()
      add({key: id+'\x00'+ts, value: op.key, type: 'put', prefix: masterDb})
      add({key: id, value: ''+ts, type: 'put', prefix: clockDb})
    }
  })

  //cleanup old records.
  //run this every so often if you have lots of overwrites.

  //go through the logs in reverse,
  //get any values that have been over written,
  //group into batches, so it's more efficient communication with db,
  //and delete each set.

  masterDb.cleanup = function (cb) {
    pull(
      pl.read(db, {reverse: true}),
        pull.nonUnique(function (d) {
        return d.value.toString()
      }),
      pull.map(function () {
        return {key: d.key, type: 'del'}
      }),
      pl.write(db, cb)
    )
  }

  masterDb.createStream = function (opts) {
    var defer = pull.defer()
    var cs = ClassicStream(function (read) {
        read(null, function (err, data) {
          defer.resolve(masterDb.createMasterStream({
            clock: data,
            tail: opts && opts.tail
          }))
          pull(read, masterDb.createSlaveStream())
        })
      }, cat([pCont(masterDb.clock), defer]))

  return serialize(cs)
  }

  masterDb.createMasterStream = pull.Source(function (opts, onAbort) {
    opts = opts || {}
    opts.clock = opts.clock || {}
    var since = opts.min || opts.since || 0

    function rest (nClock) {
      return pull(
        cat([
          merge(map(nClock, function (value, key) {
            return {min: key + '\x00' + value, max: key+'\x00\xff', tail: false}
          }).map(function (opts) {
            //can remove this once level gets exclusive ranges!
            return pull(pl.read(masterDb, opts), prep())
          }), comparator),
          (opts.tail ? pull(pl.live(masterDb), prep()) : pull.empty())
        ]),
        pull.filter(function (data) {
          var c = nClock[data.id]
          if(!data.key) return false
          if(!c || c < data.ts) {
            nClock[data.id] = data.ts
            return true
          }
          return false
        }),
        //lookup actual values
        pull.asyncMap(function (data, cb) {
          db.get(data.key, function (err, value) {
            data.value = value
            data.type = err ? 'del' : 'put'
            cb(null, data)
          })
        }),
        function (read) {
          if(!onAbort) return read
          return function (abort, cb) {
            if(abort)
              onAbort(abort === true ? null : abort)

            read(abort, function (err, data) {
              if(err) onAbort(err === true ? null : err)
              cb(err, data)
            })
          }
        }
      )
    }

    var read
    return function (abort, cb) {
      if(!read)
        masterDb.clock(function (err, clock) {
          if(err) return cb(err)

          var nClock = {}
          each(clock, function (_, key) {
            nClock[key] = 0
          })

          each(opts.clock, function (value, key) {
            if(nClock[key] < value)
              nClock[key] = value
          })

          ;(read = rest(nClock))(abort, cb)
        })
      else
        read(abort, cb)
    }
  })

  masterDb.clock = function (cb) {
    pull(
      pl.read(clockDb),
      pull.reduce(function (clock, item) {

        clock[item.key] = item.value
        return clock
      }, {}, cb)
    )
  }

  //TODO: defer opening database until this is loaded!
  //this requires a patch to level!
  masterDb.clock(function (err, _clock) {
    each(clock, function (ts, id) {
      if(clock[id] < ts)
        clock[id] = ts
    })
  })

  clockDb.post(function (op) {
    if(clock[op.key] < op.value)
      clock[op.key] = op.value
  })

  //the writable side of the replication stream.

  masterDb.createSlaveStream = function (opts, done) {
    if('function' === typeof opts) {
      done = opts; opts = null
    }

    return pull(
      pull.map(function (op) {
        if(clock[op.id] > op.ts) return
        return [
          op,
          {key: op.id+'\x00'+op.ts, value: op.key, type: 'put', prefix: masterDb},
          {key: op.id, value: op.ts, type: 'put', prefix: clockDb}
        ]
      }),
      pull.filter(Boolean),
  //    .pipe(pull.map(function (batch) {
  //
  //      var seen = {}
  //      //make sure there is only one clock update per batch
  //      batch = filterReverse(batch, function (op) {
  //        if(op.prefix !== clockDb)  return true
  //        else if (!seen[op.key])    return seen[op.key] = true
  //        return false
  //      })
  //      return batch
  //    }))
      pull.asyncMap(function (batch, cb) {
        db.batch(batch, function (err) {
          cb(err, !!batch)
        })
      }),
      function (read) {
        read(null, function next(end, data) {
          if(end) done && done()
          else    read(null, next)
        })
      }
    )
    //currently not sure why this isn't working...
    //.pipe(pull.drain(null, done))
  }

  return masterDb
}

}
<commit_msg>update to latest level-sublevel<commit_after>var timestamp = require('monotonic-timestamp')
var pull      = require('pull-stream')
var ClassicStream
              = require('pull-stream-to-stream')
var window    = require('pull-window')
var pl        = require('pull-level')
var merge     = require('pull-stream-merge')
var cat       = require('pull-cat')

function each (obj, iter) {
  for(var key in obj) {
    iter(obj[key], key, obj)
  }
}

function map (obj, iter) {
  var a = []
  for(var k in obj)
    a.push(iter(obj[k], k, obj))
  return a
}

function find (ary, test) {
  for(var i in ary)
    if(test(ary[i], i, ary)) return ary[i]
}

//function filterReverse (ary, test) {
//  var l = ary.length, a = []
//  while(l--)
//    if(test(ary[l], l, ary)) a.unshift(ary[l])
//
//  return a
//}

function flatten (ary) {
  return ary.reduce(function (ary, _ary) {
    return ary.concat(_ary)
  }, [])
}

function cmp (a, b) {
  return a === b ? 0 : a < b ? -1 : 1
}

function comparator(a, b) {
  return cmp(a.ts, b.ts) || cmp(a.id, b.id)
}

function prep () {
  return pull.map(function (data) {
    var key = data.key.split('\x00')
    return {
      key: data.value,
      value: null,
      type: null,
      id: key.shift(),
      ts: key.pop()
    }
  })
}

function pCont (continuable) {
  var n = -1
  return function (abort, cb) {
    if(abort) return cb(true)
    if(++n)   return cb(n === 1 ? true : new Error('only call once'))
    continuable(cb)
  }
}

module.exports = function (serialize) {

return function (db, masterDb, id) {

  var clock = {} //remember latest version from each dep.

  masterDb = masterDb || 'master'
  if('string' === typeof masterDb)
    masterDb = db.sublevel(masterDb)
  var clockDb = masterDb.sublevel('clock')

  //on insert, remember which keys where updated when.
  db.pre(function (op, add, batch) {
    if(!find(batch, function (_op) {
        return _op.value === op.key && _op.prefix === masterDb
      })
    ) {
      var ts = timestamp()
      add({key: id+'\x00'+ts, value: op.key, type: 'put', prefix: masterDb,
        valueEncoding: 'utf8', keyEncoding: 'utf8'
      })
      add({key: id, value: ''+ts, type: 'put', prefix: clockDb,
        valueEncoding: 'utf8', keyEncoding: 'utf8'
      })
    }
  })

  //cleanup old records.
  //run this every so often if you have lots of overwrites.

  //go through the logs in reverse,
  //get any values that have been over written,
  //group into batches, so it's more efficient communication with db,
  //and delete each set.

  masterDb.cleanup = function (cb) {
    pull(
      pl.read(db, {reverse: true}),
        pull.nonUnique(function (d) {
        return d.value.toString()
      }),
      pull.map(function () {
        return {key: d.key, type: 'del'}
      }),
      pl.write(db, cb)
    )
  }

  masterDb.createStream = function (opts) {
    var defer = pull.defer()
    var cs = ClassicStream(function (read) {
        read(null, function (err, data) {
          defer.resolve(masterDb.createMasterStream({
            clock: data,
            tail: opts && opts.tail
          }))
          pull(read, masterDb.createSlaveStream())
        })
      }, cat([pCont(masterDb.clock), defer]))

  return serialize(cs)
  }

  masterDb.createMasterStream = pull.Source(function (opts, onAbort) {
    opts = opts || {}
    opts.clock = opts.clock || {}
    var since = opts.min || opts.since || 0

    function rest (nClock) {
      return pull(
        cat([
          merge(map(nClock, function (value, key) {
            return {min: key + '\x00' + value, max: key+'\x00\xff', tail: false}
          }).map(function (opts) {
            //can remove this once level gets exclusive ranges!
            return pull(pl.read(masterDb, opts), prep())
          }), comparator),
          (opts.tail ? pull(pl.live(masterDb), prep()) : pull.empty())
        ]),
        pull.filter(function (data) {
          var c = nClock[data.id]
          if(!data.key) return false
          if(!c || c < data.ts) {
            nClock[data.id] = data.ts
            return true
          }
          return false
        }),
        //lookup actual values
        pull.asyncMap(function (data, cb) {
          db.get(data.key, function (err, value) {
            data.value = value
            data.type = err ? 'del' : 'put'
            cb(null, data)
          })
        }),
        function (read) {
          if(!onAbort) return read
          return function (abort, cb) {
            if(abort)
              onAbort(abort === true ? null : abort)

            read(abort, function (err, data) {
              if(err) onAbort(err === true ? null : err)
              cb(err, data)
            })
          }
        }
      )
    }

    var read
    return function (abort, cb) {
      if(!read)
        masterDb.clock(function (err, clock) {
          if(err) return cb(err)

          var nClock = {}
          each(clock, function (_, key) {
            nClock[key] = 0
          })

          each(opts.clock, function (value, key) {
            if(nClock[key] < value)
              nClock[key] = value
          })

          ;(read = rest(nClock))(abort, cb)
        })
      else
        read(abort, cb)
    }
  })

  masterDb.clock = function (cb) {
    pull(
      pl.read(clockDb),
      pull.reduce(function (clock, item) {

        clock[item.key] = item.value
        return clock
      }, {}, cb)
    )
  }

  //TODO: defer opening database until this is loaded!
  //this requires a patch to level!
  masterDb.clock(function (err, _clock) {
    each(clock, function (ts, id) {
      if(clock[id] < ts)
        clock[id] = ts
    })
  })

  clockDb.post(function (op) {
    if(clock[op.key] < op.value)
      clock[op.key] = op.value
  })

  //the writable side of the replication stream.

  masterDb.createSlaveStream = function (opts, done) {
    if('function' === typeof opts) {
      done = opts; opts = null
    }

    return pull(
      pull.map(function (op) {
        if(clock[op.id] > op.ts) return
        return [
          op,
          {key: op.id+'\x00'+op.ts, value: op.key, type: 'put', prefix: masterDb},
          {key: op.id, value: op.ts, type: 'put', prefix: clockDb}
        ]
      }),
      pull.filter(Boolean),
  //    .pipe(pull.map(function (batch) {
  //
  //      var seen = {}
  //      //make sure there is only one clock update per batch
  //      batch = filterReverse(batch, function (op) {
  //        if(op.prefix !== clockDb)  return true
  //        else if (!seen[op.key])    return seen[op.key] = true
  //        return false
  //      })
  //      return batch
  //    }))
      pull.asyncMap(function (batch, cb) {
        db.batch(batch, function (err) {
          cb(err, !!batch)
        })
      }),
      function (read) {
        read(null, function next(end, data) {
          if(end) done && done()
          else    read(null, next)
        })
      }
    )
    //currently not sure why this isn't working...
    //.pipe(pull.drain(null, done))
  }

  return masterDb
}

}
<|endoftext|>


<commit_before>var baseUrl = "http://192.168.0.93/op";
var basePath = "/console";
<commit_msg>set basePath<commit_after>var baseUrl = "http://192.168.0.93/op";
var basePath = "";
<|endoftext|>


<commit_before>/*globals $*/
(function () {
'use strict';

var baseURL = 'http://www.wikidata.org';

function getConfigObject (contentModel, id) {
    var prefix;
    switch (contentModel) {
        case 'item':
            prefix = 'Q';
            break;
        case 'property':
            prefix = 'Property:P';
            break;
        case 'languages':
            return {format:'json', action:'query',
                            meta:'siteinfo', siprop:'languages'
            };
        case 'search':
            return {format:'json', action:'query',
                            list: 'search', srsearch: id, srlimit: 10};
    }
    return {format:'json', action:'query',
                    prop:'revisions', rvlimit:1, meta:'siteinfo', rvprop:'content', titles: prefix + id};
}

function getEntityChildren (contentModel, id, cb) {
    var deferred = $.Deferred();
    id = (String(id)).replace(/^(Q|Property:P)/i, '');
    $.getJSON(baseURL + '/w/api.php?callback=?', getConfigObject(contentModel, id), function (data) {
        var pages = data.query.pages,
            latestRevision = Object.keys(pages)[0],
            json = JSON.parse(pages[latestRevision].revisions[0]['*']),
            claims = json.claims, // Always an empty array if a property?
            label = json.label[$('#languages').val()];

        if (cb) {
            cb(label, claims);
        }
        else {
            deferred.resolve([label, claims, id]);
        }
    });
    return deferred;
}

function processClaims (claims, ul) {
    var deferreds = $.map(claims, function (claim) {
        if (!claim.m || typeof claim.m !== 'object' || claim.m[0] !== 'value') { // Other possibilities to handle?
            return;
        }
        var dfr,
            propID = claim.m[1],
            valueType = claim.m[2],
            innerValue = claim.m[3];

        switch (valueType) {
            case 'string':
                // The following are mentioned in the API or extension source code, but not clear on whether active,
                //   so commenting out until chance to confirm
                // case 'geocoordinate':  case 'bool': case 'float': case 'int': case 'null': case 'title':
                // case 'quantity': case 'monolingualtext': case 'multilingualtext': case 'time':
                dfr = getEntityChildren('property', propID, function (prop) {
                    dfr.resolve([prop, innerValue, null, propID, null, propID === 373 ? 'https://commons.wikimedia.org/wiki/Category:' : null]);
                });
                return dfr;
            case 'wikibase-entityid':
                dfr = getEntityChildren('property', propID, function (prop) {
                    var numericID = innerValue['numeric-id'];
                    getEntityChildren('item', numericID, function (innerValue, claims) {
                        dfr.resolve([prop, innerValue, claims, propID, numericID]);
                    });
                });
                return dfr;
            default:
                throw 'Unexpected value type: ' + valueType;
        }
    });
    $.when.apply($.when, deferreds).done(function () {
        $.makeArray(arguments).sort(function (a, b) {
            return (a[0] === b[0]) ? // If properties are equal, we sort by values
                (a[1] > b[1] ? 1 : -1) :
                (a[0] > b[0] ? 1 : -1);
        }).forEach(function (data) {
            var prop = data[0], val = data[1], claims = data[2], propID = data[3], numericID = data[4], commons = data[5],
                li = $('
').appendTo(ul).html('' + prop + '' + ': ' + (numericID ? val + ' (' + numericID + ')' : (commons ? '' + val + '' : val))),
                newUl = $('
').appendTo(li);
            li.click(function (e) {
                e.stopPropagation();
                if (!newUl.children().length && claims) {
                    processClaims(claims, newUl);
                }
            });
        });
    });
}

function initialReturn (data) {
    var label = data[0],
        claims = data[1],
        id = data[2],
        li = $('
').appendTo($('#entities')).html(label + ' ('+id+')'),
        ul = $('
').appendTo(li);
    li.click(function (e) {
        e.stopPropagation();
        if (!ul.children().length) {
            processClaims(claims, ul);
        }
    });
}

function doSearch (name) {
    $.getJSON(baseURL + '/w/api.php?callback=?', getConfigObject('search', name), function (data) {
        var results = data.query.search,
            deferreds = $.map(results, function (result) {
                return getEntityChildren('item', result.title);
            });
        $.when.apply($.when, deferreds).done(function () {
            $.makeArray(arguments).sort(function (a, b) {
                return a[0] === undefined ? 1 : // Undefined apparently trips up Firefox into thinking the array is finished
                    (a[0] === b[0]) ? // If properties are equal, we sort by values
                    (a[1] > b[1] ? 1 : -1) :
                    (a[0] > b[0] ? 1 : -1);
            }).forEach(initialReturn);
        });
    });
}

$(function () {

    // INITIATE AND ATTACH EVENTS
    ini_set('phpjs.parse_url.mode', 'strict');
    var purl = parse_url(window.location.href),
        qk = purl.queryKey,
        langValue = qk.language || 'en';

    if (qk.article) {
        $('#startArticle').val(decodeURIComponent(qk.article));
    }

    doSearch($('#startArticle').val());
    $('#startArticle').change(function (e) {
        // TODO: Implement http_build_url.js for php.js to rebuild URL and redirect for boomarkability
        // delete purl.query;
        // delete purl.queryKey;

        doSearch(e.target.value);
    });

    /*
    getEntityChildren('item', $('#start').val()).done(initialReturn);
    $('#start').change(function (e) {
        getEntityChildren('item', e.target.value).done(initialReturn);
    });
    */

    $('#empty').click(function empty () {
        $('#entities').empty();
    });

    // LANGUAGES
    $.getJSON(baseURL + '/w/api.php?callback=?', getConfigObject('languages'), function (data) {
        var options = $.map(data.query.languages, function (data) {
            return '<option value="'+ data.code +'">' + data['*'] + '</option>';
        }).join('').replace('"' + langValue + '"', '"' + langValue + '" selected');
        $('#languages').append(options);
    });
});

}());
<commit_msg>+todos in comments<commit_after>/*globals $*/

/*
TODOS:
1) Cause changes in article of language to force reload so as to be bookmarkable (then deal with empty)
2) Support bi-directional languages
*/

(function () {
'use strict';

var baseURL = 'http://www.wikidata.org';

function getConfigObject (contentModel, id) {
    var prefix;
    switch (contentModel) {
        case 'item':
            prefix = 'Q';
            break;
        case 'property':
            prefix = 'Property:P';
            break;
        case 'languages':
            return {format:'json', action:'query',
                            meta:'siteinfo', siprop:'languages'
            };
        case 'search':
            return {format:'json', action:'query',
                            list: 'search', srsearch: id, srlimit: 10};
    }
    return {format:'json', action:'query',
                    prop:'revisions', rvlimit:1, meta:'siteinfo', rvprop:'content', titles: prefix + id};
}

function getEntityChildren (contentModel, id, cb) {
    var deferred = $.Deferred();
    id = (String(id)).replace(/^(Q|Property:P)/i, '');
    $.getJSON(baseURL + '/w/api.php?callback=?', getConfigObject(contentModel, id), function (data) {
        var pages = data.query.pages,
            latestRevision = Object.keys(pages)[0],
            json = JSON.parse(pages[latestRevision].revisions[0]['*']),
            claims = json.claims, // Always an empty array if a property?
            label = json.label[$('#languages').val()];

        if (cb) {
            cb(label, claims);
        }
        else {
            deferred.resolve([label, claims, id]);
        }
    });
    return deferred;
}

function processClaims (claims, ul) {
    var deferreds = $.map(claims, function (claim) {
        if (!claim.m || typeof claim.m !== 'object' || claim.m[0] !== 'value') { // Other possibilities to handle?
            return;
        }
        var dfr,
            propID = claim.m[1],
            valueType = claim.m[2],
            innerValue = claim.m[3];

        switch (valueType) {
            case 'string':
                // The following are mentioned in the API or extension source code, but not clear on whether active,
                //   so commenting out until chance to confirm
                // case 'geocoordinate':  case 'bool': case 'float': case 'int': case 'null': case 'title':
                // case 'quantity': case 'monolingualtext': case 'multilingualtext': case 'time':
                dfr = getEntityChildren('property', propID, function (prop) {
                    dfr.resolve([prop, innerValue, null, propID, null, propID === 373 ? 'https://commons.wikimedia.org/wiki/Category:' : null]);
                });
                return dfr;
            case 'wikibase-entityid':
                dfr = getEntityChildren('property', propID, function (prop) {
                    var numericID = innerValue['numeric-id'];
                    getEntityChildren('item', numericID, function (innerValue, claims) {
                        dfr.resolve([prop, innerValue, claims, propID, numericID]);
                    });
                });
                return dfr;
            default:
                throw 'Unexpected value type: ' + valueType;
        }
    });
    $.when.apply($.when, deferreds).done(function () {
        $.makeArray(arguments).sort(function (a, b) {
            return (a[0] === b[0]) ? // If properties are equal, we sort by values
                (a[1] > b[1] ? 1 : -1) :
                (a[0] > b[0] ? 1 : -1);
        }).forEach(function (data) {
            var prop = data[0], val = data[1], claims = data[2], propID = data[3], numericID = data[4], commons = data[5],
                li = $('
').appendTo(ul).html('' + prop + '' + ': ' + (numericID ? val + ' (' + numericID + ')' : (commons ? '' + val + '' : val))),
                newUl = $('
').appendTo(li);
            li.click(function (e) {
                e.stopPropagation();
                if (!newUl.children().length && claims) {
                    processClaims(claims, newUl);
                }
            });
        });
    });
}

function initialReturn (data) {
    var label = data[0],
        claims = data[1],
        id = data[2],
        li = $('
').appendTo($('#entities')).html(label + ' ('+id+')'),
        ul = $('
').appendTo(li);
    li.click(function (e) {
        e.stopPropagation();
        if (!ul.children().length) {
            processClaims(claims, ul);
        }
    });
}

function doSearch (name) {
    $.getJSON(baseURL + '/w/api.php?callback=?', getConfigObject('search', name), function (data) {
        var results = data.query.search,
            deferreds = $.map(results, function (result) {
                return getEntityChildren('item', result.title);
            });
        $.when.apply($.when, deferreds).done(function () {
            $.makeArray(arguments).sort(function (a, b) {
                return a[0] === undefined ? 1 : // Undefined apparently trips up Firefox into thinking the array is finished
                    (a[0] === b[0]) ? // If properties are equal, we sort by values
                    (a[1] > b[1] ? 1 : -1) :
                    (a[0] > b[0] ? 1 : -1);
            }).forEach(initialReturn);
        });
    });
}

$(function () {

    // INITIATE AND ATTACH EVENTS
    ini_set('phpjs.parse_url.mode', 'strict');
    var purl = parse_url(window.location.href),
        qk = purl.queryKey,
        langValue = qk.language || 'en';

    if (qk.article) {
        $('#startArticle').val(decodeURIComponent(qk.article));
    }

    doSearch($('#startArticle').val());
    $('#startArticle').change(function (e) {
        // TODO: Implement http_build_url.js for php.js to rebuild URL and redirect for boomarkability
        // delete purl.query;
        // delete purl.queryKey;

        doSearch(e.target.value);
    });

    /*
    getEntityChildren('item', $('#start').val()).done(initialReturn);
    $('#start').change(function (e) {
        getEntityChildren('item', e.target.value).done(initialReturn);
    });
    */

    $('#empty').click(function empty () {
        $('#entities').empty();
    });

    // LANGUAGES
    $.getJSON(baseURL + '/w/api.php?callback=?', getConfigObject('languages'), function (data) {
        var options = $.map(data.query.languages, function (data) {
            return '<option value="'+ data.code +'">' + data['*'] + '</option>';
        }).join('').replace('"' + langValue + '"', '"' + langValue + '" selected');
        $('#languages').append(options);
    });
});

}());
<|endoftext|>


<commit_before>"use strict";

if (typeof chrome.runtime === "undefined") chrome = browser;

document.addEventListener('DOMContentLoaded', function() {
    {
        const lnkVersion = document.getElementById("lblVersion");
        lnkVersion.textContent = "v"+chrome.runtime.getManifest().version;
        lnkVersion.addEventListener("click", function() { chrome.runtime.openOptionsPage(); }, false);

        const lnkCopyForBug = document.getElementById("lnkCopyForBug");
        lnkCopyForBug.addEventListener("click", function() { copyForBug(); }, false);

        const lnkUnmark = document.getElementById("lnkUnmark");
        lnkUnmark.addEventListener("click", function() {
            lnkUnmark.textContent = "";
            chrome.tabs.executeScript(null, {code:"{const u = document.querySelectorAll('.moarTLSUnsecure');for (let i = 0; i < u.length; i++) u[i].classList.remove('moarTLSUnsecure');}", allFrames: true, runAt:"document_idle"}, null);
        }, false);
    }

    chrome.tabs.query({active: true, currentWindow: true }, function(activeTabs) {
        if (activeTabs.length < 1) return; // impossible?

        const oUri = document.createElement("a");
        oUri.href = activeTabs[0].url;
        const sOrigin = "https://" + oUri.host +"/";

        const sProt = oUri.protocol.toLowerCase();

        if ((sProt === "http:") || (sProt === "ftp:"))
        {
            document.getElementById("lnkUnmark").style.display="inline";
        }

        if (sProt.indexOf("chrome") == 0) {
            if (oUri.host === "newtab") {
                // injecting into newtab is permitted
                document.getElementById("lnkDomain").style.display = "none";
            }
            else {
                // otherwise, bail out.
                document.getElementById("txtStatus").textContent = "Unfortunately, Chrome's internal pages cannot be analyzed.";
                return;
            }
        }

        // http://stackoverflow.com/questions/11613371/chrome-extension-content-script-on-https-chrome-google-com-webstore
        if (oUri.href.toLowerCase().indexOf("https://chrome.google.com/webstore/") == 0)
        {
            document.getElementById("txtStatus").textContent = "Unfortunately, Chrome's Web Store pages cannot be analyzed.";
            // Bail out.
            return;
        }

        const lnkDomain = document.getElementById("lnkDomain");
        lnkDomain.href = "https://dev.ssllabs.com/ssltest/analyze.html?d=" + escape(oUri.hostname);
        lnkDomain.textContent = (((sProt == "http:") || (sProt =="ftp:")) ? (sProt.slice(0,-1)+"/") : "") + oUri.hostname;

        {
            document.getElementById("txtStatus").textContent = "Analyzing top-level page";

            // Mark top-level Page
            if (sProt == "https:")
            {
                document.getElementById("lnkDomain").classList.add("pageIsHTTPS");
            }

            // If HTTP/HTTPS, use XHR to check for HSTS
            if ((sProt == "http:") || (sProt == "https:"))
            {
                const oReq = new XMLHttpRequest();
                oReq.addEventListener("load",  function() {
                    const sHSTS = oReq.getResponseHeader("Strict-Transport-Security");
                    const bHSTS = (sHSTS && sHSTS.includes("max-age=") && !sHSTS.includes("max-age=0"));
                    const l = document.getElementById("lnkDomain");
                    if (sProt != "https:") { l.classList.add("pageCanUpgrade"); }
                    if (bHSTS) { l.classList.add("pageIsHSTS");  l.classList.remove("pageIsHTTPS"); }

                    const arrLI = htLinks[sOrigin];
                    markLIs(arrLI, true, bHSTS);
                }, false);
                const fnErr = function() {
                    document.getElementById("lnkDomain").classList.add("pageCannotUpgrade");
                    const arrLI = htLinks[sOrigin];
                    markLIs(arrLI, false, false);
                };
                oReq.addEventListener("error", fnErr, false);
                oReq.addEventListener("timeout", fnErr, false);
                oReq.open("HEAD", sOrigin, true);
                oReq.setRequestHeader("Cache-Control", "no-cache");
                oReq.timeout = 5000;
                oReq.send();
            }
        }

        document.getElementById("txtStatus").textContent = "Analyzing page elements";

        chrome.tabs.insertCSS(null, {file:"injected.css", allFrames: true, runAt:"document_idle"}, function() {
            // If you try to inject into an extensions page or the webstore/NTP you'll get an error
            if (chrome.runtime.lastError) {
                console.log('moarTLS error injecting css : \n' + chrome.runtime.lastError.message);
                chrome.runtime.sendMessage(null, {"error": chrome.runtime.lastError.message, "context": "insertCSS"});
            }
        });

        chrome.tabs.executeScript(null, {file:"injected.js", allFrames: true, runAt:"document_idle"}, function() {
            // If you try to inject into an extensions page or the webstore/NTP you'll get an error
            if (chrome.runtime.lastError) {
                console.log('moarTLS error injecting script : \n' + chrome.runtime.lastError.message);
                chrome.runtime.sendMessage(null, {"error": chrome.runtime.lastError.message, "context": "executeScript"});
            }
        });

    });
}, false);

function copyForBug()
{
    const copyFrom = document.createElement("textarea");

    // TODO: Generate a proper report
    copyFrom.textContent = document.body.textContent;
    document.body.appendChild(copyFrom);
    copyFrom.focus();
    copyFrom.select();
    document.execCommand('Copy', false, null);
    copyFrom.remove();
    const lnkCopyForBug = document.getElementById("lnkCopyForBug");
    lnkCopyForBug.textContent = "copied!";
    setTimeout(function() { lnkCopyForBug.innerHTML = "Copy"; }, 450);
}

function computeDisplayString(cInsecure, cTotal)
{
    if (cTotal < 1) return "This page does not contain any links.";
    if (cInsecure < 1) {
        if (cTotal == 1) return "The only link on this page is secure.";
        if (cTotal == 2) return "Both links on this page are secure.";
        return "All " + cTotal + " links on this page are secure.";
    }
    if (cInsecure == cTotal) {
        if (cTotal == 1) return "The only link on this page is non-secure.";
        if (cTotal == 2) return "Both links on this page are non-secure.";
        return "All " + cTotal + " links on this page are non-secure.";
    }
    return (cInsecure + " of " + cTotal + " links " + ((cInsecure == 1) ? "is" : "are") + " non-secure.");
}

// Update list UI based on HTTPS/HSTS availability
function markLIs(arrLI, bHTTPS, bHSTS)
{
    // No links yet
    if (!arrLI) { return; }
    for (let i=0; i < arrLI.length; i++)
    {
        if (arrLI[i].textContent.substring(0, 11) == "[Checking] ") {
            arrLI[i].textContent = arrLI[i].textContent.substring(11);
        }

        if (bHTTPS) {
            arrLI[i].classList.add("isHTTPSyes");
            if (bHSTS) arrLI[i].classList.add("isHSTS");
            arrLI[i].title = "This URL is available via HTTPS" + ((bHSTS) ? " + HSTS!" : ".");
        }
        else {
            arrLI[i].classList.add("isHTTPSno");
            arrLI[i].title = "This URL is NOT available by simply changing the protocol to HTTPS.";
        }
    }
}

// Check a target for HTTPS/HSTS availability using XHR
// TODO: Switch to FETCH and handle cases of redirections
function checkForHTTPS(lnk)
{
    if ((lnk.title.substring(0,11) == "This URL is") ||
        (lnk.title.substring(0,11) == "[Checking] ")) return;

    const oUri = document.createElement("a");
    oUri.href = lnk.textContent;
    // Wipe path entirely to prevent cases where e.g. a HEAD example.com/buy
    // isn't idempotent    // if (oUri.pathname.includes("logout"))
    // TODO if we ever remove this: ensure proper Path encoding when calling oReq.open
    const sOrigin = "https://" + oUri.host +"/";

    const arrLI = htLinks[sOrigin];
    for (let i=0; i < arrLI.length; i++)
    {
        arrLI[i].textContent = "[Checking] " + arrLI[i].textContent;
        arrLI[i].title = "[Checking] Using XmlHttpRequest to check for a HTTPS version of this url...";
    }

    const oReq = new XMLHttpRequest();
    oReq.addEventListener("load",  function() {
        const sHSTS = oReq.getResponseHeader("Strict-Transport-Security");
        const bHSTS = (sHSTS && sHSTS.includes("max-age=") && !sHSTS.includes("max-age=0"));

        markLIs(arrLI, true, bHSTS);
    }, false);

    const fnErr = function() { markLIs(arrLI, false, false); };

    oReq.addEventListener("error", fnErr, false);
    oReq.addEventListener("timeout", fnErr, false);

    oReq.open("HEAD", sOrigin, true);
    oReq.setRequestHeader("Cache-Control", "no-cache");
    oReq.timeout = 5000;
    oReq.send();
}

// Total number of elements evaluated in the page
var cTotalLinks = 0;
// Total number of non-secure elements in the page
var cTotalUnsecure = 0;
// Hashtable mapping Origin->ListItem[]
var htLinks = {};

chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {

    if (request.error)
    {
        let divError = document.createElement("div");
        divError.className = "scanErrorMessage";
        divError.textContent = "Error in " + request.context + ": " + request.error;
        document.body.appendChild(divError);
        return;
    }

    cTotalLinks += request.cLinks || 0;
    cTotalUnsecure += (request.unsecure) ? request.unsecure.length : 0;

    const bAnyInsecure = (cTotalUnsecure > 0);

    document.getElementById("txtStatus").textContent = computeDisplayString(cTotalUnsecure, cTotalLinks);
    if (bAnyInsecure) {
        document.body.style.backgroundColor = "#FFFF40";
        document.getElementById("lnkUnmark").style.display="inline";
        document.getElementById("lnkCopyForBug").style.display="inline";
        document.getElementById("lnkTips").style.display="inline";
    }
    else
    {
        if (document.getElementById("lnkDomain").classList.contains("pageIsHTTPS"))
        {
            document.body.style.backgroundColor = "#68FF68";
        }
    }

    let listUnsecure = document.getElementById("olUnsecureList");
    if (!listUnsecure)
    {
        listUnsecure = document.createElement("ol");
        listUnsecure.id = "olUnsecureList";
        document.getElementById("divUnsecureList").appendChild(listUnsecure);
    }

    for (let i=0; i < request.unsecure.length; i++) {
        const listItem = document.createElement("li");
        const text = document.createTextNode(request.unsecure[i]);
        listItem.appendChild(text);

        const oUri = document.createElement("a");
        oUri.href = request.unsecure[i];
        const sOrigin = "https://" + oUri.host +"/";
        if (undefined === htLinks[sOrigin])
        {
            htLinks[sOrigin] = [];
        }
        htLinks[sOrigin].push(listItem);

        listItem.addEventListener('click', function(e) {

            if ((e.altKey || e.ctrlKey) || (1 == e.button))
            {
                document.getElementById("lnkTips").style.display = "none";
                checkForHTTPS(this);
                return;
            }

        }, false);
        listUnsecure.appendChild(listItem);
    }
});

window.addEventListener('click', function(e) {
    if ((e.target.nodeName == "A") && (e.target.href !== undefined)) {
        chrome.tabs.create({ url: e.target.href });
    }
});<commit_msg>Addtional<commit_after>"use strict";

if (typeof chrome.runtime === "undefined") chrome = browser;

document.addEventListener('DOMContentLoaded', function() {
    {

    }

    chrome.tabs.query({active: true, currentWindow: true }, function(activeTabs) {
        if (activeTabs.length < 1) return; // impossible?

        const oUri = document.createElement("a");
        oUri.href = activeTabs[0].url;
        const sOrigin = "https://" + oUri.host +"/";

        const sProt = oUri.protocol.toLowerCase();

        if (sProt.indexOf("chrome") == 0) {
            if (oUri.host === "newtab") {
                // injecting into newtab is permitted
                document.getElementById("lnkDomain").style.display = "none";
            }
            else {
                // otherwise, bail out.
                document.getElementById("txtStatus").textContent = "Unfortunately, Chrome's internal pages cannot be analyzed.";
                return;
            }
        }

        // http://stackoverflow.com/questions/11613371/chrome-extension-content-script-on-https-chrome-google-com-webstore
        if (oUri.href.toLowerCase().indexOf("https://chrome.google.com/webstore/") == 0)
        {
            document.getElementById("txtStatus").textContent = "Unfortunately, Chrome's Web Store pages cannot be analyzed.";
            // Bail out.
            return;
        }

        document.getElementById("txtStatus").textContent = "Analyzing page elements";

        chrome.tabs.insertCSS(null, {file:"injected.css", allFrames: true, runAt:"document_idle"}, function() {
            // If you try to inject into an extensions page or the webstore/NTP you'll get an error
            if (chrome.runtime.lastError) {
                console.log('moarTLS error injecting css : \n' + chrome.runtime.lastError.message);
                chrome.runtime.sendMessage(null, {"error": chrome.runtime.lastError.message, "context": "insertCSS"});
            }
        });

        chrome.tabs.executeScript(null, {file:"injected.js", allFrames: true, runAt:"document_idle"}, function() {
            // If you try to inject into an extensions page or the webstore/NTP you'll get an error
            if (chrome.runtime.lastError) {
                console.log('moarTLS error injecting script : \n' + chrome.runtime.lastError.message);
                chrome.runtime.sendMessage(null, {"error": chrome.runtime.lastError.message, "context": "executeScript"});
            }
        });

    });
}, false);

// Total number of non-secure elements in the page
var cTotalUnsecure = 0;
// Hashtable mapping Origin->ListItem[]
var htLinks = {};

chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {

    if (request.error)
    {
        let divError = document.createElement("div");
        divError.className = "scanErrorMessage";
        divError.textContent = "Error in " + request.context + ": " + request.error;
        document.body.appendChild(divError);
        return;
    }

    cTotalUnsecure += (request.unsecure) ? request.unsecure.length : 0;

    const bAnyInsecure = (cTotalUnsecure > 0);

    if (bAnyInsecure) {  // If any link is insecure
        document.body.style.backgroundColor = "#FFFF40";  // Yellow

    }
    else
    {
        if (document.getElementById("lnkDomain").classList.contains("pageIsHTTPS"))
        {
            document.body.style.backgroundColor = "#68FF68";  // Green
        }
    }

});

window.addEventListener('click', function(e) {
    if ((e.target.nodeName == "A") && (e.target.href !== undefined)) {
        chrome.tabs.create({ url: e.target.href });
    }
});<|endoftext|>


<commit_before>export default {
  "電源OK": {pos:0, text:'電源:OK'},
  "電源NG": {pos:1, text:'電源:NG'},
  "電源:実績あり": {pos:3, text:'使えた
実績あり'},
  "電源あり(喫煙席)": {pos:4, text:'電源あり
(喫煙席)'},
  "電源あり(禁煙席)": {pos:5, text:'電源あり(禁煙席)'},
  "Qi": {pos:9, text:'ワイヤレス
充電'},
  "タバコ:全席禁煙": {pos:6, text:'全席禁煙'},
  "タバコ:全席喫煙可": {pos:7, text:'全席喫煙'},
  "タバコ:分煙": {pos:8, text:'分煙'},
  "イートインスペース": {pos:24, text:'イートイン
スペース'},
  "電源:お客様用コンセント": {pos:0, text:'お客様用
コンセント'},
  "電源:壁コンセント": {pos:3, text:'壁コンセント'},
  "電源:USB": {pos:27, text:'USB
コンセント'},
  "電源:充電器貸出": {pos:28, text:'充電器貸し出しあり'},

  "ソフトバンクWi-Fi": {pos:10, text:'Softbank
Wi-Fi'},
  "docomo Wi-Fi": {pos:11, text:'ドコモ
Wi-Fi'},
  "au Wi-Fi": {pos:12, text:'au Wi-Fi'},
  "フレッツ・スポット": {pos:15, text:'フレッツ
スポット'},
  "BBモバイルポイント": {pos:16, text:'BBモバイル
ポイント'},
  "FREESPOT": {pos:17, text:'FREESPOT'},
  "有線LAN": {pos:18, text:'有線LAN'},
  "独自": {pos:19, text:'独自'},
  "wi2": {pos:25, text:'wi2'},
  "Tullys Wi-Fi": {pos:26, text:"Tullys
Wi-Fi"},
  "Other Wi-Fi": {pos:19, text:"その他"},
};
<commit_msg>wi2 premium/wi2 freeのアイコン追加 #33<commit_after>export default {
  "電源OK": {pos:0, text:'電源:OK'},
  "電源NG": {pos:1, text:'電源:NG'},
  "電源:実績あり": {pos:3, text:'使えた
実績あり'},
  "電源あり(喫煙席)": {pos:4, text:'電源あり
(喫煙席)'},
  "電源あり(禁煙席)": {pos:5, text:'電源あり(禁煙席)'},
  "Qi": {pos:9, text:'ワイヤレス
充電'},
  "タバコ:全席禁煙": {pos:6, text:'全席禁煙'},
  "タバコ:全席喫煙可": {pos:7, text:'全席喫煙'},
  "タバコ:分煙": {pos:8, text:'分煙'},
  "イートインスペース": {pos:24, text:'イートイン
スペース'},
  "電源:お客様用コンセント": {pos:0, text:'お客様用
コンセント'},
  "電源:壁コンセント": {pos:3, text:'壁コンセント'},
  "電源:USB": {pos:27, text:'USB
コンセント'},
  "電源:充電器貸出": {pos:28, text:'充電器貸し出しあり'},

  "ソフトバンクWi-Fi": {pos:10, text:'Softbank
Wi-Fi'},
  "docomo Wi-Fi": {pos:11, text:'ドコモ
Wi-Fi'},
  "au Wi-Fi": {pos:12, text:'au Wi-Fi'},
  "フレッツ・スポット": {pos:15, text:'フレッツ
スポット'},
  "BBモバイルポイント": {pos:16, text:'BBモバイル
ポイント'},
  "FREESPOT": {pos:17, text:'FREESPOT'},
  "有線LAN": {pos:18, text:'有線LAN'},
  "独自": {pos:19, text:'独自'},
  "wi2": {pos:25, text:'wi2'},
  "wi2:free": {pos:30, text:'wi2
free'},
  "wi2:premium": {pos:29, text:'wi2
premium'},
  "Tullys Wi-Fi": {pos:26, text:"Tullys
Wi-Fi"},
  "Other Wi-Fi": {pos:19, text:"その他"},
};
<|endoftext|>


<commit_before>import {GeoJSON, WFS} from 'ol/format';
export default [
  'hs.utils.service',
  '$http',
  'config',
  'hs.map.service',
  '$timeout',
  '$log',
  'hs.common.endpointsService',
  function (utils, $http, config, hsMap, $timeout, $log, endpointsService) {
    const me = this;
    angular.extend(me, {
      /**
       * @ngdoc method
       * @function save
       * @memberof hs.laymanService
       * @public
       * @param {String} compositionJson Json with composition definition
       * @param {Object} endpoint Endpoint description
       * @param {String} compoData Additional fields for composition such
       * @param {Boolean} saveAsNew Save as new composition
       * as title, name
       * @returns {Promise<Boolean>} Promise result of POST
       * @description Save composition to Layman
       */
      save(compositionJson, endpoint, compoData, saveAsNew) {
        return new Promise((resolve, reject) => {
          const formdata = new FormData();
          formdata.append(
            'file',
            new Blob([angular.toJson(compositionJson)], {
              type: 'application/json',
            }),
            'blob.json'
          );
          formdata.append('name', compoData.title);
          formdata.append('title', compoData.title);
          formdata.append('abstract', compoData.abstract);
          $http({
            url: `${endpoint.url}/rest/${endpoint.user}/maps${
              saveAsNew
                ? `?${Math.random()}`
                : '/' + me.urlFriendly(compoData.title)
            }`,
            method: saveAsNew ? 'POST' : 'PATCH',
            data: formdata,
            transformRequest: angular.identity,
            headers: {'Content-Type': undefined},
          }).then(
            (response) => {
              resolve(response);
            },
            (err) => {
              reject(err.data);
            }
          );
        });
      },

      urlFriendly(text) {
        return text.replaceAll(' ', '').toLowerCase();
      },

      /**
       * @ngdoc method
       * @function pushVectorSource
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} geojson Geojson object with features to send to server
       * @param {String} description Object containing {name, title, crs} of
       * layer to retrieve
       * @param {Object} layerDesc Previously fetched layer descriptor
       * @returns {Promise<Boolean>} Promise result of POST/PATCH
       * @description Send layer definition and features to Layman
       */
      pushVectorSource(endpoint, geojson, description, layerDesc) {
        return new Promise((resolve, reject) => {
          const formdata = new FormData();
          formdata.append(
            'file',
            new Blob([angular.toJson(geojson)], {type: 'application/geo+json'}),
            'blob.geojson'
          );
          formdata.append(
            'sld',
            new Blob([], {type: 'application/octet-stream'}),
            ''
          );
          formdata.append('name', description.name);
          formdata.append('title', description.title);
          formdata.append('crs', description.crs);
          me.checkIfLayerExists(endpoint, description.name, layerDesc)
            .then((layerDesc) => {
              $http({
                url: `${endpoint.url}/rest/${endpoint.user}/layers${
                  layerDesc.exists ? '/' + description.name : ''
                }?${Math.random()}`,
                method: layerDesc.exists ? 'PATCH' : 'POST',
                data: formdata,
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined},
              }).then((response) => {
                resolve(response);
              });
            })
            .catch((err) => {
              reject(err.data);
            });
        });
      },

      getLayerName(layer) {
        return layer.get('title').toLowerCase().replaceAll(' ', '');
      },

      /**
       * Send all features to Layman endpoint as WFS string
       * @memberof hs.layerSynchronizerService
       * @function push
       * @param {Ol.layer} layer Layer to get Layman friendly name for
       * get features
       */
      push(layer) {
        if (layer.getSource().loading) {
          return;
        }
        const f = new GeoJSON();
        const geojson = f.writeFeaturesObject(layer.getSource().getFeatures());
        (endpointsService.endpoints || [])
          .filter((ds) => ds.type == 'layman')
          .forEach((ds) => {
            layer.set('hs-layman-synchronizing', true);
            me.pushVectorSource(ds, geojson, {
              title: layer.get('title'),
              name: me.getLayerName(layer),
              crs: me.crs,
            }).then((response) => {
              $timeout(() => {
                me.pullVectorSource(ds, me.getLayerName(layer)).then(
                  (response) => {
                    layer.set('hs-layman-synchronizing', false);
                  }
                );
              }, 2000);
            });
          });
      },

      /**
       * @ngdoc method
       * @function addFeature
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {Array} featuresToAdd Array of features to add
       * @param {Array} featuresToUpd Array of features to update
       * @param {Array} featuresToDel Array of features to delete
       * @param {String} name Name of layer
       * @param {ol/Layer} layer Openlayers layer
       * @returns {Promise<Boolean>} Promise result of POST
       * @description Insert a feature
       */
      createWfsTransaction(
        endpoint,
        featuresToAdd,
        featuresToUpd,
        featuresToDel,
        name,
        layer
      ) {
        return new Promise((resolve, reject) => {
          me.checkIfLayerExists(
            endpoint,
            name,
            layer.get('laymanLayerDescriptor')
          )
            .then((layerDesc) => {
              if (layerDesc.exists) {
                layer.set('laymanLayerDescriptor', layerDesc);
                try {
                  const wfsFormat = new WFS();
                  const serializedFeature = wfsFormat.writeTransaction(
                    featuresToAdd,
                    featuresToUpd,
                    featuresToDel,
                    {
                      featureNS: 'http://' + endpoint.user,
                      featurePrefix: endpoint.user,
                      featureType: name,
                      srsName: hsMap.map.getView().getProjection().getCode(),
                    }
                  );

                  $http({
                    url: layerDesc.wfs.url,
                    method: 'POST',
                    data: serializedFeature.outerHTML
                      .replaceAll('<geometry>', '<wkb_geometry>')
                      .replaceAll('</geometry>', '</wkb_geometry>'),
                    headers: {'Content-Type': 'application/xml'},
                  }).then((response) => {
                    resolve(response);
                  });
                } catch (ex) {
                  $log.error(ex);
                }
              } else {
                me.push(layer);
              }
            })
            .catch((err) => {
              reject(err.data);
            });
        });
      },

      /**
       * @ngdoc method
       * @function pullVectorSource
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} layerName Object containing {name, title, crs} of
       * layer to retrieve
       * @returns {Promise<Boolean>} Promise which WFS xml (GML3.1) response
       * with features for a specified layer
       * @description Retrieve layers features from server
       */
      pullVectorSource(endpoint, layerName) {
        return new Promise((resolve, reject) => {
          me.describeLayer(endpoint, layerName).then((descr) => {
            if (descr === null) {
              resolve();
              return;
            }
            if (
              descr.wfs.status == 'NOT_AVAILABLE' &&
              descr.wms.status == 'NOT_AVAILABLE'
            ) {
              resolve();
              return;
            }
            if (descr.wfs.status == 'NOT_AVAILABLE') {
              $timeout(() => {
                me.pullVectorSource(endpoint, layerName).then((response) =>
                  resolve(response)
                );
              }, 2000);
              return;
            }
            /* When OL will support GML3.2, then we can use WFS
                        version 2.0.0. Currently only 3.1.1 is possible */
            $http({
              url:
                descr.wfs.url +
                '?' +
                utils.paramsToURL({
                  service: 'wfs',
                  version: '1.1.0',
                  request: 'GetFeature',
                  typeNames: `${endpoint.user}:${descr.name}`,
                  r: Math.random(),
                }),
              method: 'GET',
            }).then(
              (response) => {
                resolve(response);
              },
              (err) => resolve(null)
            );
          });
        });
      },

      /**
       * @ngdoc method
       * @function describeLayer
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} layerName Layer name
       * @returns {Promise<Boolean>} Promise which returns layers
       * description containig name, file, wms, wfs urls etc.
       * @description Try getting layer description from layman.
       */
      describeLayer(endpoint, layerName) {
        return new Promise((resolve, reject) => {
          $http({
            url: `${endpoint.url}/rest/${
              endpoint.user
            }/layers/${layerName}?${Math.random()}`,
            method: 'GET',
          }).then(
            (response) => {
              if (
                angular.isDefined(response.data.code) &&
                response.data.code == 15
              ) {
                resolve(null);
              }
              if (angular.isDefined(response.data.name)) {
                resolve(response.data);
              }
            },
            (err) => {
              resolve(null);
            }
          );
        });
      },

      /**
       * @ngdoc method
       * @function checkIfLayerExists
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} layerName Name of layer
       * @param {Object} layerDesc Previously loaded layer descriptor
       * @returns {Promise<Boolean>} Promise which returns boolean if layer
       * exists in Layman
       * @description Try getting layer description from layman. If it
       * succeeds, that means that layer is there and can be updated
       * instead of postign a new one
       */
      checkIfLayerExists(endpoint, layerName, layerDesc) {
        return new Promise((resolve, reject) => {
          if (angular.isUndefined(layerDesc)) {
            me.describeLayer(endpoint, layerName).then((description) => {
              if (
                description !== null &&
                angular.isDefined(description.code) &&
                description.code == 15
              ) {
                resolve({exists: false});
              } else if (
                description !== null &&
                angular.isDefined(description.name)
              ) {
                resolve(angular.extend(description, {exists: true}));
              } else {
                resolve({exists: false});
              }
            });
          } else {
            resolve(layerDesc);
          }
        });
      },
    });
    return me;
  },
];
<commit_msg>Typo fix in docs<commit_after>import {GeoJSON, WFS} from 'ol/format';
export default [
  'hs.utils.service',
  '$http',
  'config',
  'hs.map.service',
  '$timeout',
  '$log',
  'hs.common.endpointsService',
  function (utils, $http, config, hsMap, $timeout, $log, endpointsService) {
    const me = this;
    angular.extend(me, {
      /**
       * @ngdoc method
       * @function save
       * @memberof hs.laymanService
       * @public
       * @param {String} compositionJson Json with composition definition
       * @param {Object} endpoint Endpoint description
       * @param {String} compoData Additional fields for composition such
       * @param {Boolean} saveAsNew Save as new composition
       * as title, name
       * @returns {Promise<Boolean>} Promise result of POST
       * @description Save composition to Layman
       */
      save(compositionJson, endpoint, compoData, saveAsNew) {
        return new Promise((resolve, reject) => {
          const formdata = new FormData();
          formdata.append(
            'file',
            new Blob([angular.toJson(compositionJson)], {
              type: 'application/json',
            }),
            'blob.json'
          );
          formdata.append('name', compoData.title);
          formdata.append('title', compoData.title);
          formdata.append('abstract', compoData.abstract);
          $http({
            url: `${endpoint.url}/rest/${endpoint.user}/maps${
              saveAsNew
                ? `?${Math.random()}`
                : '/' + me.urlFriendly(compoData.title)
            }`,
            method: saveAsNew ? 'POST' : 'PATCH',
            data: formdata,
            transformRequest: angular.identity,
            headers: {'Content-Type': undefined},
          }).then(
            (response) => {
              resolve(response);
            },
            (err) => {
              reject(err.data);
            }
          );
        });
      },

      urlFriendly(text) {
        return text.replaceAll(' ', '').toLowerCase();
      },

      /**
       * @ngdoc method
       * @function pushVectorSource
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} geojson Geojson object with features to send to server
       * @param {String} description Object containing {name, title, crs} of
       * layer to retrieve
       * @param {Object} layerDesc Previously fetched layer descriptor
       * @returns {Promise<Boolean>} Promise result of POST/PATCH
       * @description Send layer definition and features to Layman
       */
      pushVectorSource(endpoint, geojson, description, layerDesc) {
        return new Promise((resolve, reject) => {
          const formdata = new FormData();
          formdata.append(
            'file',
            new Blob([angular.toJson(geojson)], {type: 'application/geo+json'}),
            'blob.geojson'
          );
          formdata.append(
            'sld',
            new Blob([], {type: 'application/octet-stream'}),
            ''
          );
          formdata.append('name', description.name);
          formdata.append('title', description.title);
          formdata.append('crs', description.crs);
          me.checkIfLayerExists(endpoint, description.name, layerDesc)
            .then((layerDesc) => {
              $http({
                url: `${endpoint.url}/rest/${endpoint.user}/layers${
                  layerDesc.exists ? '/' + description.name : ''
                }?${Math.random()}`,
                method: layerDesc.exists ? 'PATCH' : 'POST',
                data: formdata,
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined},
              }).then((response) => {
                resolve(response);
              });
            })
            .catch((err) => {
              reject(err.data);
            });
        });
      },

      getLayerName(layer) {
        return layer.get('title').toLowerCase().replaceAll(' ', '');
      },

      /**
       * Send all features to Layman endpoint as WFS string
       * @memberof hs.layerSynchronizerService
       * @function push
       * @param {Ol.layer} layer Layer to get Layman friendly name for
       * get features
       */
      push(layer) {
        if (layer.getSource().loading) {
          return;
        }
        const f = new GeoJSON();
        const geojson = f.writeFeaturesObject(layer.getSource().getFeatures());
        (endpointsService.endpoints || [])
          .filter((ds) => ds.type == 'layman')
          .forEach((ds) => {
            layer.set('hs-layman-synchronizing', true);
            me.pushVectorSource(ds, geojson, {
              title: layer.get('title'),
              name: me.getLayerName(layer),
              crs: me.crs,
            }).then((response) => {
              $timeout(() => {
                me.pullVectorSource(ds, me.getLayerName(layer)).then(
                  (response) => {
                    layer.set('hs-layman-synchronizing', false);
                  }
                );
              }, 2000);
            });
          });
      },

      /**
       * @ngdoc method
       * @function addFeature
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {Array} featuresToAdd Array of features to add
       * @param {Array} featuresToUpd Array of features to update
       * @param {Array} featuresToDel Array of features to delete
       * @param {String} name Name of layer
       * @param {ol/Layer} layer Openlayers layer
       * @returns {Promise<Boolean>} Promise result of POST
       * @description Insert a feature
       */
      createWfsTransaction(
        endpoint,
        featuresToAdd,
        featuresToUpd,
        featuresToDel,
        name,
        layer
      ) {
        return new Promise((resolve, reject) => {
          me.checkIfLayerExists(
            endpoint,
            name,
            layer.get('laymanLayerDescriptor')
          )
            .then((layerDesc) => {
              if (layerDesc.exists) {
                layer.set('laymanLayerDescriptor', layerDesc);
                try {
                  const wfsFormat = new WFS();
                  const serializedFeature = wfsFormat.writeTransaction(
                    featuresToAdd,
                    featuresToUpd,
                    featuresToDel,
                    {
                      featureNS: 'http://' + endpoint.user,
                      featurePrefix: endpoint.user,
                      featureType: name,
                      srsName: hsMap.map.getView().getProjection().getCode(),
                    }
                  );

                  $http({
                    url: layerDesc.wfs.url,
                    method: 'POST',
                    data: serializedFeature.outerHTML
                      .replaceAll('<geometry>', '<wkb_geometry>')
                      .replaceAll('</geometry>', '</wkb_geometry>'),
                    headers: {'Content-Type': 'application/xml'},
                  }).then((response) => {
                    resolve(response);
                  });
                } catch (ex) {
                  $log.error(ex);
                }
              } else {
                me.push(layer);
              }
            })
            .catch((err) => {
              reject(err.data);
            });
        });
      },

      /**
       * @ngdoc method
       * @function pullVectorSource
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} layerName Object containing {name, title, crs} of
       * layer to retrieve
       * @returns {Promise<Boolean>} Promise which WFS xml (GML3.1) response
       * with features for a specified layer
       * @description Retrieve layers features from server
       */
      pullVectorSource(endpoint, layerName) {
        return new Promise((resolve, reject) => {
          me.describeLayer(endpoint, layerName).then((descr) => {
            if (descr === null) {
              resolve();
              return;
            }
            if (
              descr.wfs.status == 'NOT_AVAILABLE' &&
              descr.wms.status == 'NOT_AVAILABLE'
            ) {
              resolve();
              return;
            }
            if (descr.wfs.status == 'NOT_AVAILABLE') {
              $timeout(() => {
                me.pullVectorSource(endpoint, layerName).then((response) =>
                  resolve(response)
                );
              }, 2000);
              return;
            }
            /* When OL will support GML3.2, then we can use WFS
                        version 2.0.0. Currently only 3.1.1 is possible */
            $http({
              url:
                descr.wfs.url +
                '?' +
                utils.paramsToURL({
                  service: 'wfs',
                  version: '1.1.0',
                  request: 'GetFeature',
                  typeNames: `${endpoint.user}:${descr.name}`,
                  r: Math.random(),
                }),
              method: 'GET',
            }).then(
              (response) => {
                resolve(response);
              },
              (err) => resolve(null)
            );
          });
        });
      },

      /**
       * @ngdoc method
       * @function describeLayer
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} layerName Layer name
       * @returns {Promise<Boolean>} Promise which returns layers
       * description containig name, file, wms, wfs urls etc.
       * @description Try getting layer description from layman.
       */
      describeLayer(endpoint, layerName) {
        return new Promise((resolve, reject) => {
          $http({
            url: `${endpoint.url}/rest/${
              endpoint.user
            }/layers/${layerName}?${Math.random()}`,
            method: 'GET',
          }).then(
            (response) => {
              if (
                angular.isDefined(response.data.code) &&
                response.data.code == 15
              ) {
                resolve(null);
              }
              if (angular.isDefined(response.data.name)) {
                resolve(response.data);
              }
            },
            (err) => {
              resolve(null);
            }
          );
        });
      },

      /**
       * @ngdoc method
       * @function checkIfLayerExists
       * @memberof hs.laymanService
       * @public
       * @param {Object} endpoint Endpoint description
       * @param {String} layerName Name of layer
       * @param {Object} layerDesc Previously loaded layer descriptor
       * @returns {Promise<Boolean>} Promise which returns boolean if layer
       * exists in Layman
       * @description Try getting layer description from layman. If it
       * succeeds, that means that layer is there and can be updated
       * instead of posting a new one
       */
      checkIfLayerExists(endpoint, layerName, layerDesc) {
        return new Promise((resolve, reject) => {
          if (angular.isUndefined(layerDesc)) {
            me.describeLayer(endpoint, layerName).then((description) => {
              if (
                description !== null &&
                angular.isDefined(description.code) &&
                description.code == 15
              ) {
                resolve({exists: false});
              } else if (
                description !== null &&
                angular.isDefined(description.name)
              ) {
                resolve(angular.extend(description, {exists: true}));
              } else {
                resolve({exists: false});
              }
            });
          } else {
            resolve(layerDesc);
          }
        });
      },
    });
    return me;
  },
];
<|endoftext|>