<commit_before>// ==ClosureCompiler==
// @output_file_name formdata.min.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==

if (!window.FormData || !window.FormData.prototype.keys) {

  // keep a reference to native implementation
  const _FormData = window.FormData

  // To be monkey patched
  const _send = window.XMLHttpRequest.prototype.send
  const _fetch = window.Request && window.fetch

  // Unable to patch Request constructor correctly
  // const _Request = window.Request
  // only way is to use ES6 class extend
  // https://github.com/babel/babel/issues/1966

  const stringTag = window.Symbol && Symbol.toStringTag
  const map = new WeakMap
  const wm = o => map.get(o)
  const type = obj => Object.prototype.toString.call(obj).slice(8, -1)
  const arrayFrom = Array.from || (obj => [].slice.call(obj))

  // Add missing stringTags to blob and files
  if (stringTag) {
    if (!Blob.prototype[stringTag]) {
      Blob.prototype[stringTag] = 'Blob'
    }

    if ('File' in window && !File.prototype[stringTag]) {
      File.prototype[stringTag] = 'File'
    }
  }

  // Fix so you can construct your own File
  try {
    new File([], '')
  } catch (a) {
    window.File = function(b, d, c) {
      const blob = new Blob(b, c)
      const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date

      Object.defineProperties(blob, {
        name: {
          value: d
        },
        lastModifiedDate: {
          value: t
        },
        lastModified: {
          value: +t
        },
        toString: {
          value() {
            return '[object File]'
          }
        }
      })

      if (stringTag) {
        Object.defineProperty(blob, stringTag, {
          value: 'File'
        })
      }

      return blob
    }
  }

  function normalizeValue([value, filename]) {
    if (value instanceof Blob)
      // Should always returns a new File instance
      // console.assert(fd.get(x) !== fd.get(x))
      value = new File([value], filename, {
        type: value.type,
        lastModified: value.lastModified
      })

    return value
  }

  function stringify(name) {
    if (!arguments.length)
      throw new TypeError('1 argument required, but only 0 present.')

    return [name + '']
  }

  function normalizeArgs(name, value, filename) {
    if (arguments.length < 2)
      throw new TypeError(
        `2 arguments required, but only ${arguments.length} present.`
      )

    return value instanceof Blob
      // normalize name and filename if adding an attachment
      ? [name + '', value, filename !== undefined
        ? filename + '' // Cast filename to string if 3th arg isn't undefined
        : type(value) === 'File' // if it's a File
          ? value.name // Use File.name
          : 'Blob'] // otherwise fallback to Blob

      // If no attachment, just cast the args to strings
      : [name + '', value + '']
  }

  /**
   * @implements {Iterable}
   */
  class FormDataPolyfill {

    /**
     * FormData class
     *
     * @param {HTMLElement=} form
     */
    constructor(form) {
      map.set(this, Object.create(null))

      if (!form)
        return this

      for (let elm of arrayFrom(form.elements)) {
        if (!elm.name || elm.disabled) continue

        if (elm.type === 'file')
          for (let file of elm.files)
            this.append(elm.name, file)
        else if (elm.type === 'select-multiple' || elm.type === 'select-one')
          for (let opt of arrayFrom(elm.selectedOptions))
            this.append(opt.name, opt.value)
        else if (elm.type === 'checkbox' || elm.type === 'radio') {
          if (elm.checked) this.append(elm.name, elm.value)
        } else
          this.append(elm.name, elm.value)
      }
    }

    /**
     * Append a field
     *
     * @param   {String}           name      field name
     * @param   {String|Blob|File} value     string / blob / file
     * @param   {String=}          filename  filename to use with blob
     * @return  {Undefined}
     */
    append(name, value, filename) {
      const map = wm(this)

      if (!map[name])
        map[name] = []

      map[name].push([value, filename])
    }

    /**
     * Delete all fields values given name
     *
     * @param   {String}  name  Field name
     * @return  {Undefined}
     */
    delete(name) {
      delete wm(this)[name]
    }

    /**
     * Iterate over all fields as [name, value]
     *
     * @return {Iterator}
     */
    *entries() {
      const map = wm(this)

      for (let name in map)
        for (let value of map[name])
          yield [name, normalizeValue(value)]
    }

    /**
     * Iterate over all fields
     *
     * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
     * @param   {Object=}   thisArg   `this` context for callback function
     * @return  {Undefined}
     */
    forEach(callback, thisArg) {
      for (let [name, value] of this)
        callback.call(thisArg, value, name, this)
    }

    /**
     * Return first field value given name
     * or null if non existen
     *
     * @param   {String}  name      Field name
     * @return  {String|File|null}  value Fields value
     */
    get(name) {
      const map = wm(this)
      return map[name] ? normalizeValue(map[name][0]) : null
    }

    /**
     * Return all fields values given name
     *
     * @param   {String}  name  Fields name
     * @return  {Array}         [{String|File}]
     */
    getAll(name) {
      return (wm(this)[name] || []).map(normalizeValue)
    }

    /**
     * Check for field name existence
     *
     * @param   {String}   name  Field name
     * @return  {boolean}
     */
    has(name) {
      return name in wm(this)
    }

    /**
     * Iterate over all fields name
     *
     * @return {Iterator}
     */
    *keys() {
      for (let [name] of this)
        yield name
    }

    /**
     * Overwrite all values given name
     *
     * @param   {String}    name      Filed name
     * @param   {String}    value     Field value
     * @param   {String=}   filename  Filename (optional)
     * @return  {Undefined}
     */
    set(name, value, filename) {
      wm(this)[name] = [[value, filename]]
    }

    /**
     * Iterate over all fields
     *
     * @return {Iterator}
     */
    *values() {
      for (let [name, value] of this)
        yield value
    }

    /**
     * Return a native (perhaps degraded) FormData with only a `append` method
     * Can throw if it's not supported
     *
     * @return {FormData}
     */
    ['_asNative']() {
      const fd = new _FormData

      for (let [name, value] of this)
        fd.append(name, value)

      return fd
    }

    /**
     * [_blob description]
     *
     * @return {Blob} [description]
     */
    ['_blob']() {
      const boundary = '----formdata-polyfill-' + Math.random()
      const chunks = []

      for (let [name, value] of this) {
        chunks.push(`--${boundary}\r\n`)

        if (value instanceof Blob) {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`,
            `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
            value,
            '\r\n'
          )
        } else {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
          )
        }
      }

      chunks.push(`--${boundary}--`)

      return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary})
    }

    /**
     * The class itself is iterable
     * alias for formdata.entries()
     *
     * @return  {Iterator}
     */
    [Symbol.iterator]() {
      return this.entries()
    }

    /**
     * Create the default string description.
     *
     * @return  {String} [object FormData]
     */
    toString() {
      return '[object FormData]'
    }
  }

  if (stringTag) {
    /**
     * Create the default string description.
     * It is accessed internally by the Object.prototype.toString().
     *
     * @return {String} FormData
     */
    FormDataPolyfill.prototype[stringTag] = 'FormData'
  }

  for (let [method, decorator] of [
    ['append', normalizeArgs],
    ['delete', stringify],
    ['get',    stringify],
    ['getAll', stringify],
    ['has',    stringify],
    ['set',    normalizeArgs]
  ]) {
    let orig = FormDataPolyfill.prototype[method]
    FormDataPolyfill.prototype[method] = function() {
      return orig.apply(this, decorator(...arguments))
    }
  }

  // Patch xhr's send method to call _blob transparently
  XMLHttpRequest.prototype.send = function(data) {
    _send.call(this, data instanceof FormDataPolyfill ? data['_blob']() : data)
  }

  // Patch fetch's function to call _blob transparently
  if (_fetch) {
    const _fetch = window.fetch

    window.fetch = function(input, init) {
      if (init && init.body && init.body instanceof FormDataPolyfill) {
        init.body = init.body['_blob']()
      }

      return _fetch(input, init)
    }
  }

  window['FormData'] = FormDataPolyfill
}
<commit_msg>simplified some methods that yields unnecessary es5 code<commit_after>// ==ClosureCompiler==
// @output_file_name formdata.min.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==

if (!window.FormData || !window.FormData.prototype.keys) {

  // keep a reference to native implementation
  const _FormData = window.FormData

  // To be monkey patched
  const _send = window.XMLHttpRequest.prototype.send
  const _fetch = window.Request && window.fetch

  // Unable to patch Request constructor correctly
  // const _Request = window.Request
  // only way is to use ES6 class extend
  // https://github.com/babel/babel/issues/1966

  const stringTag = window.Symbol && Symbol.toStringTag
  const map = new WeakMap
  const wm = o => map.get(o)
  const type = obj => Object.prototype.toString.call(obj).slice(8, -1)
  const arrayFrom = Array.from || (obj => [].slice.call(obj))

  // Add missing stringTags to blob and files
  if (stringTag) {
    if (!Blob.prototype[stringTag]) {
      Blob.prototype[stringTag] = 'Blob'
    }

    if ('File' in window && !File.prototype[stringTag]) {
      File.prototype[stringTag] = 'File'
    }
  }

  // Fix so you can construct your own File
  try {
    new File([], '')
  } catch (a) {
    window.File = function(b, d, c) {
      const blob = new Blob(b, c)
      const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date

      Object.defineProperties(blob, {
        name: {
          value: d
        },
        lastModifiedDate: {
          value: t
        },
        lastModified: {
          value: +t
        },
        toString: {
          value() {
            return '[object File]'
          }
        }
      })

      if (stringTag) {
        Object.defineProperty(blob, stringTag, {
          value: 'File'
        })
      }

      return blob
    }
  }

  function normalizeValue([value, filename]) {
    if (value instanceof Blob)
      // Should always returns a new File instance
      // console.assert(fd.get(x) !== fd.get(x))
      value = new File([value], filename, {
        type: value.type,
        lastModified: value.lastModified
      })

    return value
  }

  function stringify(name) {
    if (!arguments.length)
      throw new TypeError('1 argument required, but only 0 present.')

    return [name + '']
  }

  function normalizeArgs(name, value, filename) {
    if (arguments.length < 2)
      throw new TypeError(
        `2 arguments required, but only ${arguments.length} present.`
      )

    return value instanceof Blob
      // normalize name and filename if adding an attachment
      ? [name + '', value, filename !== undefined
        ? filename + '' // Cast filename to string if 3th arg isn't undefined
        : type(value) === 'File' // if it's a File
          ? value.name // Use File.name
          : 'Blob'] // otherwise fallback to Blob

      // If no attachment, just cast the args to strings
      : [name + '', value + '']
  }

  /**
   * @implements {Iterable}
   */
  class FormDataPolyfill {

    /**
     * FormData class
     *
     * @param {HTMLElement=} form
     */
    constructor(form) {
      map.set(this, Object.create(null))

      if (!form)
        return this

      for (let elm of arrayFrom(form.elements)) {
        if (!elm.name || elm.disabled) continue

        if (elm.type === 'file')
          for (let file of elm.files)
            this.append(elm.name, file)
        else if (elm.type === 'select-multiple' || elm.type === 'select-one')
          for (let opt of arrayFrom(elm.selectedOptions))
            this.append(opt.name, opt.value)
        else if (elm.type === 'checkbox' || elm.type === 'radio') {
          if (elm.checked) this.append(elm.name, elm.value)
        } else
          this.append(elm.name, elm.value)
      }
    }

    /**
     * Append a field
     *
     * @param   {String}           name      field name
     * @param   {String|Blob|File} value     string / blob / file
     * @param   {String=}          filename  filename to use with blob
     * @return  {Undefined}
     */
    append(name, value, filename) {
      const map = wm(this)

      if (!map[name])
        map[name] = []

      map[name].push([value, filename])
    }

    /**
     * Delete all fields values given name
     *
     * @param   {String}  name  Field name
     * @return  {Undefined}
     */
    delete(name) {
      delete wm(this)[name]
    }

    /**
     * Iterate over all fields as [name, value]
     *
     * @return {Iterator}
     */
    *entries() {
      const map = wm(this)

      for (let name in map)
        for (let value of map[name])
          yield [name, normalizeValue(value)]
    }

    /**
     * Iterate over all fields
     *
     * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
     * @param   {Object=}   thisArg   `this` context for callback function
     * @return  {Undefined}
     */
    forEach(callback, thisArg) {
      for (let [name, value] of this)
        callback.call(thisArg, value, name, this)
    }

    /**
     * Return first field value given name
     * or null if non existen
     *
     * @param   {String}  name      Field name
     * @return  {String|File|null}  value Fields value
     */
    get(name) {
      const map = wm(this)
      return map[name] ? normalizeValue(map[name][0]) : null
    }

    /**
     * Return all fields values given name
     *
     * @param   {String}  name  Fields name
     * @return  {Array}         [{String|File}]
     */
    getAll(name) {
      return (wm(this)[name] || []).map(normalizeValue)
    }

    /**
     * Check for field name existence
     *
     * @param   {String}   name  Field name
     * @return  {boolean}
     */
    has(name) {
      return name in wm(this)
    }

    /**
     * Iterate over all fields name
     *
     * @return {Iterator}
     */
    *keys() {
      for (let [name] of this)
        yield name
    }

    /**
     * Overwrite all values given name
     *
     * @param   {String}    name      Filed name
     * @param   {String}    value     Field value
     * @param   {String=}   filename  Filename (optional)
     * @return  {Undefined}
     */
    set(name, value, filename) {
      wm(this)[name] = [[value, filename]]
    }

    /**
     * Iterate over all fields
     *
     * @return {Iterator}
     */
    *values() {
      for (let [name, value] of this)
        yield value
    }

    /**
     * Return a native (perhaps degraded) FormData with only a `append` method
     * Can throw if it's not supported
     *
     * @return {FormData}
     */
    ['_asNative']() {
      const fd = new _FormData

      for (let [name, value] of this)
        fd.append(name, value)

      return fd
    }

    /**
     * [_blob description]
     *
     * @return {Blob} [description]
     */
    ['_blob']() {
      const boundary = '----formdata-polyfill-' + Math.random()
      const chunks = []

      for (let [name, value] of this) {
        chunks.push(`--${boundary}\r\n`)

        if (value instanceof Blob) {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`,
            `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
            value,
            '\r\n'
          )
        } else {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
          )
        }
      }

      chunks.push(`--${boundary}--`)

      return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary})
    }

    /**
     * The class itself is iterable
     * alias for formdata.entries()
     *
     * @return  {Iterator}
     */
    [Symbol.iterator]() {
      return this.entries()
    }

    /**
     * Create the default string description.
     *
     * @return  {String} [object FormData]
     */
    toString() {
      return '[object FormData]'
    }
  }

  if (stringTag) {
    /**
     * Create the default string description.
     * It is accessed internally by the Object.prototype.toString().
     *
     * @return {String} FormData
     */
    FormDataPolyfill.prototype[stringTag] = 'FormData'
  }

  const decorations = [
    ['append', normalizeArgs],
    ['delete', stringify],
    ['get',    stringify],
    ['getAll', stringify],
    ['has',    stringify],
    ['set',    normalizeArgs]
  ]

  decorations.forEach(arr => {
    const orig = FormDataPolyfill.prototype[arr[0]]
    FormDataPolyfill.prototype[method] = function() {
      return orig.apply(this, arr[1](arrayFrom(arguments)))
    }
  })

  // Patch xhr's send method to call _blob transparently
  XMLHttpRequest.prototype.send = function(data) {
    _send.call(this, data instanceof FormDataPolyfill ? data['_blob']() : data)
  }

  // Patch fetch's function to call _blob transparently
  if (_fetch) {
    const _fetch = window.fetch

    window.fetch = function(input, init) {
      if (init && init.body && init.body instanceof FormDataPolyfill) {
        init.body = init.body['_blob']()
      }

      return _fetch(input, init)
    }
  }

  window['FormData'] = FormDataPolyfill
}
<|endoftext|>


<commit_before>// ==ClosureCompiler==
// @output_file_name formdata.min.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==

if (!window.FormData || !window.FormData.prototype.keys) {

  // keep a reference to native implementation
  const _FormData = window.FormData

  // To be monkey patched
  const _send = window.XMLHttpRequest.prototype.send
  const _fetch = window.Request && window.fetch

  // Unable to patch Request constructor correctly
  // const _Request = window.Request
  // only way is to use ES6 class extend
  // https://github.com/babel/babel/issues/1966

  const stringTag = window.Symbol && Symbol.toStringTag
  const map = new WeakMap
  const wm = o => map.get(o)
  const type = obj => Object.prototype.toString.call(obj).slice(8, -1)
  const arrayFrom = Array.from || (obj => [].slice.call(obj))

  // Add missing stringTags to blob and files
  if (stringTag) {
    if (!Blob.prototype[stringTag]) {
      Blob.prototype[stringTag] = 'Blob'
    }

    if ('File' in window && !File.prototype[stringTag]) {
      File.prototype[stringTag] = 'File'
    }
  }

  // Fix so you can construct your own File
  try {
    new File([], '')
  } catch (a) {
    window.File = function(b, d, c) {
      const blob = new Blob(b, c)
      const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date

      Object.defineProperties(blob, {
        name: {
          value: d
        },
        lastModifiedDate: {
          value: t
        },
        lastModified: {
          value: +t
        },
        toString: {
          value() {
            return '[object File]'
          }
        }
      })

      if (stringTag) {
        Object.defineProperty(blob, stringTag, {
          value: 'File'
        })
      }

      return blob
    }
  }

  function normalizeValue([value, filename]) {
    if (value instanceof Blob)
      // Should always returns a new File instance
      // console.assert(fd.get(x) !== fd.get(x))
      value = new File([value], filename, {
        type: value.type,
        lastModified: value.lastModified
      })

    return value
  }

  function stringify(name) {
    if (!arguments.length)
      throw new TypeError('1 argument required, but only 0 present.')

    return [name + '']
  }

  function normalizeArgs(name, value, filename) {
    if (arguments.length < 2)
      throw new TypeError(
        `2 arguments required, but only ${arguments.length} present.`
      )

    return value instanceof Blob
      // normalize name and filename if adding an attachment
      ? [name + '', value, filename !== undefined
        ? filename + '' // Cast filename to string if 3th arg isn't undefined
        : type(value) === 'File' // if it's a File
          ? value.name // Use File.name
          : 'Blob'] // otherwise fallback to Blob

      // If no attachment, just cast the args to strings
      : [name + '', value + '']
  }

  /**
   * @implements {Iterable}
   */
  class FormDataPolyfill {

    /**
     * FormData class
     *
     * @param {HTMLElement=} form
     */
    constructor(form) {
      map.set(this, Object.create(null))

      if (!form)
        return this

      for (let elm of arrayFrom(form.elements)) {
        if (!elm.name || elm.disabled) continue

        if (elm.type === 'file')
          for (let file of elm.files)
            this.append(elm.name, file)
        else if (elm.type === 'select-multiple' || elm.type === 'select-one')
          for (let elm of arrayFrom(elm.selectedOptions))
            this.append(elm.name, elm.value)
        else if (elm.type === 'checkbox' || elm.type === 'radio') {
          if (elm.checked) this.append(elm.name, elm.value)
        } else
          this.append(elm.name, elm.value)
      }
    }

    /**
     * Append a field
     *
     * @param   {String}           name      field name
     * @param   {String|Blob|File} value     string / blob / file
     * @param   {String=}          filename  filename to use with blob
     * @return  {Undefined}
     */
    append(name, value, filename) {
      const map = wm(this)

      if (!map[name])
        map[name] = []

      map[name].push([value, filename])
    }

    /**
     * Delete all fields values given name
     *
     * @param   {String}  name  Field name
     * @return  {Undefined}
     */
    delete(name) {
      delete wm(this)[name]
    }

    /**
     * Iterate over all fields as [name, value]
     *
     * @return {Iterator}
     */
    *entries() {
      const map = wm(this)

      for (let name in map)
        for (let value of map[name])
          yield [name, normalizeValue(value)]
    }

    /**
     * Iterate over all fields
     *
     * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
     * @param   {Object=}   thisArg   `this` context for callback function
     * @return  {Undefined}
     */
    forEach(callback, thisArg) {
      for (let [name, value] of this)
        callback.call(thisArg, value, name, this)
    }

    /**
     * Return first field value given name
     * or null if non existen
     *
     * @param   {String}  name      Field name
     * @return  {String|File|null}  value Fields value
     */
    get(name) {
      const map = wm(this)
      return map[name] ? normalizeValue(map[name][0]) : null
    }

    /**
     * Return all fields values given name
     *
     * @param   {String}  name  Fields name
     * @return  {Array}         [{String|File}]
     */
    getAll(name) {
      return (wm(this)[name] || []).map(normalizeValue)
    }

    /**
     * Check for field name existence
     *
     * @param   {String}   name  Field name
     * @return  {boolean}
     */
    has(name) {
      return name in wm(this)
    }

    /**
     * Iterate over all fields name
     *
     * @return {Iterator}
     */
    *keys() {
      for (let [name] of this)
        yield name
    }

    /**
     * Overwrite all values given name
     *
     * @param   {String}    name      Filed name
     * @param   {String}    value     Field value
     * @param   {String=}   filename  Filename (optional)
     * @return  {Undefined}
     */
    set(name, value, filename) {
      wm(this)[name] = [[value, filename]]
    }

    /**
     * Iterate over all fields
     *
     * @return {Iterator}
     */
    *values() {
      for (let [name, value] of this)
        yield value
    }

    /**
     * Return a native (perhaps degraded) FormData with only a `append` method
     * Can throw if it's not supported
     *
     * @return {FormData}
     */
    ['_asNative']() {
      const fd = new _FormData

      for (let [name, value] of this)
        fd.append(name, value)

      return fd
    }

    /**
     * [_blob description]
     *
     * @return {Blob} [description]
     */
    ['_blob']() {
      const boundary = '----formdata-polyfill-' + Math.random()
      const chunks = []

      for (let [name, value] of this) {
        chunks.push(`--${boundary}\r\n`)

        if (value instanceof Blob) {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`,
            `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
            value,
            '\r\n'
          )
        } else {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
          )
        }
      }

      chunks.push(`--${boundary}--`)

      return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary})
    }

    /**
     * The class itself is iterable
     * alias for formdata.entries()
     *
     * @return  {Iterator}
     */
    [Symbol.iterator]() {
      return this.entries()
    }

    /**
     * Create the default string description.
     *
     * @return  {String} [object FormData]
     */
    toString() {
      return '[object FormData]'
    }
  }

  if (stringTag) {
    /**
     * Create the default string description.
     * It is accessed internally by the Object.prototype.toString().
     *
     * @return {String} FormData
     */
    FormDataPolyfill.prototype[stringTag] = 'FormData'
  }

  for (let [method, decorator] of [
    ['append', normalizeArgs],
    ['delete', stringify],
    ['get',    stringify],
    ['getAll', stringify],
    ['has',    stringify],
    ['set',    normalizeArgs]
  ]) {
    let orig = FormDataPolyfill.prototype[method]
    FormDataPolyfill.prototype[method] = function() {
      return orig.apply(this, decorator(...arguments))
    }
  }

  // Patch xhr's send method to call _blob transparently
  XMLHttpRequest.prototype.send = function(data) {
    _send.call(this, data instanceof FormDataPolyfill ? data['_blob']() : data)
  }

  // Patch fetch's function to call _blob transparently
  if (_fetch) {
    const _fetch = window.fetch

    window.fetch = function(input, init) {
      if (init && init.body && init.body instanceof FormDataPolyfill) {
        init.body = init.body['_blob']()
      }

      return _fetch(input, init)
    }
  }

  window['FormData'] = FormDataPolyfill
}
<commit_msg>fix variable declarations<commit_after>// ==ClosureCompiler==
// @output_file_name formdata.min.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==

if (!window.FormData || !window.FormData.prototype.keys) {

  // keep a reference to native implementation
  const _FormData = window.FormData

  // To be monkey patched
  const _send = window.XMLHttpRequest.prototype.send
  const _fetch = window.Request && window.fetch

  // Unable to patch Request constructor correctly
  // const _Request = window.Request
  // only way is to use ES6 class extend
  // https://github.com/babel/babel/issues/1966

  const stringTag = window.Symbol && Symbol.toStringTag
  const map = new WeakMap
  const wm = o => map.get(o)
  const type = obj => Object.prototype.toString.call(obj).slice(8, -1)
  const arrayFrom = Array.from || (obj => [].slice.call(obj))

  // Add missing stringTags to blob and files
  if (stringTag) {
    if (!Blob.prototype[stringTag]) {
      Blob.prototype[stringTag] = 'Blob'
    }

    if ('File' in window && !File.prototype[stringTag]) {
      File.prototype[stringTag] = 'File'
    }
  }

  // Fix so you can construct your own File
  try {
    new File([], '')
  } catch (a) {
    window.File = function(b, d, c) {
      const blob = new Blob(b, c)
      const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date

      Object.defineProperties(blob, {
        name: {
          value: d
        },
        lastModifiedDate: {
          value: t
        },
        lastModified: {
          value: +t
        },
        toString: {
          value() {
            return '[object File]'
          }
        }
      })

      if (stringTag) {
        Object.defineProperty(blob, stringTag, {
          value: 'File'
        })
      }

      return blob
    }
  }

  function normalizeValue([value, filename]) {
    if (value instanceof Blob)
      // Should always returns a new File instance
      // console.assert(fd.get(x) !== fd.get(x))
      value = new File([value], filename, {
        type: value.type,
        lastModified: value.lastModified
      })

    return value
  }

  function stringify(name) {
    if (!arguments.length)
      throw new TypeError('1 argument required, but only 0 present.')

    return [name + '']
  }

  function normalizeArgs(name, value, filename) {
    if (arguments.length < 2)
      throw new TypeError(
        `2 arguments required, but only ${arguments.length} present.`
      )

    return value instanceof Blob
      // normalize name and filename if adding an attachment
      ? [name + '', value, filename !== undefined
        ? filename + '' // Cast filename to string if 3th arg isn't undefined
        : type(value) === 'File' // if it's a File
          ? value.name // Use File.name
          : 'Blob'] // otherwise fallback to Blob

      // If no attachment, just cast the args to strings
      : [name + '', value + '']
  }

  /**
   * @implements {Iterable}
   */
  class FormDataPolyfill {

    /**
     * FormData class
     *
     * @param {HTMLElement=} form
     */
    constructor(form) {
      map.set(this, Object.create(null))

      if (!form)
        return this

      for (let elm of arrayFrom(form.elements)) {
        if (!elm.name || elm.disabled) continue

        if (elm.type === 'file')
          for (let file of elm.files)
            this.append(elm.name, file)
        else if (elm.type === 'select-multiple' || elm.type === 'select-one')
          for (let opt of arrayFrom(elm.selectedOptions))
            this.append(opt.name, opt.value)
        else if (elm.type === 'checkbox' || elm.type === 'radio') {
          if (elm.checked) this.append(elm.name, elm.value)
        } else
          this.append(elm.name, elm.value)
      }
    }

    /**
     * Append a field
     *
     * @param   {String}           name      field name
     * @param   {String|Blob|File} value     string / blob / file
     * @param   {String=}          filename  filename to use with blob
     * @return  {Undefined}
     */
    append(name, value, filename) {
      const map = wm(this)

      if (!map[name])
        map[name] = []

      map[name].push([value, filename])
    }

    /**
     * Delete all fields values given name
     *
     * @param   {String}  name  Field name
     * @return  {Undefined}
     */
    delete(name) {
      delete wm(this)[name]
    }

    /**
     * Iterate over all fields as [name, value]
     *
     * @return {Iterator}
     */
    *entries() {
      const map = wm(this)

      for (let name in map)
        for (let value of map[name])
          yield [name, normalizeValue(value)]
    }

    /**
     * Iterate over all fields
     *
     * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
     * @param   {Object=}   thisArg   `this` context for callback function
     * @return  {Undefined}
     */
    forEach(callback, thisArg) {
      for (let [name, value] of this)
        callback.call(thisArg, value, name, this)
    }

    /**
     * Return first field value given name
     * or null if non existen
     *
     * @param   {String}  name      Field name
     * @return  {String|File|null}  value Fields value
     */
    get(name) {
      const map = wm(this)
      return map[name] ? normalizeValue(map[name][0]) : null
    }

    /**
     * Return all fields values given name
     *
     * @param   {String}  name  Fields name
     * @return  {Array}         [{String|File}]
     */
    getAll(name) {
      return (wm(this)[name] || []).map(normalizeValue)
    }

    /**
     * Check for field name existence
     *
     * @param   {String}   name  Field name
     * @return  {boolean}
     */
    has(name) {
      return name in wm(this)
    }

    /**
     * Iterate over all fields name
     *
     * @return {Iterator}
     */
    *keys() {
      for (let [name] of this)
        yield name
    }

    /**
     * Overwrite all values given name
     *
     * @param   {String}    name      Filed name
     * @param   {String}    value     Field value
     * @param   {String=}   filename  Filename (optional)
     * @return  {Undefined}
     */
    set(name, value, filename) {
      wm(this)[name] = [[value, filename]]
    }

    /**
     * Iterate over all fields
     *
     * @return {Iterator}
     */
    *values() {
      for (let [name, value] of this)
        yield value
    }

    /**
     * Return a native (perhaps degraded) FormData with only a `append` method
     * Can throw if it's not supported
     *
     * @return {FormData}
     */
    ['_asNative']() {
      const fd = new _FormData

      for (let [name, value] of this)
        fd.append(name, value)

      return fd
    }

    /**
     * [_blob description]
     *
     * @return {Blob} [description]
     */
    ['_blob']() {
      const boundary = '----formdata-polyfill-' + Math.random()
      const chunks = []

      for (let [name, value] of this) {
        chunks.push(`--${boundary}\r\n`)

        if (value instanceof Blob) {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`,
            `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
            value,
            '\r\n'
          )
        } else {
          chunks.push(
            `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
          )
        }
      }

      chunks.push(`--${boundary}--`)

      return new Blob(chunks, {type: 'multipart/form-data; boundary=' + boundary})
    }

    /**
     * The class itself is iterable
     * alias for formdata.entries()
     *
     * @return  {Iterator}
     */
    [Symbol.iterator]() {
      return this.entries()
    }

    /**
     * Create the default string description.
     *
     * @return  {String} [object FormData]
     */
    toString() {
      return '[object FormData]'
    }
  }

  if (stringTag) {
    /**
     * Create the default string description.
     * It is accessed internally by the Object.prototype.toString().
     *
     * @return {String} FormData
     */
    FormDataPolyfill.prototype[stringTag] = 'FormData'
  }

  for (let [method, decorator] of [
    ['append', normalizeArgs],
    ['delete', stringify],
    ['get',    stringify],
    ['getAll', stringify],
    ['has',    stringify],
    ['set',    normalizeArgs]
  ]) {
    let orig = FormDataPolyfill.prototype[method]
    FormDataPolyfill.prototype[method] = function() {
      return orig.apply(this, decorator(...arguments))
    }
  }

  // Patch xhr's send method to call _blob transparently
  XMLHttpRequest.prototype.send = function(data) {
    _send.call(this, data instanceof FormDataPolyfill ? data['_blob']() : data)
  }

  // Patch fetch's function to call _blob transparently
  if (_fetch) {
    const _fetch = window.fetch

    window.fetch = function(input, init) {
      if (init && init.body && init.body instanceof FormDataPolyfill) {
        init.body = init.body['_blob']()
      }

      return _fetch(input, init)
    }
  }

  window['FormData'] = FormDataPolyfill
}
<|endoftext|>


<commit_before>/**
 * jquery.scrollFollow.js
 * Copyright (c) 2008 Net Perspective (net-perspective.com)
 * Licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
 *
 * @author R.A. Ray
 *
 * @projectDescription	jQuery plugin for allowing an element to animate down as the user scrolls the page.
 *
 * @version 0.2.1
 *
 * @requires jquery.js (tested with 1.2.5)
 * @requires ui.core.js (tested with 1.5b4)
 * @requires jquery.easing.js (http://gsgd.co.uk/sandbox/jquery/easing/ - tested with 1.3)
 * @requires jquery.cookie.js (http://www.stilbuero.de/2006/09/17/cookie-plugin-for-jquery/)
 *
 * @param speed		int - Duration of animation (in milliseconds)
 * 								default: 500
 * @param offset			int - Number of pixels box should remain from top of viewport
 * 								default: 0
 * @param easing		string - Any one of the easing options from the easing plugin - < http://gsgd.co.uk/sandbox/jquery/easing/ > - Use 'linear' for no easing
 * 								default: 'easeOutBack'
 * @param container	string - ID of the containing div
 * 								default: box's immediate parent
 * @param killSwitch	string - ID of the On/Off toggle element
 * 								default: 'killSwitch'
 * @param onText		string - killSwitch text to be displayed if sliding is enabled
 * 								default: 'Turn Slide Off'
 * @param offText		string - killSwitch text to be displayed if sliding is disabled
 * 								default: 'Turn Slide On'
 * @param animate		string - Setting to 'no' enables a non-animated scroll following action
 * 								default: 'yes
 */

( function( $ ) {

	$.scrollFollow = function ( box, options )
	{
		var position = $( box ).css( 'position' );

		function ani( box, iTop )
		{
			$( box ).dequeue();

			var pageScroll = $( document ).scrollTop();
			var parentTop = $( cont ).offset().top;
			var parentHeight = $( cont ).height();
			var boxTop = $( box ).offset().top;
			var boxHeight = box.offsetHeight;
			var aniTop;

			if ( isActive )
			{
				aniTop = Math.min( ( Math.max( ( parentTop + iTop ), ( pageScroll + options.offset ) ) - parentTop ), ( parentHeight - boxHeight ) );

				// First options is for modern browsers
				if ( options.animate == 'yes' )
				{
					$( box ).animate(
						{
							top: aniTop
						}, options.speed, options.easing
					);

				}
				else if ( typeof document.body.style.maxHeight != "undefined" )
				{
					$( box ).css(
						{
							top: options.offset,
							position: 'fixed'
						}
					);
				}
				else
				{
					$( box ).css(
						{
							top: aniTop
						}
					);
				}
			}
		};

		// For user-initiated stopping of the slide
		var isActive = true;

		if( $.cookie( 'scrollFollowSetting' + $( box ).attr( 'id' ) ) == 'false' )
		{
			var isActive = false;

			$( '#' + options.killSwitch ).text( options.offText )
				.toggle(
					function ()
					{
						isActive = true;

						$( this ).text( options.onText );

						$.cookie( 'scrollFollowSetting' + $( box ).attr( 'id' ), true, { expires: 365, path: '/'} );
					},
					function ()
					{
						isActive = false;

						$( this ).text( options.offText );

						if( options.animate == 'yes' )
						{
							$( box ).animate(
								{
									top: initialTop
								}, options.speed, options.easing
							);
						}
						else
						{
							$( box ).css(
								{
									position: position,
									top: initialTop
								}
							);
						}

						$.cookie( 'scrollFollowSetting' + $( box ).attr( 'id' ), false, { expires: 365, path: '/'} );
					}
				);
		}
		else
		{
			$( '#' + options.killSwitch ).text( options.onText )
				.toggle(
					function ()
					{
						isActive = false;

						$( this ).text( options.offText );

						$( box ).animate(
							{
								top: initialTop
							}, 0
						);

						$.cookie( 'scrollFollowSetting' + $( box ).attr( 'id' ), false, { expires: 365, path: '/'} );
					},
					function ()
					{
						isActive = true;

						$( this ).text( options.onText );

						$.cookie( 'scrollFollowSetting' + $( box ).attr( 'id' ), true, { expires: 365, path: '/'} );
					}
				);
		}

		// If no parent ID was specified, and the immediate parent does not have an ID
		// options.container will be undefined. So we need to figure out the parent element.
		var cont;

		if ( options.container == '')
		{
			cont = $( box ).parent();
		}
		else
		{
			cont = document.getElementById( options.container );
		}

		// Finds the defaul positioning of the box.
		var initialTop = $( box ).css( 'top' );

		if( initialTop == 'auto' )
		{
			initialTop = 0;
		}
		else
		{
			initialTop = parseInt( initialTop.substr( 0, initialTop.indexOf( 'p' ) ) );
		}

		// Animated the box when the page is scrolled.
		$( window ).scroll( function ()
			{
				ani( box, initialTop );
			}
		);
	};

	$.fn.scrollFollow = function ( options )
	{
		options = options || {};
		options.speed = options.speed || 500;
		options.offset = options.offset || 0;
		options.easing = options.easing || 'easeOutBack';
		options.container = options.container || this.parent().attr( 'id' );
		options.killSwitch = options.killSwitch || 'killSwitch';
		options.onText = options.onText || 'Turn Slide Off';
		options.offText = options.offText || 'Turn Slide On';
		options.animate = options.animate || 'yes';

		this.each( function()
			{
				new $.scrollFollow( this, options );
			}
		);

		return this;
	};
})( jQuery );

<commit_msg>added new option 'relativeTo'  improved algorithm<commit_after>/**
 * jquery.scrollFollow.js
 * Copyright (c) 2008 Net Perspective (http://kitchen.net-perspective.com/)
 * Licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
 *
 * @author R.A. Ray
 *
 * @projectDescription	jQuery plugin for allowing an element to animate down as the user scrolls the page.
 *
 * @version 0.2.1
 *
 * @requires jquery.js (tested with 1.2.5)
 * @requires ui.core.js (tested with 1.6)
 * @requires jquery.easing.js (http://gsgd.co.uk/sandbox/jquery/easing/ - tested with 1.3)
 * @requires jquery.cookie.js (http://www.stilbuero.de/2006/09/17/cookie-plugin-for-jquery/)
 *
 * @param speed		int - Duration of animation (in milliseconds)
 * 								default: 500
 * @param offset			int - Number of pixels box should remain from top of viewport
 * 								default: 0
 * @param easing		string - Any one of the easing options from the easing plugin - < http://gsgd.co.uk/sandbox/jquery/easing/ > - Use 'linear' for no easing
 * 								default: 'easeOutBack'
 * @param container	string - ID of the containing div
 * 								default: box's immediate parent
 * @param killSwitch	string - ID of the On/Off toggle element
 * 								default: 'killSwitch'
 * @param onText		string - killSwitch text to be displayed if sliding is enabled
 * 								default: 'Turn Slide Off'
 * @param offText		string - killSwitch text to be displayed if sliding is disabled
 * 								default: 'Turn Slide On'
 * @param animate		string - Setting to 'no' enables a non-animated scroll following action
 * 								default: 'yes
 */

( function( $ ) {

	$.scrollFollow = function ( box, options )
	{
		// Convert box into a jQuery object
		box = $( box );

		// 'box' is the object to be animated
		var position = box.css( 'position' );

		function ani( box, iTop, iBoxOTop, padAdj )
		{
			// The script runs on every scroll which really means many times during a scroll.
			// We don't want multiple slides to queue up.
			box.dequeue();

			var docHeight = parseInt( $( document ).height() );
			var viewportHeight = parseInt( $( window ).height() );
			var pageScroll =  parseInt( $( document ).scrollTop() );
			var parentTop =  parseInt( cont.offset().top );
			var parentHeight = parseInt( cont.attr( 'offsetHeight' ) );
			var boxTop =  parseInt( box.offset().top );
			var boxHeight = parseInt( box.attr( 'offsetHeight' ) + parseInt( box.css( 'marginTop' ) ) + parseInt( box.css( 'marginBottom' ) ) );
			var aniTop;

			// Make sure the user wants the animation to happen
			if ( isActive )
			{
				// If the box should animate relative to the top of the window
				if ( options.relativeTo == 'top' )
				{
					// Don't animate until the top of the window is close enough to the top of the box
					if ( iBoxOTop >= ( pageScroll + options.offset ) )
					{
						aniTop = iTop;
					}
					else
					{
						/*console.log( 'Case 1: ' + ( -parentTop ) );
						console.log( 'Case 2: ' + ( pageScroll - iBoxOTop + iTop ) );
						console.log( 'Case 3: ' + ( parentHeight - boxHeight - padAdj ) )*/

						aniTop = Math.min( ( Math.max( ( -parentTop ), ( pageScroll - iBoxOTop + iTop ) ) + options.offset ), ( parentHeight - boxHeight - padAdj ) );

						//console.log( aniTop );
					}
				}
				// If the box should animate relative to the bottom of the window
				else if ( options.relativeTo == 'bottom' )
				{
					console.log( 'Case 1: ' + ( pageScroll + viewportHeight - boxHeight - options.offset ) );
					console.log( 'Case 2: ' + ( parentHeight - boxHeight ) );

					aniTop = Math.min( ( pageScroll + viewportHeight - boxHeight - options.offset ), ( parentHeight - boxHeight ) );

					console.log( aniTop );
				}

				// First options is for modern browsers
				if ( options.animate == 'yes' )
				{
					box.animate(
						{
							top: aniTop
						}, options.speed, options.easing
					);

				}
				else if ( typeof document.body.style.maxHeight != "undefined" )
				{
					box.css(
						{
							top: options.offset,
							position: 'fixed'
						}
					);
				}
				else
				{
					box.css(
						{
							top: aniTop
						}
					);
				}
			}
		};

		// For user-initiated stopping of the slide
		var isActive = true;

		if( $.cookie( 'scrollFollowSetting' + box.attr( 'id' ) ) == 'false' )
		{
			var isActive = false;

			$( '#' + options.killSwitch ).text( options.offText )
				.toggle(
					function ()
					{
						isActive = true;

						$( this ).text( options.onText );

						$.cookie( 'scrollFollowSetting' + box.attr( 'id' ), true, { expires: 365, path: '/'} );
					},
					function ()
					{
						isActive = false;

						$( this ).text( options.offText );

						if( options.animate == 'yes' )
						{
							box.animate(
								{
									top: initialTop
								}, options.speed, options.easing
							);
						}
						else
						{
							box.css(
								{
									position: position,
									top: initialTop
								}
							);
						}

						$.cookie( 'scrollFollowSetting' + box.attr( 'id' ), false, { expires: 365, path: '/'} );
					}
				);
		}
		else
		{
			$( '#' + options.killSwitch ).text( options.onText )
				.toggle(
					function ()
					{
						isActive = false;

						$( this ).text( options.offText );

						box.animate(
							{
								top: initialTop
							}, 0
						);

						$.cookie( 'scrollFollowSetting' + box.attr( 'id' ), false, { expires: 365, path: '/'} );
					},
					function ()
					{
						isActive = true;

						$( this ).text( options.onText );

						$.cookie( 'scrollFollowSetting' + box.attr( 'id' ), true, { expires: 365, path: '/'} );
					}
				);
		}

		// If no parent ID was specified, and the immediate parent does not have an ID
		// options.container will be undefined. So we need to figure out the parent element.
		var cont;

		if ( options.container == '')
		{
			cont = box.parent();
		}
		else
		{
			cont = $( '#' + options.container );
		}

		// Finds the defaul positioning of the box.
		var contTop = parseInt( cont.offset().top );
		var boxInitialOffsetTop =  parseInt( box.offset().top );
		var initialTop = parseInt( box.css( 'top' ) );

		// Hack to fix different treatment of boxes positioned 'absolute' and 'relative'
		var paddingAdjustment;
		if ( box.css( 'position' ) == 'relative' )
		{
			paddingAdjustment = parseInt( cont.css( 'paddingTop' ) ) + parseInt( cont.css( 'paddingBottom' ) );
		}
		else
		{
			paddingAdjustment = 0;
		}

		// Animated the box when the page is scrolled.
		$( window ).scroll( function ()
			{
				ani( box, initialTop, boxInitialOffsetTop, paddingAdjustment );
			}
		);

		ani( box, initialTop, boxInitialOffsetTop, paddingAdjustment );
	};

	$.fn.scrollFollow = function ( options )
	{
		options = options || {};
		options.relativeTo = options.relativeTo || 'top';
		options.speed = options.speed || 500;
		options.offset = options.offset || 0;
		options.easing = options.easing || 'easeOutBack';
		options.container = options.container || this.parent().attr( 'id' );
		options.killSwitch = options.killSwitch || 'killSwitch';
		options.onText = options.onText || 'Turn Slide Off';
		options.offText = options.offText || 'Turn Slide On';
		options.animate = options.animate || 'yes';

		this.each( function()
			{
				new $.scrollFollow( this, options );
			}
		);

		return this;
	};
})( jQuery );

<|endoftext|>


<commit_before>// $Id$
/*
file: autoToc.js
author: Jobi Carter
contact: keacarterdev@gmail.com
license: WTFPL
purpose: Automatically generate two table of contents on tiki pages with more than one header. The tocs are composed of clickable links to headers. One toc stays on the top of the page, while the seconds scrolls along with the page (css necessary) and hides until moused over.
last updated: 08/09/13

This script can be implimented in Tiki Wiki via editing the tiki-setup.php file and adding the following to the end of it.
	$headerlib->add_jsfile('path/to/file/here');

Generated output / What this does
	This script will generate two divs which contain a clickable table of contents for the headers on the page. One sits at the top of the page, and the other appears when you scroll past the one on the top. When you scroll past the top toc, a tab appears on the top of the page. When you mouse over this tab, a table of contents will appear. Clicking an item in the toc, or scrolling the page, will make the tab close again. The tab will be semi-transparent until it is moused over, and the toc appears. You can also click the tab to be taken to the top of the page.

disabling the toc
		turn off this script by adding a div with the id "toc-off" to a page.
		this script can be turned off for a category by adding a module with the "toc-off" div, and setting that module to only appear on that category.
		the script can be set to apply only to specific headers via the largestHeader and smallestHeader variables
		the script can be completely disabled by removing the headerLib entry in tiki-setup.php

This script does the following:
		determine if wiki page
		determine if page has the "toc-off"
		determine if more than one header on page
		insert toc HTML
		iterate over page and set Ids for header elements
			This is necessary because WYSIWYG fails to set correct Ids consistently.
		While setting header Ids, generate a HTML indented list of header anchors which point to their respective headers via header id
		set mouseover and onScroll events for two tocs

The following CSS should be applied to your theme's css file for correct behavior. You may need to adjust margin (where the toc that scrolls with you shows up) based on your theme:

	div#outerToc {
	        position:fixed;
	        top:0px;
	        margin-left:25%;
	        background:#E0E0E0;
	        display:none;
	        padding:10px;
	        border-radius:0px 0px 5px 5px;
	        z-index:5;
	}

*/

$.buildAutoToc = function () {
	//if a wiki page, and if there is no toc-off div
	if (document.getElementById("page-data") && !document.getElementById("toc-off") && document.location.href.indexOf("tiki-print.php") == -1) {
		//get headers
		headers = $("div#page-data").find(":header");

		//if there are more than one headers on the page
		if (headers.length > 1) {

			//**************Add the Toc Divs to the Page**************//

			//set target div IDs. these divs will be populated with an autoToc
			//innerToc and toc-static are required. Additional divs may be specified.
			tocDivs = ["innerToc", "toc-static"];

			//string to add html for toc divs
			tocString = "
  Table of Contents, or Click for Page Top

";

			//create the div to hold the tocs, and populate its inner HTML with the tocString
			tocContainer = document.createElement("div");
			tocContainer.innerHTML = tocString;

			//get pageData div, as we want to add toc before that
			page = document.getElementById("page-data");

			//add the tocContainer as first child of page-data div
			firstChild = page.firstChild;
			page.insertBefore(tocContainer, firstChild);

			//Configure starting header and ending header
			largestHeader = 1;
			smallestHeader = 6;

			//**************Iterate over headers and create autoToc HTML List**************//

			//start from indentation level 0, with header one as base
			previousHeaderLevel = 1;

			//create object to store processed IDs.
			var processedId = new Object();

			//function to process header Id generation. If an ID which has been processed is generated again and passed in again, the id name will be incremented to id_[1*]
			function processId(id) {
				if (id in processedId) {
					//if processed before
					//iterate count for header with this ane
					processedId[id] += 1;
					//set the new id to id plus count for header
					newId = id + "_" + processedId[id];
				} else {
					//if not processed before
					//add to "dictionary' with count of 0
					processedId[id] = 0;
					//return id passed in
					newId = id;
				}
				return newId;
			}

			//open HTML list and initialize string which will store the HTML list
			var str = '
';

			//Iterate over the headers
			for (i = 0; i < headers.length; i++) {

				//get header level for header to see if this header should be processed or not
				headerLevel = parseInt(headers[i].tagName[1]);

				//If the header level in range of acceptable headers, begin processing it
				if (headerLevel >= largestHeader && headerLevel <= smallestHeader) {

					//store browser location to append the #[1*/header text] to
					browserLocation = document.location.href;

					//Make the base URL for the link as everything up to page name plus hash. multistructure pages might have issues with this, but we are avoiding multistruction pages to avoid multicategory pages, so should be fine. For links to headers (via ids) to work, you need to have a clean url like cewiki/pagename#id, with nothing after the id string: https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview will work, but https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview&structure=es-Enrollment+Services will not.
					pointTo = browserLocation.substring(0, browserLocation.indexOf('&')) + "#";

					//current header
					currentHeader = headers[i];		// header is a reserved word in IE

					//grab the whole header element to show as the anchor text
					aText = $.trim(currentHeader.innerHTML); //trim incase inner html is text (if <hx>text</hx>)

                                        //generate and set id if necessary (if element does not already have an id, create one)
                                        if (currentHeader.id == ""){
                                        	//if the header is complicated (composed of multiple HTML Elements)
                                        	if (currentHeader.children.length > 0) {
                                        	//set the id to the the inner text of the header, with underscores instead of "<", ">", and spaces (" "). Increment Id checks if the ID has been assigned yet, and if so, increments the Id with a number at the end of the id name
                                        		id = processId($(aText).text().trim().replace(/ /g, "_").replace(/</g, "_").replace(/>/g, "_"))

                                       		//if the header is simple
                                        	} else {
                                       			//the inner html of the header is just text, so use anchor text for the id directly
                                        		id = processId(aText.replace(/ /g, "_").replace(/</g, "_").replace(/>/g, "_")) //replace all spaces with underscore, and special chars
                                        	}
                                        	//set the id on the elmeent
                                        	currentHeader.setAttribute("id", id);
                                        }

					//if the element already has an Id, just use the one it has for processing
					else {
                                        	id = currentHeader.id;
                                        }

					//construct the URL from the base URL with a # and the ID for the Div
					url = pointTo + id; //url is baseurl#divId

					//create the HTML anchor item with the text from the header and pointing to baseurl#divId
					itm = '
' + aText + '
';

					//*****use the information gathered to write the string of the HTML for the list of anchors*****//

					//The following code determines how indented the previous header was, and closes or opens sublists to match that indentation level.
					//Example:
					//h1
					//h2
					//h3
					//h1
					//Above, if it had just written the h3, and it went to write the h1, its initial indentLevel would be 1, while previous would be 3, so it would know it needs to close sublists equal to the difference beteen its indentation level and that of h3. So, it would need to close two levels from h3 to get to h1, and then write the item.
					//To do this, the code below either applies ++ or -- on the indentLevel variable while closing or opening sublists until the current indentLevel matches the previousHeaderLevel.

					indentLevel = headerLevel;

					//if header is a sub header (nested on a child level of the previous header), meaning its indent level is greater than that of the previousHeaderLevel
					if (indentLevel > previousHeaderLevel) {
						//open a new sublist for each level of difference

						//this is an alternative way to think about this
						//~ openCount = indentLevel - previousHeaderLevel
						//~ for i in range(openCount):
						//~ str +="
"
						//~ str+= item

						while (indentLevel > previousHeaderLevel) {
							//open sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel -= 1;
						}
						//when header levels match, write item
						str += itm;
					}
					//else, if header is higher up (parent level of list, but wont be a parent of the page because below)
					else if (indentLevel < previousHeaderLevel) {
						//close sublists for each level of difference
						while (indentLevel < previousHeaderLevel) {
							//close sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel += 1;
						}
						//when header levels match, write item
						str += itm;

						//elif header levels are same
					} else if (indentLevel == previousHeaderLevel) {
						//write item
						str += itm;
					}
				}
				//set current header level to previous header level for next iteration
				previousHeaderLevel = headerLevel;
			}

			//close list
			str += '
';

			//set target div's inner html to the list
			for (i = 0; i < tocDivs.length; i++) {
				document.getElementById(tocDivs[i]).innerHTML = str;
			}

			//**************Add special mouse over and scroll behavior to toc**************//
			//make it so the non-static toc shows up on top of the page when you scroll past the first static top which sits at the top of the page.
			//make it so that when you mouse over the non-static toc's outer container, the inner container shows up
			//make it so that when you scroll, the innner-toc, which holds the toc, hides, and you just see the outerToc container.

			//set onmouseover for outerToc
			document.getElementById("outerToc").onmouseover = function () {
				//closure, sets "scope/context" for function setVis on mouse over
				var inner = document.getElementById('innerToc');
				var outer = document.getElementById('outerToc');

				function setVis(inner, outer) {
					//make the outerToc container more opaque
					outer.style.opacity = 1;
					//show the inner toc div, displaying the toc
					inner.style.display = 'block';
				}

				return setVis(inner, outer);
			}

			//set the scroll functionality
			//determine where the "mobile toc" should start showing up
			static_toc = $('#outerToc-static');
			start = $(static_toc).offset().top + $(static_toc).height();

			//assign inner and outer vars for window scroll event
			var outer = $('#outerToc'); //$(div);
			var inner = $('#innerToc');

			//set on scroll event to do: on scroll, hide the floating toc if you would overlap with the toc on the top of the page, otherwise hide the inner toc
			$.event.add(window, "scroll", function () {
				//position
				var p = $(window).scrollTop();

				//if current position is below the other toc and start of the page
				if (p > start) {
					//set outertoc position to fixed at top of screen
					outer.css('display', 'block');
					//hide inner toc
					inner.css('display', 'none');
					//make outerToc slightly transparent
					var elem = document.getElementById('outerToc');
					if (elem) {
						elem.style.opacity = 0.5;
					}
				} else {
					//if current position overlaps with the static toc at top of page, hide non-static toc div
					outer.css('display', 'none');
				}
			});
		}
	}
};
$.buildAutoToc();
<commit_msg>[REF] replaced spaces with tabs in previously committed code<commit_after>// $Id$
/*
file: autoToc.js
author: Jobi Carter
contact: keacarterdev@gmail.com
license: WTFPL
purpose: Automatically generate two table of contents on tiki pages with more than one header. The tocs are composed of clickable links to headers. One toc stays on the top of the page, while the seconds scrolls along with the page (css necessary) and hides until moused over.
last updated: 08/09/13

This script can be implimented in Tiki Wiki via editing the tiki-setup.php file and adding the following to the end of it.
	$headerlib->add_jsfile('path/to/file/here');

Generated output / What this does
	This script will generate two divs which contain a clickable table of contents for the headers on the page. One sits at the top of the page, and the other appears when you scroll past the one on the top. When you scroll past the top toc, a tab appears on the top of the page. When you mouse over this tab, a table of contents will appear. Clicking an item in the toc, or scrolling the page, will make the tab close again. The tab will be semi-transparent until it is moused over, and the toc appears. You can also click the tab to be taken to the top of the page.

disabling the toc
		turn off this script by adding a div with the id "toc-off" to a page.
		this script can be turned off for a category by adding a module with the "toc-off" div, and setting that module to only appear on that category.
		the script can be set to apply only to specific headers via the largestHeader and smallestHeader variables
		the script can be completely disabled by removing the headerLib entry in tiki-setup.php

This script does the following:
		determine if wiki page
		determine if page has the "toc-off"
		determine if more than one header on page
		insert toc HTML
		iterate over page and set Ids for header elements
			This is necessary because WYSIWYG fails to set correct Ids consistently.
		While setting header Ids, generate a HTML indented list of header anchors which point to their respective headers via header id
		set mouseover and onScroll events for two tocs

The following CSS should be applied to your theme's css file for correct behavior. You may need to adjust margin (where the toc that scrolls with you shows up) based on your theme:

	div#outerToc {
		position:fixed;
		top:0px;
		margin-left:25%;
		background:#E0E0E0;
		display:none;
		padding:10px;
		border-radius:0px 0px 5px 5px;
		z-index:5;
	}

*/

$.buildAutoToc = function () {
	//if a wiki page, and if there is no toc-off div
	if (document.getElementById("page-data") && !document.getElementById("toc-off") && document.location.href.indexOf("tiki-print.php") == -1) {
		//get headers
		headers = $("div#page-data").find(":header");

		//if there are more than one headers on the page
		if (headers.length > 1) {

			//**************Add the Toc Divs to the Page**************//

			//set target div IDs. these divs will be populated with an autoToc
			//innerToc and toc-static are required. Additional divs may be specified.
			tocDivs = ["innerToc", "toc-static"];

			//string to add html for toc divs
			tocString = "
  Table of Contents, or Click for Page Top

";

			//create the div to hold the tocs, and populate its inner HTML with the tocString
			tocContainer = document.createElement("div");
			tocContainer.innerHTML = tocString;

			//get pageData div, as we want to add toc before that
			page = document.getElementById("page-data");

			//add the tocContainer as first child of page-data div
			firstChild = page.firstChild;
			page.insertBefore(tocContainer, firstChild);

			//Configure starting header and ending header
			largestHeader = 1;
			smallestHeader = 6;

			//**************Iterate over headers and create autoToc HTML List**************//

			//start from indentation level 0, with header one as base
			previousHeaderLevel = 1;

			//create object to store processed IDs.
			var processedId = new Object();

			//function to process header Id generation. If an ID which has been processed is generated again and passed in again, the id name will be incremented to id_[1*]
			function processId(id) {
				if (id in processedId) {
					//if processed before
					//iterate count for header with this ane
					processedId[id] += 1;
					//set the new id to id plus count for header
					newId = id + "_" + processedId[id];
				} else {
					//if not processed before
					//add to "dictionary' with count of 0
					processedId[id] = 0;
					//return id passed in
					newId = id;
				}
				return newId;
			}

			//open HTML list and initialize string which will store the HTML list
			var str = '
';

			//Iterate over the headers
			for (i = 0; i < headers.length; i++) {

				//get header level for header to see if this header should be processed or not
				headerLevel = parseInt(headers[i].tagName[1]);

				//If the header level in range of acceptable headers, begin processing it
				if (headerLevel >= largestHeader && headerLevel <= smallestHeader) {

					//store browser location to append the #[1*/header text] to
					browserLocation = document.location.href;

					//Make the base URL for the link as everything up to page name plus hash. multistructure pages might have issues with this, but we are avoiding multistruction pages to avoid multicategory pages, so should be fine. For links to headers (via ids) to work, you need to have a clean url like cewiki/pagename#id, with nothing after the id string: https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview will work, but https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview&structure=es-Enrollment+Services will not.
					pointTo = browserLocation.substring(0, browserLocation.indexOf('&')) + "#";

					//current header
					currentHeader = headers[i];		// header is a reserved word in IE

					//grab the whole header element to show as the anchor text
					aText = $.trim(currentHeader.innerHTML); //trim incase inner html is text (if <hx>text</hx>)

					//generate and set id if necessary (if element does not already have an id, create one)
					if (currentHeader.id == ""){
						//if the header is complicated (composed of multiple HTML Elements)
						if (currentHeader.children.length > 0) {
							//set the id to the the inner text of the header, with underscores instead of "<", ">", and spaces (" "). Increment Id checks if the ID has been assigned yet, and if so, increments the Id with a number at the end of the id name
							id = processId($(aText).text().trim().replace(/ /g, "_").replace(/</g, "_").replace(/>/g, "_"))

						//if the header is simple
						} else {
							//the inner html of the header is just text, so use anchor text for the id directly
						id = processId(aText.replace(/ /g, "_").replace(/</g, "_").replace(/>/g, "_")) //replace all spaces with underscore, and special chars
						}
						//set the id on the elmeent
						currentHeader.setAttribute("id", id);
					}

					//if the element already has an Id, just use the one it has for processing
					else {
						id = currentHeader.id;
					}

					//construct the URL from the base URL with a # and the ID for the Div
					url = pointTo + id; //url is baseurl#divId

					//create the HTML anchor item with the text from the header and pointing to baseurl#divId
					itm = '
' + aText + '
';

					//*****use the information gathered to write the string of the HTML for the list of anchors*****//

					//The following code determines how indented the previous header was, and closes or opens sublists to match that indentation level.
					//Example:
					//h1
					//h2
					//h3
					//h1
					//Above, if it had just written the h3, and it went to write the h1, its initial indentLevel would be 1, while previous would be 3, so it would know it needs to close sublists equal to the difference beteen its indentation level and that of h3. So, it would need to close two levels from h3 to get to h1, and then write the item.
					//To do this, the code below either applies ++ or -- on the indentLevel variable while closing or opening sublists until the current indentLevel matches the previousHeaderLevel.

					indentLevel = headerLevel;

					//if header is a sub header (nested on a child level of the previous header), meaning its indent level is greater than that of the previousHeaderLevel
					if (indentLevel > previousHeaderLevel) {
						//open a new sublist for each level of difference

						//this is an alternative way to think about this
						//~ openCount = indentLevel - previousHeaderLevel
						//~ for i in range(openCount):
						//~ str +="
"
						//~ str+= item

						while (indentLevel > previousHeaderLevel) {
							//open sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel -= 1;
						}
						//when header levels match, write item
						str += itm;
					}
					//else, if header is higher up (parent level of list, but wont be a parent of the page because below)
					else if (indentLevel < previousHeaderLevel) {
						//close sublists for each level of difference
						while (indentLevel < previousHeaderLevel) {
							//close sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel += 1;
						}
						//when header levels match, write item
						str += itm;

						//elif header levels are same
					} else if (indentLevel == previousHeaderLevel) {
						//write item
						str += itm;
					}
				}
				//set current header level to previous header level for next iteration
				previousHeaderLevel = headerLevel;
			}

			//close list
			str += '
';

			//set target div's inner html to the list
			for (i = 0; i < tocDivs.length; i++) {
				document.getElementById(tocDivs[i]).innerHTML = str;
			}

			//**************Add special mouse over and scroll behavior to toc**************//
			//make it so the non-static toc shows up on top of the page when you scroll past the first static top which sits at the top of the page.
			//make it so that when you mouse over the non-static toc's outer container, the inner container shows up
			//make it so that when you scroll, the innner-toc, which holds the toc, hides, and you just see the outerToc container.

			//set onmouseover for outerToc
			document.getElementById("outerToc").onmouseover = function () {
				//closure, sets "scope/context" for function setVis on mouse over
				var inner = document.getElementById('innerToc');
				var outer = document.getElementById('outerToc');

				function setVis(inner, outer) {
					//make the outerToc container more opaque
					outer.style.opacity = 1;
					//show the inner toc div, displaying the toc
					inner.style.display = 'block';
				}

				return setVis(inner, outer);
			}

			//set the scroll functionality
			//determine where the "mobile toc" should start showing up
			static_toc = $('#outerToc-static');
			start = $(static_toc).offset().top + $(static_toc).height();

			//assign inner and outer vars for window scroll event
			var outer = $('#outerToc'); //$(div);
			var inner = $('#innerToc');

			//set on scroll event to do: on scroll, hide the floating toc if you would overlap with the toc on the top of the page, otherwise hide the inner toc
			$.event.add(window, "scroll", function () {
				//position
				var p = $(window).scrollTop();

				//if current position is below the other toc and start of the page
				if (p > start) {
					//set outertoc position to fixed at top of screen
					outer.css('display', 'block');
					//hide inner toc
					inner.css('display', 'none');
					//make outerToc slightly transparent
					var elem = document.getElementById('outerToc');
					if (elem) {
						elem.style.opacity = 0.5;
					}
				} else {
					//if current position overlaps with the static toc at top of page, hide non-static toc div
					outer.css('display', 'none');
				}
			});
		}
	}
};
$.buildAutoToc();
<|endoftext|>