<commit_before>$(function() {
    if (!$('#submit-persona').length) {
        return;
    }
    initCharCount();
    initLicense();
    initPreview();
});

z.licenses = {
    'copyr': {
        'id': 7,
        'name': gettext('All Rights Reserved')
    },
    'cc-attrib': {
        'id': 9,
        'name': gettext('Creative Commons Attribution 3.0'),
        'url': 'http://creativecommons.org/licenses/by/3.0/'
    },
    'cc-attrib cc-noncom': {
        'id': 10,
        'name': gettext('Creative Commons Attribution-NonCommercial 3.0'),
        'url': 'http://creativecommons.org/licenses/by-nc/3.0/'
    },
    'cc-attrib cc-noncom cc-noderiv': {
        'id': 11,
        'name': gettext('Creative Commons Attribution-NonCommercial-NoDerivs 3.0'),
        'url': 'http://creativecommons.org/licenses/by/3.0/'
    },
    'cc-attrib cc-noncom cc-share': {
        'id': 8,
        'name': gettext('Creative Commons Attribution-NonCommercial-Share Alike 3.0'),
        'url': 'http://creativecommons.org/licenses/by-nc-sa/3.0/'
    },
    'cc-attrib cc-noderiv': {
        'id': 12,
        'name': gettext('Creative Commons Attribution-NoDerivs 3.0'),
        'url': 'http://creativecommons.org/licenses/by-nd/3.0/'
    },
    'cc-attrib cc-share': {
        'id': 13,
        'name': gettext('Creative Commons Attribution-ShareAlike 3.0'),
        'url': 'http://creativecommons.org/licenses/by/3.0/'
    }
};

function initLicense() {
    var ccClasses = {
        'cc-attrib': ['cc-attrib', 'copyr'],
        'cc-noncom': ['', 'cc-noncom'],
        'cc-noderiv': ['', 'cc-share', 'cc-noderiv']
    };
    function licenseUpdate() {
        var license = '';
        $.each(ccClasses, function(k, val) {
            v = val[parseInt($('input[name=' + k + ']:checked').val())];
            if (v) {
                var is_copyr = (v == 'copyr');
                if (k == 'cc-attrib') {
                    // Hide the other radio buttons when copyright is selected.
                    $('input[name="cc-noncom"], input[name="cc-noderiv"]').attr('disabled', is_copyr);
                }
                if (license != ' copyr') {
                    license += ' ' + v;
                }
            }
        });
        license = $.trim(license);
        var l = z.licenses[license];
        if (!l) {
            return;
        }
        var license_txt = l['name'];
        if (l['url']) {
            license_txt = format('{1}',
                                  l['url'], license_txt);
        }
        var $p = $('#persona-license');
        $p.show();
        $p.find('#cc-license').html(license_txt).attr('class', 'license icon ' + license);
        $('#id_license').val(l['id']);
    }
    $('input[name^="cc-"]').change(licenseUpdate);
    licenseUpdate();

    function saveLicense() {
        $('#persona-license-list input[value="' + $('#id_license').val() + '"]').attr('checked', true);
    }
    $('#persona-license .select-license').click(_pd(function() {
        $('#persona-license-list').show();
        $('#cc-chooser').hide();
        saveLicense();
    }));
    saveLicense();
}

function initPreview() {
    $('#submit-persona input[type="color"]').miniColors({change: updatePersona});

    $('#submit-persona').delegate('#id_name', 'change keyup paste blur', function() {
        $('#persona-preview-name').text($(this).val() || gettext("Your Persona's Name"));
    });

    var $d = $('#persona-design'),
        upload_finished = function(e) {
            $(this).closest('.row').find('.preview').removeClass('loading');
            $('#submit-persona button').attr('disabled', false);
            updatePersona();
        },
        upload_start = function(e, file) {
            var $p = $(this).closest('.row'),
                $errors = $p.find('.errorlist');
            if ($errors.length == 2) {
                $errors.eq(0).remove();
            }
            $p.find('.errorlist').html('');
            $p.find('.preview').addClass('loading').removeClass('error-loading');
            $('#submit-persona button').attr('disabled', true);
        },
        upload_success = function(e, file, upload_hash) {
            var $p = $(this).closest('.row');
            $p.find('input[type="hidden"]').val(upload_hash);
            $p.find('input[type=file], .note').hide();
            $p.find('.preview').attr('src', file.dataURL).addClass('loaded');
            updatePersona();
            $p.find('.preview, .reset').show();
        },
        upload_errors = function(e, file, errors) {
            var $p = $(this).closest('.row'),
                $errors = $p.find('.errorlist');
            $p.find('.preview').addClass('error-loading');
            $.each(errors, function(i, v) {
                $errors.append('
' + v + '
');
            });
        };

    $d.delegate('.reset', 'click', _pd(function() {
        var $p = $(this).closest('.row');
        $p.find('input[type="hidden"]').val('');
        $p.find('input[type=file], .note').show();
        $p.find('.preview').removeAttr('src').removeClass('loaded');
        updatePersona();
        $(this).hide();
    }));

    $d.delegate('input[type="file"]', 'upload_finished', upload_finished)
      .delegate('input[type="file"]', 'upload_start', upload_start)
      .delegate('input[type="file"]', 'upload_success', upload_success)
      .delegate('input[type="file"]', 'upload_errors', upload_errors)
      .delegate('input[type="file"]', 'change', function(e) {
        $(this).imageUploader();
    });

    function updatePersona() {
        var previewSrc = $('#persona-header .preview').attr('src'),
            $preview = $('#persona-preview .persona-viewer');
        if (previewSrc) {
            $preview.css('background-image', 'url(' + previewSrc + ')');
        } else {
            $preview.removeAttr('style');
        }
        var data = {'id': '0'};
        $.each(['name', 'accentcolor', 'textcolor'], function(i, v) {
            data[v] = $d.find('#id_' + v).val();
        });
        // TODO(cvan): We need to link to the CDN-served Persona images since
        //             Personas cannot reference moz-filedata URIs.
        data['header'] = data['headerURL'] = $d.find('#persona-header .preview').attr('src');
        data['footer'] = data['footerURL'] = $d.find('#persona-footer .preview').attr('src');
        $preview.attr('data-browsertheme', JSON.stringify(data));
        var accentcolor = $d.find('#id_accentcolor').siblings('a[data-color]').attr('data-color'),
            textcolor = $d.find('#id_textcolor').val();
        $preview.find('.title, .author').css({
            'background-color': format('rgba({0}, 0.7)', accentcolor),
            'color': textcolor
        });
    }
    updatePersona();
}
<commit_msg>preserve text/bg theme colors upon POST and allow HTML5 native input[type=color] to work (bug 771850)<commit_after>$(function() {
    if (!$('#submit-persona').length) {
        return;
    }
    initCharCount();
    initLicense();
    initPreview();
});

z.licenses = {
    'copyr': {
        'id': 7,
        'name': gettext('All Rights Reserved')
    },
    'cc-attrib': {
        'id': 9,
        'name': gettext('Creative Commons Attribution 3.0'),
        'url': 'http://creativecommons.org/licenses/by/3.0/'
    },
    'cc-attrib cc-noncom': {
        'id': 10,
        'name': gettext('Creative Commons Attribution-NonCommercial 3.0'),
        'url': 'http://creativecommons.org/licenses/by-nc/3.0/'
    },
    'cc-attrib cc-noncom cc-noderiv': {
        'id': 11,
        'name': gettext('Creative Commons Attribution-NonCommercial-NoDerivs 3.0'),
        'url': 'http://creativecommons.org/licenses/by/3.0/'
    },
    'cc-attrib cc-noncom cc-share': {
        'id': 8,
        'name': gettext('Creative Commons Attribution-NonCommercial-Share Alike 3.0'),
        'url': 'http://creativecommons.org/licenses/by-nc-sa/3.0/'
    },
    'cc-attrib cc-noderiv': {
        'id': 12,
        'name': gettext('Creative Commons Attribution-NoDerivs 3.0'),
        'url': 'http://creativecommons.org/licenses/by-nd/3.0/'
    },
    'cc-attrib cc-share': {
        'id': 13,
        'name': gettext('Creative Commons Attribution-ShareAlike 3.0'),
        'url': 'http://creativecommons.org/licenses/by/3.0/'
    }
};

function initLicense() {
    var ccClasses = {
        'cc-attrib': ['cc-attrib', 'copyr'],
        'cc-noncom': ['', 'cc-noncom'],
        'cc-noderiv': ['', 'cc-share', 'cc-noderiv']
    };
    function licenseUpdate() {
        var license = '';
        $.each(ccClasses, function(k, val) {
            v = val[parseInt($('input[name=' + k + ']:checked').val())];
            if (v) {
                var is_copyr = (v == 'copyr');
                if (k == 'cc-attrib') {
                    // Hide the other radio buttons when copyright is selected.
                    $('input[name="cc-noncom"], input[name="cc-noderiv"]').attr('disabled', is_copyr);
                }
                if (license != ' copyr') {
                    license += ' ' + v;
                }
            }
        });
        license = $.trim(license);
        var l = z.licenses[license];
        if (!l) {
            return;
        }
        var license_txt = l['name'];
        if (l['url']) {
            license_txt = format('{1}',
                                  l['url'], license_txt);
        }
        var $p = $('#persona-license');
        $p.show();
        $p.find('#cc-license').html(license_txt).attr('class', 'license icon ' + license);
        $('#id_license').val(l['id']);
    }
    $('input[name^="cc-"]').change(licenseUpdate);
    licenseUpdate();

    function saveLicense() {
        $('#persona-license-list input[value="' + $('#id_license').val() + '"]').attr('checked', true);
    }
    $('#persona-license .select-license').click(_pd(function() {
        $('#persona-license-list').show();
        $('#cc-chooser').hide();
        saveLicense();
    }));
    saveLicense();
}

function initPreview() {

    function hex2rgb(hex) {
        var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
        return {
            r: hex >> 16,
            g: (hex & 0x00FF00) >> 8,
            b: (hex & 0x0000FF)
        };
    }

    var $d = $('#persona-design'),
        upload_finished = function(e) {
            $(this).closest('.row').find('.preview').removeClass('loading');
            $('#submit-persona button').attr('disabled', false);
            updatePersona();
        },
        upload_start = function(e, file) {
            var $p = $(this).closest('.row'),
                $errors = $p.find('.errorlist');
            if ($errors.length == 2) {
                $errors.eq(0).remove();
            }
            $p.find('.errorlist').html('');
            $p.find('.preview').addClass('loading').removeClass('error-loading');
            $('#submit-persona button').attr('disabled', true);
        },
        upload_success = function(e, file, upload_hash) {
            var $p = $(this).closest('.row');
            $p.find('input[type="hidden"]').val(upload_hash);
            $p.find('input[type=file], .note').hide();
            $p.find('.preview').attr('src', file.dataURL).addClass('loaded');
            updatePersona();
            $p.find('.preview, .reset').show();
        },
        upload_errors = function(e, file, errors) {
            var $p = $(this).closest('.row'),
                $errors = $p.find('.errorlist');
            $p.find('.preview').addClass('error-loading');
            $.each(errors, function(i, v) {
                $errors.append('
' + v + '
');
            });
        };

    $d.delegate('.reset', 'click', _pd(function() {
        var $this = $(this),
            $p = $this.closest('.row');
        $p.find('input[type="hidden"]').val('');
        $p.find('input[type=file], .note').show();
        $p.find('.preview').removeAttr('src').removeClass('loaded');
        updatePersona();
        $this.hide();
    }));

    $d.delegate('input[type="file"]', 'upload_finished', upload_finished)
      .delegate('input[type="file"]', 'upload_start', upload_start)
      .delegate('input[type="file"]', 'upload_success', upload_success)
      .delegate('input[type="file"]', 'upload_errors', upload_errors)
      .delegate('input[type="file"]', 'change', function(e) {
        $(this).imageUploader();
    });

    function updatePersona() {
        var previewSrc = $('#persona-header .preview').attr('src'),
            $preview = $('#persona-preview .persona-viewer');
        if (previewSrc) {
            $preview.css('background-image', 'url(' + previewSrc + ')');
        } else {
            $preview.removeAttr('style');
        }
        var data = {'id': '0'};
        $.each(['name', 'accentcolor', 'textcolor'], function(i, v) {
            data[v] = $d.find('#id_' + v).val();
        });
        // TODO(cvan): We need to link to the CDN-served Persona images since
        //             Personas cannot reference moz-filedata URIs.
        data['header'] = data['headerURL'] = $d.find('#persona-header .preview').attr('src');
        data['footer'] = data['footerURL'] = $d.find('#persona-footer .preview').attr('src');
        $preview.attr('data-browsertheme', JSON.stringify(data));
        var accentcolor = $d.find('#id_accentcolor').attr('data-rgb'),
            textcolor = $d.find('#id_textcolor').val();
        $preview.find('.title, .author').css({
            'background-color': format('rgba({0}, 0.7)', accentcolor),
            'color': textcolor
        });
    }

    $('input[type=color]').on('change', function() {
        var $this = $(this),
            val = $this.val();
        if (val.indexOf('#') === 0) {
            var rgb = hex2rgb(val);
            $this.attr('data-rgb', format('{0},{1},{2}', rgb.r, rgb.g, rgb.b));
        }
        updatePersona();
    }).trigger('change');
    $('#submit-persona input[type="color"]').miniColors({change: function() {
        $('input[type=color]').trigger('change');
        updatePersona();
    }});

    $('#submit-persona').delegate('#id_name', 'change keyup paste blur', function() {
        $('#persona-preview-name').text($(this).val() || gettext("Your Persona's Name"));
    });
}
<|endoftext|>


<commit_before>import CorePlugin from 'base/core_plugin'
import Events from 'base/events'
import $ from 'clappr-zepto'

import playIcon from 'icons/01-play.svg'
import pauseIcon from 'icons/02-pause.svg'

const oldIcon = $('link[rel="shortcut icon"]')

export default class Favicon extends CorePlugin {
  get name() { return 'favicon' }
  get oldIcon() { return oldIcon }

  constructor(core) {
    super(core)
    this.configure()
  }

  configure() {
    if (this.core.options.changeFavicon) {
      if (!this.enabled) {
        this.stopListening(this.core, Events.CORE_OPTIONS_CHANGE)
        this.enable()
      }
    } else if (this.enabled) {
      this.disable()
      this.listenTo(this.core, Events.CORE_OPTIONS_CHANGE, this.configure)
    }
  }

  bindEvents() {
    this.listenTo(this.core, Events.CORE_OPTIONS_CHANGE, this.configure)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.containerChanged)
    if (this.core.mediaControl.container) {
      this.containerChanged()
    }
  }

  containerChanged() {
    this.stopListening(this.core.mediaControl.container)
    this.listenTo(this.core.mediaControl.container, Events.CONTAINER_PLAY, this.setPlayIcon)
    this.listenTo(this.core.mediaControl.container, Events.CONTAINER_PAUSE, this.setPauseIcon)
    this.listenTo(this.core.mediaControl.container, Events.CONTAINER_STOP, this.resetIcon)
    this.listenTo(this.core.mediaControl.container, Events.CONTAINER_ENDED, this.resetIcon)
    this.listenTo(this.core.mediaControl.container, Events.CONTAINER_ERROR, this.resetIcon)
    this.resetIcon()
  }

  disable() {
    super.disable()
    this.resetIcon()
  }

  destroy() {
    super.destroy()
    this.resetIcon()
  }

  createIcon(svg) {
    const canvas = $('<canvas/>')
    canvas[0].width = 16
    canvas[0].height = 16
    const ctx = canvas[0].getContext('2d')
    ctx.fillStyle = '#000'
    const d = $(svg).find('path').attr('d')
    const path = new Path2D(d)
    ctx.fill(path)
    const icon = $('<link rel="shortcut icon" type="image/png"/>')
    icon.attr('href', canvas[0].toDataURL('image/png'))
    return icon
  }

  setPlayIcon() {
    if (!this.playIcon) {
      this.playIcon = this.createIcon(playIcon)
    }
    this.changeIcon(this.playIcon)
  }

  setPauseIcon() {
    if (!this.pauseIcon) {
      this.pauseIcon = this.createIcon(pauseIcon)
    }
    this.changeIcon(this.pauseIcon)
  }

  resetIcon() {
    $('link[rel="shortcut icon"]').remove()
    $('head').append(this.oldIcon)
  }

  changeIcon(icon) {
    if (icon) {
      $('link[rel="shortcut icon"]').remove()
      $('head').append(icon)
    }
  }
}
<commit_msg>favicon: Remove event listeners from old container on change<commit_after>import CorePlugin from 'base/core_plugin'
import Events from 'base/events'
import $ from 'clappr-zepto'

import playIcon from 'icons/01-play.svg'
import pauseIcon from 'icons/02-pause.svg'

const oldIcon = $('link[rel="shortcut icon"]')

export default class Favicon extends CorePlugin {
  get name() { return 'favicon' }
  get oldIcon() { return oldIcon }

  constructor(core) {
    super(core)
    this._container = null
    this.configure()
  }

  configure() {
    if (this.core.options.changeFavicon) {
      if (!this.enabled) {
        this.stopListening(this.core, Events.CORE_OPTIONS_CHANGE)
        this.enable()
      }
    } else if (this.enabled) {
      this.disable()
      this.listenTo(this.core, Events.CORE_OPTIONS_CHANGE, this.configure)
    }
  }

  bindEvents() {
    this.listenTo(this.core, Events.CORE_OPTIONS_CHANGE, this.configure)
    this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.containerChanged)
    if (this.core.mediaControl.container) {
      this.containerChanged()
    }
  }

  containerChanged() {
    this._container && this.stopListening(this._container)
    this._container = this.core.mediaControl.container
    this.listenTo(this._container, Events.CONTAINER_PLAY, this.setPlayIcon)
    this.listenTo(this._container, Events.CONTAINER_PAUSE, this.setPauseIcon)
    this.listenTo(this._container, Events.CONTAINER_STOP, this.resetIcon)
    this.listenTo(this._container, Events.CONTAINER_ENDED, this.resetIcon)
    this.listenTo(this._container, Events.CONTAINER_ERROR, this.resetIcon)
    this.resetIcon()
  }

  disable() {
    super.disable()
    this.resetIcon()
  }

  destroy() {
    super.destroy()
    this.resetIcon()
  }

  createIcon(svg) {
    const canvas = $('<canvas/>')
    canvas[0].width = 16
    canvas[0].height = 16
    const ctx = canvas[0].getContext('2d')
    ctx.fillStyle = '#000'
    const d = $(svg).find('path').attr('d')
    const path = new Path2D(d)
    ctx.fill(path)
    const icon = $('<link rel="shortcut icon" type="image/png"/>')
    icon.attr('href', canvas[0].toDataURL('image/png'))
    return icon
  }

  setPlayIcon() {
    if (!this.playIcon) {
      this.playIcon = this.createIcon(playIcon)
    }
    this.changeIcon(this.playIcon)
  }

  setPauseIcon() {
    if (!this.pauseIcon) {
      this.pauseIcon = this.createIcon(pauseIcon)
    }
    this.changeIcon(this.pauseIcon)
  }

  resetIcon() {
    $('link[rel="shortcut icon"]').remove()
    $('head').append(this.oldIcon)
  }

  changeIcon(icon) {
    if (icon) {
      $('link[rel="shortcut icon"]').remove()
      $('head').append(icon)
    }
  }
}
<|endoftext|>


<commit_before>const webpack = require('webpack')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const cssnano = require('cssnano')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const merge = require('webpack-merge')
const common = require('./webpack.config.base')

module.exports = (env) => {
  return merge(common(), {
    plugins: [
      new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15}),
      new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000}),
      new FaviconsWebpackPlugin({logo: './favicon.png', inject: true}),
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production')
      }),
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false,
          drop_console: false
        },
        sourceMap: true,
        extractComments: true
      }),
      new webpack.optimize.AggressiveMergingPlugin(),
      new OptimizeCSSAssetsPlugin({
        cssProcessor: cssnano,
        cssProcessorOptions: {
          discardComments: {
            removeAll: true
          },
          safe: true
        },
        canPrint: false
      })
    ]
  })
}
<commit_msg>Optim<commit_after>const webpack = require('webpack')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const cssnano = require('cssnano')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const merge = require('webpack-merge')
const common = require('./webpack.config.base')

module.exports = (env) => {
  return merge(common(), {
    plugins: [
      new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15}),
      new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000}),
      new webpack.optimize.AggressiveMergingPlugin(),
      new FaviconsWebpackPlugin({logo: './favicon.png', inject: true}),
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production')
      }),
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false,
          drop_console: false
        },
        sourceMap: true,
        extractComments: true
      }),
      new OptimizeCSSAssetsPlugin({
        cssProcessor: cssnano,
        cssProcessorOptions: {
          discardComments: {
            removeAll: true
          },
          safe: true
        },
        canPrint: false
      })
    ]
  })
}
<|endoftext|>


<commit_before>if (typeof exports !== 'undefined') {
  _    = require('underscore');
}

// Util
// --------

/*!
Math.uuid.js (v1.4)
http://www.broofa.com
mailto:robert@broofa.com

Copyright (c) 2010 Robert Kieffer
Dual licensed under the MIT and GPL licenses.
*/

Math.uuid = function (prefix) {
  var chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split(''),
      uuid = [],
      radix = 16,
      len = 32;

  if (len) {
    // Compact form
    for (var i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
  } else {
    // rfc4122, version 4 form
    var r;

    // rfc4122 requires these characters
    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
    uuid[14] = '4';

    // Fill in random data.  At i==19 set the high bits of clock sequence as
    // per rfc4122, sec. 4.1.5
    for (var i = 0; i < 36; i++) {
      if (!uuid[i]) {
        r = 0 | Math.random()*16;
        uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
      }
    }
  }
  return (prefix ? prefix : "") + uuid.join('');
};

// Session
// --------
//
// Representing a document editing session

var Session = function(doc, options) {
  var that = this;
  that.users = options.users || {};
  that.doc = doc;

  // this.enter = function(user) {
  //   that.users[user.id] = { id: user.id, username: user.username, color: user.color || "red"};
  // };

  // this.leave = function(id) {
  //   delete that.users[id];
  // };

  // Update selection
  // this.select = function(options) {
  //   if (that.users[options.user].selection) {
  //     that.users[options.user].selection.forEach(function(node) {
  //       delete that.selections[node];
  //     });
  //   }

  //   that.users[options.user].selection = options.nodes;
  //   options.nodes.forEach(function(node) {
  //     that.selections[node] = options.user;
  //   });
  // };
};

// Document
// --------
//
// A generic model for representing and transforming digital documents

var Document = function(doc, schema) {
  this.model = doc;
  this.schema = schema;
  this.content = {
    properties: {},
    nodes: {},
  };

  // Store ops for redo
  this.undoneOperations = [];
};

 _.extend(Document.prototype, _.Events, {

  // TODO: error handling
  checkout: function(ref) {
    var that = this;
    // var commit =  || ref;

    // Current commit (=head)
    var commit = this.getRef(ref);
    if (!commit) return false;
    this.head = ref;

    var op = this.model.commits[commit];
    var operations = [op];

    while (op = this.model.commits[op.parent]) {
      operations.push(op);
    }

    operations.reverse();

    _.each(operations, function(op) {
      that.apply(op, true);
    });
  },

  // Get sha the current head points to
  getRef: function(ref) {
    return this.model.refs[ref];
  },

  // Go back in document history
  // --------

  undo: function() {
    // TODO: implement
  },

  // If there are any undone operations
  // --------

  redo: function() {
    // TODO: implement
  },

  // List all nodes in a document
  // --------

  list: function(fn, ctx) {
    var doc = this.content;
    function node(id) {
      return doc.nodes[id];
    }

    if (!doc.head) return;
    var current = node(doc.head);
    var index = 0;

    fn.call(ctx || doc, current, index);

    while (current = node(current.next)) {
      index += 1;
      fn.call(ctx || doc, current, index);
    }
  },

  toJSON: function() {
    return this.content;
  },

  merge: function(ref) {
    Document.merges["fast-forward"](this, ref);
  },

  // Apply a given operation on the current document state
  apply: function(operation, silent) {
    var method = operation.op[0].split(':');
    Document.methods[method[0]][method[1]](this.content, operation.op[1]);
    if (!silent) this.commit(operation);
  },

  // Create a commit for a certain operation
  commit: function(op) {
    var sha = Math.uuid();
    // console.log('committing... parent=', this.head, this.getRef(this.head));
    this.model.commits[sha] = {
      op: op.op,
      user: op.user,
      parent: this.getRef(this.head)
    };
    this.model.refs[this.head] = sha;
  }
});

// Create a new (empty) document
// --------

Document.create = function(schema) {
  var doc = {
    "id": Math.uuid(),
    "created_at": "2012-04-10T15:17:28.946Z",
    "updated_at": "2012-04-10T15:17:28.946Z",
    "nodes": {},
    "properties": {},
    "rev": 0,
  };
  return doc;
};

// Merge Strategies
// --------

Document.merges = {

  // Teh good old fast-forward merge
  "fast-forward": function(doc, ref) {
    // For the merge, operate on a temporary doc
    var mergedDoc = new Document(doc.model);
    var sha = mergedDoc.getRef(ref);

    // Checkout master
    mergedDoc.checkout('master');

    function commit(sha) {
      return mergedDoc.model.commits[sha];
    }

    // Find operations between ref and master
    var operations = [];

    while (sha && sha !== mergedDoc.getRef('master')) {
      var c = commit(sha);
      operations.push(c);
      sha = c.parent;
    }

    operations.reverse();

    // Apply those guys
    _.each(operations, function(op) {
      mergedDoc.apply(op);
    });

    doc.model = mergedDoc.model;
    doc.checkout('master');
    return true;
  }
};

// Build (rebuild) a document, based on a stream of operations
// --------

Document.methods = { node: {}, document: {}};

// Node manipulation interface
// --------

Document.methods.node = {

  insert: function(doc, options) {
    var id = options.id ? options.id : Math.uuid();

    // Construct a new document node
    var newNode = {
      id: id,
      properties: _.clone(options.properties),
      type: options.type
    };

    // TODO: validate against schema
    // validate(newNode);

    // Register new node
    doc.nodes[newNode.id] = newNode;

    // Insert position
    if (options.target === "front") {
      // This goes to the front
      var headNode = doc.nodes[doc.head];

      if (headNode) {
        newNode.next = headNode.id;
        headNode.prev = newNode.id;
      }
      newNode.prev = null;
      doc.head = newNode.id;

    } else if (!options.target || options.target === "back") {
      // This goes to the back
      var tailNode = doc.nodes[doc.tail];

      if (tailNode) {
        tailNode.next = newNode.id;
        newNode.prev = tailNode.id;
      } else { // Empty doc
        doc.head = newNode.id;
        newNode.prev = null;
      }
      newNode.next = null;
      doc.tail = newNode.id;
    } else {
      // This goes after the target node
      var targetNode = doc.nodes[options.target];
      newNode.prev = targetNode.id;
      newNode.next = targetNode.next;
      targetNode.next = newNode.id;

      // Update tail reference if necessary
      if (targetNode.id === doc.tail) doc.tail = newNode.id;
    }
  },

  update: function(doc, options) {
    // that.nodes.get(options.node).set(options.properties);
    // that.trigger('node:update', options.node);
  },

  move: function(doc, options) {
    var f  = doc.nodes[_.first(options.nodes)], // first node of selection
        l  = doc.nodes[_.last(options.nodes)], // last node of selection
        t  = doc.nodes[options.target], // target node
        fp = doc.nodes[f.prev], // first-previous
        ln = doc.nodes[l.next], // last-next
        tn = doc.nodes[t.next]; // target-next

    t.next = f.id;
    t.prev = t.prev === l.id ? (fp ? fp.id : null)
                             : (t.prev ? t.prev : null)

    if (fp) {
      fp.next = ln ? ln.id : null;
    } else { // dealing with the first node
      doc.head = ln.id; // why we had this before? doc.head = t.id;
    }

    // First node of the selection is now preceded by the target node
    f.prev = t.id;

    // Set some pointers
    if (ln) ln.prev = fp ? pf.id : null;

    // Pointers, everywhere.
    l.next = tn ? tn.id : null;

    if (tn) {
      tn.prev = l.id;
    } else { // Special case: target is tail node
      doc.tail = l.id;
    }

    // doc.trigger('node:move', options);
  },

  delete: function(doc, node) {
    console.log('deleting... NOT YET IMPLEMENTED');
  }
};

// Export Module
// --------

Document.Session = Session;

if (typeof exports !== 'undefined') {
  module.exports = Document;
} else {
  sc.models.Document = Document;
}<commit_msg>Implemented OT for text updates.<commit_after>if (typeof exports !== 'undefined') {
  var _    = require('underscore');
  var ot   = require('operational-transformation');
}

// UUID
// -----------------

/*!
Math.uuid.js (v1.4)
http://www.broofa.com
mailto:robert@broofa.com

Copyright (c) 2010 Robert Kieffer
Dual licensed under the MIT and GPL licenses.
*/

Math.uuid = function (prefix) {
  var chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split(''),
      uuid = [],
      radix = 16,
      len = 32;

  if (len) {
    // Compact form
    for (var i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
  } else {
    // rfc4122, version 4 form
    var r;

    // rfc4122 requires these characters
    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
    uuid[14] = '4';

    // Fill in random data.  At i==19 set the high bits of clock sequence as
    // per rfc4122, sec. 4.1.5
    for (var i = 0; i < 36; i++) {
      if (!uuid[i]) {
        r = 0 | Math.random()*16;
        uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
      }
    }
  }
  return (prefix ? prefix : "") + uuid.join('');
};

// _.Events
// -----------------
//
// Creates a TextOperation based on an array-based serialization

function createTextOperation(ops) {
  var operation = new ot.Operation(0)

  function map(method) {
    if (method === "ret") return "retain";
    if (method === "del") return "delete";
    if (method === "ins") return "insert";
  }

  _.each(ops, function(op) {
    operation[map(op[0])](op[1]);
  });
  return operation;
};

// _.Events
// -----------------

// Regular expression used to split event strings
var eventSplitter = /\s+/;

// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback functions
// to an event; trigger`-ing an event fires all callbacks in succession.
//
//     var object = {};
//     _.extend(object, Backbone.Events);
//     object.on('expand', function(){ alert('expanded'); });
//     object.trigger('expand');
//
_.Events = {

  // Bind one or more space separated events, `events`, to a `callback`
  // function. Passing `"all"` will bind the callback to all events fired.
  on: function(events, callback, context) {

    var calls, event, node, tail, list;
    if (!callback) return this;
    events = events.split(eventSplitter);
    calls = this._callbacks || (this._callbacks = {});

    // Create an immutable callback list, allowing traversal during
    // modification.  The tail is an empty object that will always be used
    // as the next node.
    while (event = events.shift()) {
      list = calls[event];
      node = list ? list.tail : {};
      node.next = tail = {};
      node.context = context;
      node.callback = callback;
      calls[event] = {tail: tail, next: list ? list.next : node};
    }

    return this;
  },

  // Remove one or many callbacks. If `context` is null, removes all callbacks
  // with that function. If `callback` is null, removes all callbacks for the
  // event. If `events` is null, removes all bound callbacks for all events.
  off: function(events, callback, context) {
    var event, calls, node, tail, cb, ctx;

    // No events, or removing *all* events.
    if (!(calls = this._callbacks)) return;
    if (!(events || callback || context)) {
      delete this._callbacks;
      return this;
    }

    // Loop through the listed events and contexts, splicing them out of the
    // linked list of callbacks if appropriate.
    events = events ? events.split(eventSplitter) : _.keys(calls);
    while (event = events.shift()) {
      node = calls[event];
      delete calls[event];
      if (!node || !(callback || context)) continue;
      // Create a new list, omitting the indicated callbacks.
      tail = node.tail;
      while ((node = node.next) !== tail) {
        cb = node.callback;
        ctx = node.context;
        if ((callback && cb !== callback) || (context && ctx !== context)) {
          this.on(event, cb, ctx);
        }
      }
    }

    return this;
  },

  // Trigger one or many events, firing all bound callbacks. Callbacks are
  // passed the same arguments as `trigger` is, apart from the event name
  // (unless you're listening on `"all"`, which will cause your callback to
  // receive the true name of the event as the first argument).
  trigger: function(events) {
    var event, node, calls, tail, args, all, rest;
    if (!(calls = this._callbacks)) return this;
    all = calls.all;
    events = events.split(eventSplitter);
    rest = Array.prototype.slice.call(arguments, 1);

    // For each event, walk through the linked list of callbacks twice,
    // first to trigger the event, then to trigger any `"all"` callbacks.
    while (event = events.shift()) {
      if (node = calls[event]) {
        tail = node.tail;
        while ((node = node.next) !== tail) {
          node.callback.apply(node.context || this, rest);
        }
      }
      if (node = all) {
        tail = node.tail;
        args = [event].concat(rest);
        while ((node = node.next) !== tail) {
          node.callback.apply(node.context || this, args);
        }
      }
    }

    return this;
  }

};

// Aliases for backwards compatibility.
_.Events.bind   = _.Events.on;
_.Events.unbind = _.Events.off;

// Document
// --------
//
// A generic model for representing and transforming digital documents

var Document = function(doc, schema) {
  this.model = doc;
  this.schema = schema;
  this.content = {
    properties: {},
    nodes: {},
  };

  this.callbacks = {
    "operation:applied": function() {}
  };

  // Store ops for redo
  this.undoneOperations = [];
};

 _.extend(Document.prototype, _.Events, {

  // TODO: error handling
  checkout: function(ref) {
    var that = this;
    // var commit =  || ref;

    // Current commit (=head)
    var commit = this.getRef(ref);
    if (!commit) return false;
    this.head = ref;

    var op = this.model.operations[commit];
    var operations = [op];

    while (op = this.model.operations[op.parent]) {
      operations.push(op);
    }

    operations.reverse();

    _.each(operations, function(op) {
      that.apply(op, true);
    });
  },

  // Get sha the current head points to
  getRef: function(ref) {
    return this.model.refs[ref];
  },

  // Go back in document history
  // --------

  undo: function() {
    // TODO: implement
  },

  // If there are any undone operations
  // --------

  redo: function() {
    // TODO: implement
  },

  // List all nodes in a document
  // --------

  list: function(fn, ctx) {
    var doc = this.content;
    function node(id) {
      return doc.nodes[id];
    }

    if (!doc.head) return;
    var current = node(doc.head);
    var index = 0;

    fn.call(ctx || doc, current, index);

    while (current = node(current.next)) {
      index += 1;
      fn.call(ctx || doc, current, index);
    }
  },

  toJSON: function() {
    return this.content;
  },

  merge: function(ref) {
    Document.merges["fast-forward"](this, ref);
  },

  // Apply a given operation on the current document state
  apply: function(operation, silent) {
    // var method = operation.op[0].split(':');
    Document.methods[operation.op[0]](this.content, operation.op[1]);
    if (!silent) {
      this.commit(operation);
      this.trigger('operation:applied', operation);
    }
  },

  // Create a commit for a certain operation
  commit: function(op) {
    var sha = Math.uuid();
    this.model.operations[sha] = {
      op: op.op,
      user: op.user,
      parent: this.getRef(this.head)
    };
    this.model.refs[this.head] = sha;
  }
});

// Create a new (empty) document
// --------

Document.create = function(schema) {
  var doc = {
    "id": Math.uuid(),
    "created_at": "2012-04-10T15:17:28.946Z",
    "updated_at": "2012-04-10T15:17:28.946Z",
    "nodes": {},
    "properties": {},
    "rev": 0,
  };
  return doc;
};

// Merge Strategies
// --------

Document.merges = {

  // Teh good old fast-forward merge
  "fast-forward": function(doc, ref) {
    // For the merge, operate on a temporary doc
    var mergedDoc = new Document(doc.model);
    var sha = mergedDoc.getRef(ref);

    // Checkout master
    mergedDoc.checkout('master');

    function commit(sha) {
      return mergedDoc.model.operations[sha];
    }

    // Find operations between ref and master
    var operations = [];

    while (sha && sha !== mergedDoc.getRef('master')) {
      var c = commit(sha);
      operations.push(c);
      sha = c.parent;
    }

    operations.reverse();

    // Apply those guys
    _.each(operations, function(op) {
      mergedDoc.apply(op);
    });

    doc.model = mergedDoc.model;
    doc.checkout('master');
    return true;
  }
};

// Node manipulation interface
// --------

Document.methods = {

  insert: function(doc, options) {
    var id = options.id ? options.id : Math.uuid();

    // Construct a new document node
    var newNode = _.extend(options.properties, {
      id: id,
      type: options.type
    });

    // TODO: validate against schema
    // validate(newNode);

    // Register new node
    doc.nodes[newNode.id] = newNode;

    // Insert position
    if (options.target === "front") {
      // This goes to the front
      var headNode = doc.nodes[doc.head];

      if (headNode) {
        newNode.next = headNode.id;
        headNode.prev = newNode.id;
      }
      newNode.prev = null;
      doc.head = newNode.id;

    } else if (!options.target || options.target === "back") {
      // This goes to the back
      var tailNode = doc.nodes[doc.tail];

      if (tailNode) {
        tailNode.next = newNode.id;
        newNode.prev = tailNode.id;
      } else { // Empty doc
        doc.head = newNode.id;
        newNode.prev = null;
      }
      newNode.next = null;
      doc.tail = newNode.id;
    } else {
      // This goes after the target node
      var targetNode = doc.nodes[options.target];
      newNode.prev = targetNode.id;
      newNode.next = targetNode.next;
      targetNode.next = newNode.id;

      // Update tail reference if necessary
      if (targetNode.id === doc.tail) doc.tail = newNode.id;
    }
  },

  update: function(doc, options) {
    var node = doc.nodes[options.id];
    if (node.type === "text") {
      // Use OT delta updates for text nodes
      node.content = createTextOperation(options.delta).apply(node.content);
    } else {
      delete options.id;
      _.extend(node, options);
    }
  },

  move: function(doc, options) {
    var f  = doc.nodes[_.first(options.nodes)], // first node of selection
        l  = doc.nodes[_.last(options.nodes)], // last node of selection
        t  = doc.nodes[options.target], // target node
        fp = doc.nodes[f.prev], // first-previous
        ln = doc.nodes[l.next], // last-next
        tn = doc.nodes[t.next]; // target-next

    t.next = f.id;
    t.prev = t.prev === l.id ? (fp ? fp.id : null)
                             : (t.prev ? t.prev : null)

    if (fp) {
      fp.next = ln ? ln.id : null;
    } else { // dealing with the first node
      doc.head = ln.id; // why we had this before? doc.head = t.id;
    }

    // First node of the selection is now preceded by the target node
    f.prev = t.id;

    // Set some pointers
    if (ln) ln.prev = fp ? pf.id : null;

    // Pointers, everywhere.
    l.next = tn ? tn.id : null;

    if (tn) {
      tn.prev = l.id;
    } else { // Special case: target is tail node
      doc.tail = l.id;
    }
  },

  delete: function(doc, node) {
    console.log('deleting... NOT YET IMPLEMENTED');
  }
};

// Export Module
// --------

if (typeof exports !== 'undefined') {
  module.exports = Document;
} else {
  sc.models.Document = Document;
}<|endoftext|>


<commit_before>var path   = require('path');
var fs     = require('fs');
var rimraf = require('rimraf');
var glob   = require('glob');
var _      = require('underscore');

deleteDepSources(function(err) {
  deleteBuildFiles(function(err) {
  });
});

function deleteDepSources(cb) {
  var dirPath = path.resolve(__dirname, '..', 'deps');
  rimraf(dirPath, cb);
}

function deleteBuildFiles(cb) {
  var pattern = path.resolve(__dirname, '..', 'build', '**', '*');
  glob(pattern, {nodir:true}, function(err, files) {
    if (err) {
      return cb(err);
    }

    files = _.reject(files, function(val, key) {
      return /cld\.node$/.test(val)
    });

    for (var i = 0; i < files.length; i++) {
      var file = files[i];
      fs.unlinkSync(file);
    }

    cb(null);
  });
}
<commit_msg>Don't delete deps folder post-install<commit_after>var path   = require('path');
var fs     = require('fs');
var rimraf = require('rimraf');
var glob   = require('glob');
var _      = require('underscore');

deleteBuildFiles(function(err) {
});

function deleteBuildFiles(cb) {
  var pattern = path.resolve(__dirname, '..', 'build', '**', '*');
  glob(pattern, {nodir:true}, function(err, files) {
    if (err) {
      return cb(err);
    }

    files = _.reject(files, function(val, key) {
      return /cld\.node$/.test(val)
    });

    for (var i = 0; i < files.length; i++) {
      var file = files[i];
      fs.unlinkSync(file);
    }

    cb(null);
  });
}
<|endoftext|>


<commit_before>(function() {
    'use strict';

    var strings;
    var topicHeaders;
    var initialTopic;

    /**
     * Check for feature support
     */
    function supportsBaselineJS() {
        return 'querySelectorAll' in document &&
               'addEventListener' in window;
    }

    /**
     * Adds the data choices widget(Firefox only) to the first sub-section under
     * "Firefox by default shares data to".
     * @param {Object} section - The section to which the widget
     * will be added
     */
    function addDataChoicesWidget(section) {
        var container = document.createElement('div');
        var copyContainer = document.createElement('p');
        var button = document.createElement('button');

        container.setAttribute('class', 'data-choices');

        copyContainer.textContent = strings.dataset.choicesCopy;

        button.textContent = strings.dataset.choicesButton;
        button.setAttribute('id', 'choose');
        button.setAttribute('type', 'button');
        button.className = 'mzp-c-button mzp-t-primary mzp-t-download mzp-t-small';

        container.appendChild(copyContainer);
        container.appendChild(button);

        section.appendChild(container);

        // handle clicks on the data choices "Choose" button
        $('#choose').on('click', function() {
            // if the uitour did not load, just return
            if (Mozilla.UITour === undefined) {
                return;
            }

            Mozilla.UITour.openPreferences('privacy-reports');
        });
    }

    // Don't execute if features aren't supported and client isn't desktop Firefox
    if (supportsBaselineJS() && Mozilla.Client.isFirefoxDesktop) {
        strings = document.getElementById('strings');
        topicHeaders = document.querySelectorAll('.privacy-body > div > section');
        initialTopic = topicHeaders[0].querySelector('section > *:last-child');

        // check that the UITour works (requires base/uitour-lib.js)
        if (Mozilla.UITour === undefined) {
            return;
        } else {
            Mozilla.UITour.ping(function() {
                addDataChoicesWidget(initialTopic);
            });
        }
    }

})();
<commit_msg>Privacy preferences button is missing on /privacy/firefox/ (Fixes #7758) (#7759)<commit_after>(function() {
    'use strict';

    var strings;
    var topicHeaders;
    var initialTopic;

    /**
     * Check for feature support
     */
    function supportsBaselineJS() {
        return 'querySelectorAll' in document &&
               'addEventListener' in window;
    }

    /**
     * Adds the data choices widget(Firefox only) to the first sub-section under
     * "Firefox by default shares data to".
     * @param {Object} section - The section to which the widget
     * will be added
     */
    function addDataChoicesWidget(section) {
        var container = document.createElement('div');
        var copyContainer = document.createElement('p');
        var button = document.createElement('button');

        container.setAttribute('class', 'data-choices');

        copyContainer.textContent = strings.dataset.choicesCopy;

        button.textContent = strings.dataset.choicesButton;
        button.setAttribute('id', 'choose');
        button.setAttribute('type', 'button');
        button.className = 'mzp-c-button mzp-t-primary mzp-t-download mzp-t-small';

        container.appendChild(copyContainer);
        container.appendChild(button);

        section.appendChild(container);

        // handle clicks on the data choices "Choose" button
        $('#choose').on('click', function() {
            // if the uitour did not load, just return
            if (Mozilla.UITour === undefined) {
                return;
            }

            Mozilla.UITour.openPreferences('privacy-reports');
        });
    }

    // Don't execute if features aren't supported and client isn't desktop Firefox
    if (supportsBaselineJS() && Mozilla.Client.isFirefoxDesktop) {
        strings = document.getElementById('strings');
        topicHeaders = document.querySelectorAll('.privacy-body > section');
        initialTopic = topicHeaders[0].querySelector('section > *:last-child');

        // check that the UITour works (requires base/uitour-lib.js)
        if (Mozilla.UITour === undefined) {
            return;
        } else {
            Mozilla.UITour.ping(function() {
                addDataChoicesWidget(initialTopic);
            });
        }
    }

})();
<|endoftext|>


<commit_before>"use strict";

const _ = require("lodash");
const Msg = require("../../models/msg");

module.exports = function(irc, network) {
	const client = this;

	// The following saves the channel key based on channel mode instead of
	// extracting it from `/join #channel key`. This lets us not have to
	// temporarily store the key until successful join, but also saves the key
	// if a key is set or changed while being on the channel.
	irc.on("channel info", function(data) {
		if (!data.modes) {
			return;
		}

		const targetChan = network.getChannel(data.channel);

		if (typeof targetChan === "undefined") {
			return;
		}

		data.modes.forEach((mode) => {
			const text = mode.mode;
			const add = text[0] === "+";
			const char = text[1];

			if (char === "k") {
				targetChan.key = add ? mode.param : "";
				client.save();
			}
		});

		const msg = new Msg({
			type: Msg.Type.MODE_CHANNEL,
			text: `${data.raw_modes} ${data.raw_params}`,
		});
		targetChan.pushMessage(client, msg);
	});

	irc.on("mode", function(data) {
		let targetChan;

		if (data.target === irc.user.nick) {
			targetChan = network.channels[0];
		} else {
			targetChan = network.getChannel(data.target);

			if (typeof targetChan === "undefined") {
				return;
			}
		}

		let usersUpdated;
		const userModeSortPriority = {};
		const supportsMultiPrefix = network.irc.network.cap.isEnabled("multi-prefix");

		irc.network.options.PREFIX.forEach((prefix, index) => {
			userModeSortPriority[prefix.symbol] = index;
		});

		data.modes.forEach((mode) => {
			let text = mode.mode;
			const add = text[0] === "+";
			const char = text[1];

			if (char === "k") {
				targetChan.key = add ? mode.param : "";
				client.save();
			}

			if (mode.param) {
				text += " " + mode.param;
			}

			const msg = new Msg({
				time: data.time,
				type: Msg.Type.MODE,
				from: targetChan.getUser(data.nick),
				text: text,
				self: data.nick === irc.user.nick,
			});
			targetChan.pushMessage(client, msg);

			if (!mode.param) {
				return;
			}

			const user = targetChan.findUser(mode.param);

			if (!user) {
				return;
			}

			usersUpdated = true;

			if (!supportsMultiPrefix) {
				return;
			}

			const changedMode = network.prefixLookup[char];

			if (!add) {
				_.pull(user.modes, changedMode);
			} else if (!user.modes.includes(changedMode)) {
				user.modes.push(changedMode);
				user.modes.sort(function(a, b) {
					return userModeSortPriority[a] - userModeSortPriority[b];
				});
			}

			// TODO: remove in future
			user.mode = (user.modes && user.modes[0]) || "";
		});

		if (!usersUpdated) {
			return;
		}

		if (!supportsMultiPrefix) {
			// TODO: This is horrible
			irc.raw("NAMES", data.target);
		} else {
			client.emit("users", {
				chan: targetChan.id,
			});
		}
	});
};
<commit_msg>Display the original sets mode string<commit_after>"use strict";

const _ = require("lodash");
const Msg = require("../../models/msg");

module.exports = function(irc, network) {
	const client = this;

	// The following saves the channel key based on channel mode instead of
	// extracting it from `/join #channel key`. This lets us not have to
	// temporarily store the key until successful join, but also saves the key
	// if a key is set or changed while being on the channel.
	irc.on("channel info", function(data) {
		if (!data.modes) {
			return;
		}

		const targetChan = network.getChannel(data.channel);

		if (typeof targetChan === "undefined") {
			return;
		}

		data.modes.forEach((mode) => {
			const text = mode.mode;
			const add = text[0] === "+";
			const char = text[1];

			if (char === "k") {
				targetChan.key = add ? mode.param : "";
				client.save();
			}
		});

		const msg = new Msg({
			type: Msg.Type.MODE_CHANNEL,
			text: `${data.raw_modes} ${data.raw_params.join(" ")}`,
		});
		targetChan.pushMessage(client, msg);
	});

	irc.on("mode", function(data) {
		let targetChan;

		if (data.target === irc.user.nick) {
			targetChan = network.channels[0];
		} else {
			targetChan = network.getChannel(data.target);

			if (typeof targetChan === "undefined") {
				return;
			}
		}

		const msg = new Msg({
			time: data.time,
			type: Msg.Type.MODE,
			from: targetChan.getUser(data.nick),
			text: `${data.raw_modes} ${data.raw_params.join(" ")}`,
			self: data.nick === irc.user.nick,
		});
		targetChan.pushMessage(client, msg);

		let usersUpdated;
		const userModeSortPriority = {};
		const supportsMultiPrefix = network.irc.network.cap.isEnabled("multi-prefix");

		irc.network.options.PREFIX.forEach((prefix, index) => {
			userModeSortPriority[prefix.symbol] = index;
		});

		data.modes.forEach((mode) => {
			const add = mode.mode[0] === "+";
			const char = mode.mode[1];

			if (char === "k") {
				targetChan.key = add ? mode.param : "";
				client.save();
			}

			if (!mode.param) {
				return;
			}

			const user = targetChan.findUser(mode.param);

			if (!user) {
				return;
			}

			usersUpdated = true;

			if (!supportsMultiPrefix) {
				return;
			}

			const changedMode = network.prefixLookup[char];

			if (!add) {
				_.pull(user.modes, changedMode);
			} else if (!user.modes.includes(changedMode)) {
				user.modes.push(changedMode);
				user.modes.sort(function(a, b) {
					return userModeSortPriority[a] - userModeSortPriority[b];
				});
			}

			// TODO: remove in future
			user.mode = (user.modes && user.modes[0]) || "";
		});

		if (!usersUpdated) {
			return;
		}

		if (!supportsMultiPrefix) {
			// TODO: This is horrible
			irc.raw("NAMES", data.target);
		} else {
			client.emit("users", {
				chan: targetChan.id,
			});
		}
	});
};
<|endoftext|>


<commit_before>// @AngularClass

/*
 * Helper: root(), and rootDir() are defined at the bottom
 */
var path = require('path');
// Webpack Plugins
var ProvidePlugin = require('webpack/lib/ProvidePlugin');
var DefinePlugin  = require('webpack/lib/DefinePlugin');
var ENV = process.env.ENV = process.env.NODE_ENV = 'test';

/*
 * Config
 */
module.exports = {
  resolve: {
    cache: false,
    extensions: prepend(['.ts','.js','.json','.css','.html'], '.async') // ensure .async.ts etc also works
  },
  devtool: 'inline-source-map',
  module: {
    preLoaders: [
      {
        test: /\.ts$/,
        loader: 'tslint-loader',
        exclude: [
          root('node_modules')
        ]
      },
      {
        test: /\.js$/,
        loader: "source-map-loader",
        exclude: [
          root('node_modules/rxjs')
        ]
      }
    ],
    loaders: [
      {
        test: /\.async\.ts$/,
        loaders: ['es6-promise-loader', 'ts-loader'],
        exclude: [ /\.(spec|e2e)\.ts$/ ]
      },
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        query: {
          "compilerOptions": {
            "noEmitHelpers": true,
            "removeComments": true,
          }
        },
        exclude: [ /\.e2e\.ts$/ ]
      },
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.html$/, loader: 'raw-loader' },
      { test: /\.css$/,  loader: 'raw-loader' }
    ],
    postLoaders: [
      // instrument only testing sources with Istanbul
      {
        test: /\.(js|ts)$/,
        include: root('src'),
        loader: 'istanbul-instrumenter-loader',
        exclude: [
          /\.e2e\.ts$/,
          /node_modules/
        ]
      }
    ],
    noParse: [
      root('zone.js/dist'),
      root('angular2/bundles')
    ]
  },
  stats: { colors: true, reasons: true },
  debug: false,
  plugins: [
    new DefinePlugin({
      // Environment helpers
      'process.env': {
        'ENV': JSON.stringify(ENV),
        'NODE_ENV': JSON.stringify(ENV)
      }
    }),
    new ProvidePlugin({
      // TypeScript helpers
      '__metadata': 'ts-helper/metadata',
      '__decorate': 'ts-helper/decorate',
      '__awaiter': 'ts-helper/awaiter',
      '__extends': 'ts-helper/extends',
      '__param': 'ts-helper/param',
      'Reflect': 'es7-reflect-metadata/src/global/browser'
    })
  ],
    // we need this due to problems with es6-shim
  node: {
    global: 'window',
    progress: false,
    crypto: 'empty',
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
};

// Helper functions

function root(args) {
  args = Array.prototype.slice.call(arguments, 0);
  return path.join.apply(path, [__dirname].concat(args));
}

function rootNode(args) {
  args = Array.prototype.slice.call(arguments, 0);
  return root.apply(path, ['node_modules'].concat(args));
}

function prepend(extensions, args) {
  args = args || [];
  if (!Array.isArray(args)) { args = [args] }
  return extensions.reduce(function(memo, val) {
    return memo.concat(val, args.map(function(prefix) {
      return prefix + val
    }));
  }, ['']);
}
<commit_msg>Exclude *.spec.ts files from being listed in coverage reports<commit_after>// @AngularClass

/*
 * Helper: root(), and rootDir() are defined at the bottom
 */
var path = require('path');
// Webpack Plugins
var ProvidePlugin = require('webpack/lib/ProvidePlugin');
var DefinePlugin  = require('webpack/lib/DefinePlugin');
var ENV = process.env.ENV = process.env.NODE_ENV = 'test';

/*
 * Config
 */
module.exports = {
  resolve: {
    cache: false,
    extensions: prepend(['.ts','.js','.json','.css','.html'], '.async') // ensure .async.ts etc also works
  },
  devtool: 'inline-source-map',
  module: {
    preLoaders: [
      {
        test: /\.ts$/,
        loader: 'tslint-loader',
        exclude: [
          root('node_modules')
        ]
      },
      {
        test: /\.js$/,
        loader: "source-map-loader",
        exclude: [
          root('node_modules/rxjs')
        ]
      }
    ],
    loaders: [
      {
        test: /\.async\.ts$/,
        loaders: ['es6-promise-loader', 'ts-loader'],
        exclude: [ /\.(spec|e2e)\.ts$/ ]
      },
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        query: {
          "compilerOptions": {
            "noEmitHelpers": true,
            "removeComments": true,
          }
        },
        exclude: [ /\.e2e\.ts$/ ]
      },
      { test: /\.json$/, loader: 'json-loader' },
      { test: /\.html$/, loader: 'raw-loader' },
      { test: /\.css$/,  loader: 'raw-loader' }
    ],
    postLoaders: [
      // instrument only testing sources with Istanbul
      {
        test: /\.(js|ts)$/,
        include: root('src'),
        loader: 'istanbul-instrumenter-loader',
        exclude: [
          /\.(e2e\spec)\.ts$/,
          /node_modules/
        ]
      }
    ],
    noParse: [
      root('zone.js/dist'),
      root('angular2/bundles')
    ]
  },
  stats: { colors: true, reasons: true },
  debug: false,
  plugins: [
    new DefinePlugin({
      // Environment helpers
      'process.env': {
        'ENV': JSON.stringify(ENV),
        'NODE_ENV': JSON.stringify(ENV)
      }
    }),
    new ProvidePlugin({
      // TypeScript helpers
      '__metadata': 'ts-helper/metadata',
      '__decorate': 'ts-helper/decorate',
      '__awaiter': 'ts-helper/awaiter',
      '__extends': 'ts-helper/extends',
      '__param': 'ts-helper/param',
      'Reflect': 'es7-reflect-metadata/src/global/browser'
    })
  ],
    // we need this due to problems with es6-shim
  node: {
    global: 'window',
    progress: false,
    crypto: 'empty',
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
};

// Helper functions

function root(args) {
  args = Array.prototype.slice.call(arguments, 0);
  return path.join.apply(path, [__dirname].concat(args));
}

function rootNode(args) {
  args = Array.prototype.slice.call(arguments, 0);
  return root.apply(path, ['node_modules'].concat(args));
}

function prepend(extensions, args) {
  args = args || [];
  if (!Array.isArray(args)) { args = [args] }
  return extensions.reduce(function(memo, val) {
    return memo.concat(val, args.map(function(prefix) {
      return prefix + val
    }));
  }, ['']);
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

exec = require("child_process").exec;
nodejs = '"' + process.execPath + '"  ';
exec( nodejs + ' ./node_modules/coffee-script/bin/coffee --compile --bare --no-header .', require_compile);

function require_compile(err) {
  if (err) {
    console.error(err)
    process.exit(2);
  } else {
    exec(nodejs + ' ./node_modules/light-require-compile/bin/light-require-compile.js ./lib/Client.js ./lib/tpl/static/bundle.js',uglifyjs);
  }
}

function uglifyjs(err) {
  if (err) {
    console.log(err);
    process.exit(3);
  } else {
    exec(nodejs + ' ./node_modules/uglifyjs/bin/uglifyjs ./lib/tpl/static/bundle.js -o ./lib/tpl/static/bundle.min.js --compress --mangle');
  }
}
<commit_msg>Fixed:node exec on windows<commit_after>#!/usr/bin/env node

exec = require("child_process").exec;
//nodejs = '"' + process.execPath + '"  ';
nodejs = 'node ';
exec( nodejs + ' ./node_modules/coffee-script/bin/coffee --compile --bare --no-header .', require_compile);

function require_compile(err) {
  if (err) {
    console.log(err)
    process.exit(2);
  } else {
    exec(nodejs + ' ./node_modules/light-require-compile/bin/light-require-compile.js ./lib/Client.js ./lib/tpl/static/bundle.js',uglifyjs);
  }
}

function uglifyjs(err) {
  if (err) {
    console.log(err);
    process.exit(3);
  } else {
    exec(nodejs + ' ./node_modules/uglifyjs/bin/uglifyjs ./lib/tpl/static/bundle.js -o ./lib/tpl/static/bundle.min.js --compress --mangle');
  }
}
<|endoftext|>


<commit_before>// TODO(drheld): Count duchies / dukes / islands here.
var special_counts = new Object();

var scores = new Object();
var decks = new Object();

var deck_spot;
var points_spot;
var started = false;
var last_player = "";
var last_reveal_card = "";

function DebugString() {
  return "[Scores: " + JSON.stringify(scores) + "] " +
         "[Cards: " + JSON.stringify(decks) + "]";
}

function PointsForCard(card) {
  if (card == undefined) {
    alert("Undefined card for points...");
    return;
  }
  if (card.indexOf("Colony") == 0) return 10;
  if (card.indexOf("Province") == 0) return 6;
  if (card.indexOf("Duchy") == 0) return 3;
  if (card.indexOf("Estate") == 0) return 1;
  if (card.indexOf("Curse") == 0) return -1;

  if (card.indexOf("Island") == 0) return 2;
  if (card.indexOf("Nobles") == 0) return 2;
  if (card.indexOf("Harem") == 0) return 2;
  if (card.indexOf("Great Hall") == 0) return 1;

  return 0;
}

function GainCard(player, card, count) {
  if (player == null) return;

  if (typeof decks[player] == "undefined") {
    decks[player] = 10;
  }
  if (typeof scores[player] == "undefined") {
    scores[player] = 3;
  }

  scores[player] = scores[player] + PointsForCard(card) * count;
  decks[player] = decks[player] + count;
}

function findTrailingPlayer(text) {
  var arr = text.match(/ ([A-Za-z0-9]+)\./);
  if (arr.length == 2) {
    return arr[1];
  }
  return null;
}

function MaybeHandleTurnChange(text) {
  if (text.indexOf("---") != -1) {
    // This must be a turn start.
    if (text.match(/Your turn/) != null) {
      last_player = "You";
    } else {
      var arr = text.match(/--- (.+)'s turn ---/);
      if (arr != null && arr.length == 2) {
        last_player = arr[1];
      } else {
        alert("Couldn't handle turn change: " + text);
      }
    }
    return true;
  }
  return false;
}

function MaybeReturnToSupply(text) {
  if (text.indexOf("returning it to the supply") != -1) {
    GainCard(last_player, last_reveal_card, -1);
    return true;
  } else {
    var arr = text.match("([0-9]*) copies to the supply");
    if (arr != null && arr.length == 2) {
      GainCard(last_player, last_reveal_card, -arr[1]);
      return true;
    }
  }
  return false;
}

function MaybeHandleSwindler(elems, text) {
  if (text.indexOf("replacing your") != -1) {
    if (elems.length == 2) {
      changeScore("You", -PointsForCard(elems[0].innerText));
      changeScore("You", PointsForCard(elems[1].innerText));
    } else {
      alert("Replacing your has " + elems.length + " elements.");
    }
    return true;
  }
  if (text.indexOf("You replace") != -1) {
    if (elems.length == 2) {
      var arr = text.match("You replace ([^']*)'");
      if (arr != null && arr.length == 2) {
        changeScore(arr[1], -PointsForCard(elems[0].innerText));
        changeScore(arr[1], PointsForCard(elems[1].innerText));
      } else {
        alert("Could not split: " + text);
      }
    } else {
      alert("Replacing your has " + elems.length + " elements.");
    }
    return true;
  }
  return false;
}

function handleLogEntry(node) {
  elems = node.getElementsByTagName("span");
  if (elems.length == 0) {
    if (MaybeHandleTurnChange(node.innerText)) return;
    if (MaybeReturnToSupply(node.innerText)) return;
    return;
  }

  if (MaybeHandleSwindler(elems, node.innerText)) return;

  // Remove leading stuff from the text.
  var text = node.innerText.split(" ");
  var i = 0;
  for (i = 0; i < text.length; i++) {
    if (text[i].match(/[A-Za-z0-9]/) != null) break;
  }
  if (i == text.length) return;
  text = text.slice(i);

  if (text[0] == "trashing" ||  text[1] == "trash") {
    for (elem in elems) {
      if (elems[elem].innerText != undefined) {
        var card = elems[elem].innerText;
        var count = 1;
        var re = new RegExp("([0-9]+) " + card);
        var arr = (node.innerText.match(re));
        if (arr != null && arr.length == 2) {
          count = arr[1];
        }
        GainCard(last_player, card, -count);
      }
    }
    return;
  }

  // Expect one element from here on out.
  if (elems.length > 1) return;

  // It's a single card action.
  var card = elems[0].innerText;

  if (text[0].indexOf("gaining") == 0) {
    GainCard(last_player, card, 1);
    return;
  }

  var player = text[0];
  var action = text[1];
  var delta = 0;
  if (action.indexOf("buy") == 0 || action.indexOf("gain") == 0) {
    GainCard(player, card, 1);
  } else if (action.indexOf("pass") == 0) {
    GainCard(player, card, -1);
    var other_player = findTrailingPlayer(node.innerText);
    GainCard(other_player, card, 1);
  } else if (action.indexOf("receive") == 0) {
    GainCard(player, card, 1);
    var other_player = findTrailingPlayer(node.innerText);
    GainCard(other_player, card, -1);
  } else if (action.indexOf("reveal") == 0) {
    last_reveal_card = card;
  }
}

function UpdateScores() {
  if (points_spot == undefined) return;
  var print_scores = "Points: "
  for (var score in scores) {
    print_scores = print_scores + " " + score + "=" + scores[score];
  }
  points_spot.innerHTML = print_scores;
}

function UpdateDeck() {
  if (deck_spot == undefined) return;
  var print_deck = "Cards: "
  for (var deck in decks) {
    print_deck = print_deck + " " + deck + "=" + decks[deck];
  }
  deck_spot.innerHTML = print_deck;
}

function initialize() {
  started = true;
  special_counts = new Object();
  scores = new Object();
  decks = new Object();

  UpdateScores();
  UpdateDeck();
}

function handle(doc) {
  if (doc.constructor == HTMLDivElement &&
      doc.innerText.indexOf("Say") == 0) {
    initialize();
    deck_spot = doc.children[5];
    points_spot = doc.children[6];
  }

  if (doc.constructor == HTMLElement && doc.parentNode.id == "log" &&
      doc.innerText.indexOf("Turn order") != -1) {
    initialize();
  }

  if (started && doc.constructor == HTMLElement && doc.parentNode.id == "log") {
    handleLogEntry(doc);
  }

  if (started) {
    UpdateScores();
    UpdateDeck();
  }
}

document.body.addEventListener('DOMNodeInserted', function(ev) {
  handle(ev.target);
});
<commit_msg>Handle gaining multiple cards at once (for example from treasure map) and switch to more standardized javascript naming conventions.<commit_after>// TODO(drheld): Count duchies / dukes / islands here.
var special_counts = new Object();

var scores = new Object();
var decks = new Object();

var deck_spot;
var points_spot;
var started = false;
var last_player = "";
var last_reveal_card = "";

function debugString() {
  return "[Scores: " + JSON.stringify(scores) + "] " +
         "[Cards: " + JSON.stringify(decks) + "]";
}

function pointsForCard(card) {
  if (card == undefined) {
    alert("Undefined card for points...");
    return;
  }
  if (card.indexOf("Colony") == 0) return 10;
  if (card.indexOf("Province") == 0) return 6;
  if (card.indexOf("Duchy") == 0) return 3;
  if (card.indexOf("Estate") == 0) return 1;
  if (card.indexOf("Curse") == 0) return -1;

  if (card.indexOf("Island") == 0) return 2;
  if (card.indexOf("Nobles") == 0) return 2;
  if (card.indexOf("Harem") == 0) return 2;
  if (card.indexOf("Great Hall") == 0) return 1;

  return 0;
}

function gainCard(player, card, count) {
  if (player == null) return;
  count = parseInt(count);

  if (typeof decks[player] == "undefined") {
    decks[player] = 10;
  }
  if (typeof scores[player] == "undefined") {
    scores[player] = 3;
  }

  scores[player] = scores[player] + pointsForCard(card) * count;
  decks[player] = decks[player] + count;
}

function findTrailingPlayer(text) {
  var arr = text.match(/ ([A-Za-z0-9]+)\./);
  if (arr.length == 2) {
    return arr[1];
  }
  return null;
}

function maybeHandleTurnChange(text) {
  if (text.indexOf("---") != -1) {
    // This must be a turn start.
    if (text.match(/Your turn/) != null) {
      last_player = "You";
    } else {
      var arr = text.match(/--- (.+)'s turn ---/);
      if (arr != null && arr.length == 2) {
        last_player = arr[1];
      } else {
        alert("Couldn't handle turn change: " + text);
      }
    }
    return true;
  }
  return false;
}

function maybeReturnToSupply(text) {
  if (text.indexOf("returning it to the supply") != -1) {
    gainCard(last_player, last_reveal_card, -1);
    return true;
  } else {
    var arr = text.match("([0-9]*) copies to the supply");
    if (arr != null && arr.length == 2) {
      gainCard(last_player, last_reveal_card, -arr[1]);
      return true;
    }
  }
  return false;
}

function maybeHandleSwindler(elems, text) {
  if (text.indexOf("replacing your") != -1) {
    if (elems.length == 2) {
      changeScore("You", -pointsForCard(elems[0].innerText));
      changeScore("You", pointsForCard(elems[1].innerText));
    } else {
      alert("Replacing your has " + elems.length + " elements.");
    }
    return true;
  }
  if (text.indexOf("You replace") != -1) {
    if (elems.length == 2) {
      var arr = text.match("You replace ([^']*)'");
      if (arr != null && arr.length == 2) {
        changeScore(arr[1], -pointsForCard(elems[0].innerText));
        changeScore(arr[1], pointsForCard(elems[1].innerText));
      } else {
        alert("Could not split: " + text);
      }
    } else {
      alert("Replacing your has " + elems.length + " elements.");
    }
    return true;
  }
  return false;
}

function getCardCount(card, text) {
  var count = 1;
  var re = new RegExp("([0-9]+) " + card);
  var arr = (text.match(re));
  if (arr != null && arr.length == 2) {
    count = arr[1];
  }
  return count;
}

function handleLogEntry(node) {
  elems = node.getElementsByTagName("span");
  if (elems.length == 0) {
    if (maybeHandleTurnChange(node.innerText)) return;
    if (maybeReturnToSupply(node.innerText)) return;
    return;
  }

  if (maybeHandleSwindler(elems, node.innerText)) return;

  // Remove leading stuff from the text.
  var text = node.innerText.split(" ");
  var i = 0;
  for (i = 0; i < text.length; i++) {
    if (text[i].match(/[A-Za-z0-9]/) != null) break;
  }
  if (i == text.length) return;
  text = text.slice(i);

  if (text[0] == "trashing" ||  text[1] == "trash") {
    for (elem in elems) {
      if (elems[elem].innerText != undefined) {
        var card = elems[elem].innerText;
        var count = getCardCount(card, node.innerText);
        gainCard(last_player, card, -count);
      }
    }
    return;
  }

  // Expect one element from here on out.
  if (elems.length > 1) return;

  // It's a single card action.
  var card = elems[0].innerText;

  if (text[0].indexOf("gaining") == 0) {
    var count = getCardCount(card, node.innerText);
    gainCard(last_player, card, count);
    return;
  }

  var player = text[0];
  var action = text[1];
  var delta = 0;
  if (action.indexOf("buy") == 0 || action.indexOf("gain") == 0) {
    var count = getCardCount(card, node.innerText);
    gainCard(player, card, count);
  } else if (action.indexOf("pass") == 0) {
    gainCard(player, card, -1);
    var other_player = findTrailingPlayer(node.innerText);
    gainCard(other_player, card, 1);
  } else if (action.indexOf("receive") == 0) {
    gainCard(player, card, 1);
    var other_player = findTrailingPlayer(node.innerText);
    gainCard(other_player, card, -1);
  } else if (action.indexOf("reveal") == 0) {
    last_reveal_card = card;
  }
}

function updateScores() {
  if (points_spot == undefined) return;
  var print_scores = "Points: "
  for (var score in scores) {
    print_scores = print_scores + " " + score + "=" + scores[score];
  }
  points_spot.innerHTML = print_scores;
}

function updateDeck() {
  if (deck_spot == undefined) return;
  var print_deck = "Cards: "
  for (var deck in decks) {
    print_deck = print_deck + " " + deck + "=" + decks[deck];
  }
  deck_spot.innerHTML = print_deck;
}

function initialize() {
  started = true;
  special_counts = new Object();
  scores = new Object();
  decks = new Object();

  updateScores();
  updateDeck();
}

function handle(doc) {
  if (doc.constructor == HTMLDivElement &&
      doc.innerText.indexOf("Say") == 0) {
    initialize();
    deck_spot = doc.children[5];
    points_spot = doc.children[6];
  }

  if (doc.constructor == HTMLElement && doc.parentNode.id == "log" &&
      doc.innerText.indexOf("Turn order") != -1) {
    initialize();
  }

  if (started && doc.constructor == HTMLElement && doc.parentNode.id == "log") {
    handleLogEntry(doc);
  }

  if (started) {
    updateScores();
    updateDeck();
  }
}

document.body.addEventListener('DOMNodeInserted', function(ev) {
  handle(ev.target);
});
<|endoftext|>


<commit_before>/*
This file is part of WebNES.

WebNES is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

WebNES is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with WebNES.  If not, see <http://www.gnu.org/licenses/>.
*/

this.Gui = this.Gui || {};

(function(){
	"use strict";

	// Must be power of 2
	var TEXTURE_WIDTH = 256;
	var TEXTURE_HEIGHT = 256;

	var getGlContext = function( canvas ) {
		return canvas.getContext("webgl") || canvas.getContext("experimental-webgl")
	};

	var WebGlVertexBuffer = function( glContext ) {

		this._glContext = glContext;
		this._itemSize = 0;
		this._itemCount = 0;
		this._buffer = this._glContext.createBuffer();
	};

	WebGlVertexBuffer.prototype.setData = function( vertices, itemSize, itemCount ) {

		// ELEMENT_ARRAY_BUFFER is used by index buffer, ARRAY_BUFFER by vertex and tex coord buffers
		this._itemSize = itemSize;
		this._itemCount = itemCount;
		this._glContext.bindBuffer( this._glContext.ARRAY_BUFFER, this._buffer );
		this._glContext.bufferData( this._glContext.ARRAY_BUFFER, vertices, this._glContext.STATIC_DRAW );
	};

	WebGlVertexBuffer.prototype.bind = function( positionAttribute ) {
		this._glContext.bindBuffer( this._glContext.ARRAY_BUFFER, this._buffer );
		this._glContext.vertexAttribPointer( positionAttribute, this._itemSize, this._glContext.FLOAT, false, 0, 0 );
	};

	var WebGlIndexBuffer = function( glContext ) {

		this._glContext = glContext;
		this._itemCount = 0;
		this._buffer = this._glContext.createBuffer();
	};

	WebGlIndexBuffer.prototype.setData = function( indices, itemCount ) {
		this._itemCount = itemCount;
		this._glContext.bindBuffer( this._glContext.ELEMENT_ARRAY_BUFFER, this._buffer );
		this._glContext.bufferData( this._glContext.ELEMENT_ARRAY_BUFFER, indices, this._glContext.STATIC_DRAW );
	};

	WebGlIndexBuffer.prototype.bind = function() {
		this._glContext.bindBuffer(this._glContext.ELEMENT_ARRAY_BUFFER, this._buffer);
	};

	WebGlIndexBuffer.prototype.draw = function() {
		this._glContext.drawElements(this._glContext.TRIANGLES, this._itemCount, this._glContext.UNSIGNED_SHORT, 0);
	};

	var WebGlRenderSurface = function( canvasParent ) {

		var that = this;

		this._cubeVertexPositionBuffer = null;
		this._cubeVertexTextureCoordBuffer = null;
		this._cubeVertexIndexBuffer = null;

		this._clearArray = new Uint32Array( SCREEN_WIDTH * SCREEN_HEIGHT );
		this._clearArrayColour = this._clearArray[0];

		this._bufferIndexArray = new Int32Array( SCREEN_WIDTH * SCREEN_HEIGHT );
		this._offscreen32BitView = new Uint32Array( TEXTURE_WIDTH * TEXTURE_HEIGHT );
		this._offscreen8BitView = new Uint8Array( this._offscreen32BitView.buffer );

		this._element = canvasParent.getCanvasElement();
		this._glContext = getGlContext( this._element );

		this._initShaders();
		this._initBuffers();
		this._initTexture();
		this._initView();

		this._vertexBuffer.bind( this._shaderProgram.vertexPositionAttribute );
		this._textureCoordBuffer.bind( this._shaderProgram._cubeVertexTextureCoordBuffer );
		this._indexBuffer.bind();

		this._glContext.activeTexture(this._glContext.TEXTURE0);
		this._glContext.bindTexture( this._glContext.TEXTURE_2D, this._texture );

		var filtering = this._glContext.LINEAR; // NEAREST for block quality, LINEAR for softer texture

		this._glContext.texParameteri(this._glContext.TEXTURE_2D, this._glContext.TEXTURE_MAG_FILTER, filtering);
		this._glContext.texParameteri(this._glContext.TEXTURE_2D, this._glContext.TEXTURE_MIN_FILTER, filtering);

		this._glContext.uniform1i(this._shaderProgram.samplerUniform, 0);

		canvasParent.connect( 'resize', function() { that._onResize(); } );
	};

	WebGlRenderSurface.prototype._initView = function() {

		var mvMatrix = mat4.create();
		var pMatrix = mat4.create();

		mat4.ortho(pMatrix, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT, 0.1, 100)
		mat4.identity(mvMatrix);
		mat4.translate(mvMatrix, mvMatrix, [0.0, 0.0, -0.1]);

		this._glContext.uniformMatrix4fv(this._shaderProgram.pMatrixUniform, false, pMatrix);
		this._glContext.uniformMatrix4fv(this._shaderProgram.mvMatrixUniform, false, mvMatrix);
	};

	WebGlRenderSurface.prototype._initBuffers = function() {

		this._vertexBuffer = new WebGlVertexBuffer( this._glContext );
		this._vertexBuffer.setData( new Float32Array( [
				0, 0,							0.0,
				SCREEN_WIDTH,	0,				0.0,
				SCREEN_WIDTH,	SCREEN_HEIGHT,	0.0,
				0,				SCREEN_HEIGHT,	0.0
			] ), 3, 4 );

		this._textureCoordBuffer = new WebGlVertexBuffer( this._glContext );
		var t = SCREEN_WIDTH / TEXTURE_WIDTH;
		var u = SCREEN_HEIGHT / TEXTURE_HEIGHT;
		this._textureCoordBuffer.setData( new Float32Array( [
				0.0,	0.0,
				t,		0.0,
				t,		u,
				0.0,	u
			] ), 2, 4 );

		this._indexBuffer = new WebGlIndexBuffer( this._glContext );
		this._indexBuffer.setData( new Uint16Array( [
			0, 1, 2,	0, 2, 3
		] ), 6 );
	};

	WebGlRenderSurface.prototype._initTexture = function() {

		this._texture = this._glContext.createTexture();
		this._glContext.bindTexture(this._glContext.TEXTURE_2D, this._texture);
		this._glContext.pixelStorei(this._glContext.UNPACK_FLIP_Y_WEBGL, true);
		this._glContext.texImage2D(this._glContext.TEXTURE_2D, 0, this._glContext.RGBA, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, this._glContext.RGBA, this._glContext.UNSIGNED_BYTE, null );
	};

	WebGlRenderSurface.prototype._compileShader = function(id) {
		var shaderScript = document.getElementById(id);
		if (!shaderScript) {
			throw new Error( "Could not find shader script for DOM element '" + id + "'" );
		}

		var str = "";
		var k = shaderScript.firstChild;
		while (k) {
			if (k.nodeType === 3) {
				str += k.textContent;
			}
			k = k.nextSibling;
		}

		var shader;
		if (shaderScript.type === "x-shader/x-fragment") {
			shader = this._glContext.createShader(this._glContext.FRAGMENT_SHADER);
		} else if (shaderScript.type === "x-shader/x-vertex") {
			shader = this._glContext.createShader(this._glContext.VERTEX_SHADER);
		} else {
			throw new Error( "Could not find shader script for DOM element '" + id + "'" );
		}

		this._glContext.shaderSource(shader, str);
		this._glContext.compileShader(shader);

		if (!this._glContext.getShaderParameter(shader, this._glContext.COMPILE_STATUS)) {
			throw new Error( "Error compiling shader script '" + id + "' " + this._glContext.getShaderInfoLog(shader) );
		}

		return shader;
	};

	WebGlRenderSurface.prototype._initShaders = function() {

		var fragmentShader = this._compileShader( "shader-fs" );
		var vertexShader = this._compileShader( "shader-vs" );

		this._shaderProgram = this._glContext.createProgram();
		this._glContext.attachShader(this._shaderProgram, vertexShader);
		this._glContext.attachShader(this._shaderProgram, fragmentShader);
		this._glContext.linkProgram(this._shaderProgram);

		if (!this._glContext.getProgramParameter(this._shaderProgram, this._glContext.LINK_STATUS)) {
			throw new Error("Could not initialise shaders");
		}

		this._glContext.useProgram(this._shaderProgram);

		this._shaderProgram.vertexPositionAttribute = this._glContext.getAttribLocation(this._shaderProgram, "aVertexPosition");
		this._glContext.enableVertexAttribArray(this._shaderProgram.vertexPositionAttribute);

		this._shaderProgram.textureCoordAttribute = this._glContext.getAttribLocation(this._shaderProgram, "aTextureCoord");
		this._glContext.enableVertexAttribArray(this._shaderProgram.textureCoordAttribute);

		this._shaderProgram.pMatrixUniform = this._glContext.getUniformLocation(this._shaderProgram, "uPMatrix");
		this._shaderProgram.mvMatrixUniform = this._glContext.getUniformLocation(this._shaderProgram, "uMVMatrix");
		this._shaderProgram.samplerUniform = this._glContext.getUniformLocation(this._shaderProgram, "uSampler");
	};

	WebGlRenderSurface.prototype._onResize = function() {
		this._glContext.viewport(0, 0, this._element.width, this._element.height);
		this._glContext.clearColor(0.0, 0.0, 0.0, 1.0);
	};

	WebGlRenderSurface.prototype.writeToBuffer = function( bufferIndex, insertIndex, colour ) {

		//if ( baseIndex < 0 || baseIndex >= this._offscreen32BitView.length ) { throw new Error( "WebGlRenderSurface.prototype.writeToBuffer: Invalid bounds" ); }
		var existingIndex = TYPED_ARRAY_GET_INT32( this._bufferIndexArray, insertIndex );
		if ( existingIndex <= bufferIndex ) {
			TYPED_ARRAY_SET_UINT32( this._offscreen32BitView, insertIndex, 0xFF000000 | colour );
			TYPED_ARRAY_SET_INT32( this._bufferIndexArray, insertIndex, bufferIndex );
		}
	};

	WebGlRenderSurface.prototype.getRenderBufferHash = function() {

		var rusha = new Rusha();
		return rusha.digestFromArrayBuffer( this._offscreen32BitView ).toUpperCase();
	};

	WebGlRenderSurface.prototype.clearBuffers = function( backgroundColour ) {

		// update clear array if background colour changes
		if ( backgroundColour !== this._clearArrayColour ) {
			for ( var i=0; i<this._clearArray.length; ++i ) {
				this._clearArray[ i ] = 0xFF000000 | backgroundColour;
			}
			this._clearArrayColour = backgroundColour;
		}

		// set background colour
		this._offscreen32BitView.set( this._clearArray );

		// Nes.ClearScreenArray is a quicker way of clearing this array
		this._bufferIndexArray.set( g_ClearScreenArray );
	};

	WebGlRenderSurface.prototype.render = function( mainboard ) {

		this._glContext.clear(this._glContext.COLOR_BUFFER_BIT);
		this._glContext.texSubImage2D( this._glContext.TEXTURE_2D, 0, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, this._glContext.RGBA, this._glContext.UNSIGNED_BYTE, this._offscreen8BitView );
		this._indexBuffer.draw();
	};

	WebGlRenderSurface.prototype._createCanvasWithScreenshotOn = function() {

		// create copy of offscreen buffer into a new canvas element
		var element = document.createElement('canvas');
		element.width = SCREEN_WIDTH;
		element.height = SCREEN_HEIGHT;
		var canvas = element.getContext( "2d" );
		var imgData = canvas.getImageData( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT );
		imgData.data.set( this._offscreen8BitView.subarray( 0, SCREEN_WIDTH * SCREEN_HEIGHT * 4 ) );
		canvas.putImageData( imgData, 0, 0 );
		return element;
	};

	WebGlRenderSurface.prototype.screenshotToFile = function() {

		var element = this._createCanvasWithScreenshotOn();
		element.toBlob( function( blob ) {
			saveAs( blob, "screenshot.png" );
		});
	};

	WebGlRenderSurface.prototype.screenshotToString = function() {

		var element = this._createCanvasWithScreenshotOn();
		return element.toDataURL("image/png");
	};

	Gui.WebGlSupported = function() {
		try {
			var canvas = document.createElement('canvas');
			var ctx = getGlContext( canvas );
			return ctx !== null;
		}
		catch (e) {
			return false;
		}
	};

	Gui.WebGlRenderSurface = WebGlRenderSurface;

}());
<commit_msg>Further refactor<commit_after>/*
This file is part of WebNES.

WebNES is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

WebNES is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with WebNES.  If not, see <http://www.gnu.org/licenses/>.
*/

this.Gui = this.Gui || {};

(function(){
	"use strict";

	// Must be power of 2
	var TEXTURE_WIDTH = 256;
	var TEXTURE_HEIGHT = 256;

	var getGlContext = function( canvas ) {
		return canvas.getContext("webgl") || canvas.getContext("experimental-webgl")
	};

	var WebGlVertexBuffer = function( glContext ) {

		this._glContext = glContext;
		this._itemSize = 0;
		this._itemCount = 0;
		this._buffer = this._glContext.createBuffer();
	};

	WebGlVertexBuffer.prototype.setData = function( vertices, itemSize, itemCount ) {

		// ELEMENT_ARRAY_BUFFER is used by index buffer, ARRAY_BUFFER by vertex and tex coord buffers
		this._itemSize = itemSize;
		this._itemCount = itemCount;
		this._glContext.bindBuffer( this._glContext.ARRAY_BUFFER, this._buffer );
		this._glContext.bufferData( this._glContext.ARRAY_BUFFER, vertices, this._glContext.STATIC_DRAW );
	};

	WebGlVertexBuffer.prototype.bind = function( positionAttribute ) {
		this._glContext.bindBuffer( this._glContext.ARRAY_BUFFER, this._buffer );
		this._glContext.vertexAttribPointer( positionAttribute, this._itemSize, this._glContext.FLOAT, false, 0, 0 );
	};

	var WebGlIndexBuffer = function( glContext ) {

		this._glContext = glContext;
		this._itemCount = 0;
		this._buffer = this._glContext.createBuffer();
	};

	WebGlIndexBuffer.prototype.setData = function( indices, itemCount ) {
		this._itemCount = itemCount;
		this._glContext.bindBuffer( this._glContext.ELEMENT_ARRAY_BUFFER, this._buffer );
		this._glContext.bufferData( this._glContext.ELEMENT_ARRAY_BUFFER, indices, this._glContext.STATIC_DRAW );
	};

	WebGlIndexBuffer.prototype.bind = function() {
		this._glContext.bindBuffer(this._glContext.ELEMENT_ARRAY_BUFFER, this._buffer);
	};

	WebGlIndexBuffer.prototype.draw = function() {
		this._glContext.drawElements(this._glContext.TRIANGLES, this._itemCount, this._glContext.UNSIGNED_SHORT, 0);
	};

	var WebGlRenderSurface = function( canvasParent ) {

		var that = this;

		this._clearArray = new Uint32Array( SCREEN_WIDTH * SCREEN_HEIGHT );
		this._clearArrayColour = this._clearArray[0];

		this._bufferIndexArray = new Int32Array( SCREEN_WIDTH * SCREEN_HEIGHT );
		this._offscreen32BitView = new Uint32Array( TEXTURE_WIDTH * TEXTURE_HEIGHT );
		this._offscreen8BitView = new Uint8Array( this._offscreen32BitView.buffer );

		this._element = canvasParent.getCanvasElement();
		this._glContext = getGlContext( this._element );

		this._initShaders();
		this._initBuffers();
		this._initTexture();
		this._initView();

		this._vertexBuffer.bind( this._shaderProgram.vertexPositionAttribute );
		this._textureCoordBuffer.bind( this._shaderProgram.textureCoordAttribute );
		this._indexBuffer.bind();

		this._glContext.activeTexture(this._glContext.TEXTURE0);
		this._glContext.bindTexture( this._glContext.TEXTURE_2D, this._texture );

		var filtering = this._glContext.LINEAR; // NEAREST for block quality, LINEAR for softer texture

		this._glContext.texParameteri(this._glContext.TEXTURE_2D, this._glContext.TEXTURE_MAG_FILTER, filtering);
		this._glContext.texParameteri(this._glContext.TEXTURE_2D, this._glContext.TEXTURE_MIN_FILTER, filtering);

		this._glContext.uniform1i(this._shaderProgram.samplerUniform, 0);

		canvasParent.connect( 'resize', function() { that._onResize(); } );
	};

	WebGlRenderSurface.prototype._initView = function() {

		var mvMatrix = mat4.create();
		var pMatrix = mat4.create();

		mat4.ortho(pMatrix, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT, 0.1, 100);
		mat4.identity(mvMatrix);
		mat4.translate(mvMatrix, mvMatrix, [0.0, 0.0, -0.1]);

		this._glContext.uniformMatrix4fv(this._shaderProgram.pMatrixUniform, false, pMatrix);
		this._glContext.uniformMatrix4fv(this._shaderProgram.mvMatrixUniform, false, mvMatrix);
	};

	WebGlRenderSurface.prototype._initBuffers = function() {
		var t = SCREEN_WIDTH / TEXTURE_WIDTH;
		var u = SCREEN_HEIGHT / TEXTURE_HEIGHT;

		var vertices = new Float32Array( [
				0, 0,							0.0,
				SCREEN_WIDTH,	0,				0.0,
				SCREEN_WIDTH,	SCREEN_HEIGHT,	0.0,
				0,				SCREEN_HEIGHT,	0.0
			] );
		var texCoords = new Float32Array( [
				0.0,	0.0,
				t,		0.0,
				t,		u,
				0.0,	u
			] );
		var indices = new Uint16Array( [ 0, 1, 2,	0, 2, 3 ] );

		this._vertexBuffer = new WebGlVertexBuffer( this._glContext );
		this._vertexBuffer.setData( vertices, 3, 4 );

		this._textureCoordBuffer = new WebGlVertexBuffer( this._glContext );
		this._textureCoordBuffer.setData( texCoords, 2, 4 );

		this._indexBuffer = new WebGlIndexBuffer( this._glContext );
		this._indexBuffer.setData( indices, 6 );
	};

	WebGlRenderSurface.prototype._initTexture = function() {

		this._texture = this._glContext.createTexture();
		this._glContext.bindTexture(this._glContext.TEXTURE_2D, this._texture);
		this._glContext.pixelStorei(this._glContext.UNPACK_FLIP_Y_WEBGL, true);
		this._glContext.texImage2D(this._glContext.TEXTURE_2D, 0, this._glContext.RGBA, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, this._glContext.RGBA, this._glContext.UNSIGNED_BYTE, null );
	};

	WebGlRenderSurface.prototype._compileShader = function(id) {
		var shaderScript = document.getElementById(id);
		if (!shaderScript) {
			throw new Error( "Could not find shader script for DOM element '" + id + "'" );
		}

		var str = "";
		var k = shaderScript.firstChild;
		while (k) {
			if (k.nodeType === 3) {
				str += k.textContent;
			}
			k = k.nextSibling;
		}

		var shader;
		if (shaderScript.type === "x-shader/x-fragment") {
			shader = this._glContext.createShader(this._glContext.FRAGMENT_SHADER);
		} else if (shaderScript.type === "x-shader/x-vertex") {
			shader = this._glContext.createShader(this._glContext.VERTEX_SHADER);
		} else {
			throw new Error( "Could not find shader script for DOM element '" + id + "'" );
		}

		this._glContext.shaderSource(shader, str);
		this._glContext.compileShader(shader);

		if (!this._glContext.getShaderParameter(shader, this._glContext.COMPILE_STATUS)) {
			throw new Error( "Error compiling shader script '" + id + "' " + this._glContext.getShaderInfoLog(shader) );
		}

		return shader;
	};

	WebGlRenderSurface.prototype._initShaders = function() {

		var fragmentShader = this._compileShader( "shader-fs" );
		var vertexShader = this._compileShader( "shader-vs" );

		this._shaderProgram = this._glContext.createProgram();
		this._glContext.attachShader(this._shaderProgram, vertexShader);
		this._glContext.attachShader(this._shaderProgram, fragmentShader);
		this._glContext.linkProgram(this._shaderProgram);

		if (!this._glContext.getProgramParameter(this._shaderProgram, this._glContext.LINK_STATUS)) {
			throw new Error("Could not initialise shaders");
		}

		this._glContext.useProgram(this._shaderProgram);

		this._shaderProgram.vertexPositionAttribute = this._glContext.getAttribLocation(this._shaderProgram, "aVertexPosition");
		this._glContext.enableVertexAttribArray(this._shaderProgram.vertexPositionAttribute);

		this._shaderProgram.textureCoordAttribute = this._glContext.getAttribLocation(this._shaderProgram, "aTextureCoord");
		this._glContext.enableVertexAttribArray(this._shaderProgram.textureCoordAttribute);

		this._shaderProgram.pMatrixUniform = this._glContext.getUniformLocation(this._shaderProgram, "uPMatrix");
		this._shaderProgram.mvMatrixUniform = this._glContext.getUniformLocation(this._shaderProgram, "uMVMatrix");
		this._shaderProgram.samplerUniform = this._glContext.getUniformLocation(this._shaderProgram, "uSampler");
	};

	WebGlRenderSurface.prototype._onResize = function() {
		this._glContext.viewport(0, 0, this._element.width, this._element.height);
		this._glContext.clearColor(0.0, 0.0, 0.0, 1.0);
	};

	WebGlRenderSurface.prototype.writeToBuffer = function( bufferIndex, insertIndex, colour ) {

		//if ( baseIndex < 0 || baseIndex >= this._offscreen32BitView.length ) { throw new Error( "WebGlRenderSurface.prototype.writeToBuffer: Invalid bounds" ); }
		var existingIndex = TYPED_ARRAY_GET_INT32( this._bufferIndexArray, insertIndex );
		if ( existingIndex <= bufferIndex ) {
			TYPED_ARRAY_SET_UINT32( this._offscreen32BitView, insertIndex, 0xFF000000 | colour );
			TYPED_ARRAY_SET_INT32( this._bufferIndexArray, insertIndex, bufferIndex );
		}
	};

	WebGlRenderSurface.prototype.getRenderBufferHash = function() {

		var rusha = new Rusha();
		return rusha.digestFromArrayBuffer( this._offscreen32BitView ).toUpperCase();
	};

	WebGlRenderSurface.prototype.clearBuffers = function( backgroundColour ) {

		// update clear array if background colour changes
		if ( backgroundColour !== this._clearArrayColour ) {
			for ( var i=0; i<this._clearArray.length; ++i ) {
				this._clearArray[ i ] = 0xFF000000 | backgroundColour;
			}
			this._clearArrayColour = backgroundColour;
		}

		// set background colour
		this._offscreen32BitView.set( this._clearArray );

		// Nes.ClearScreenArray is a quicker way of clearing this array
		this._bufferIndexArray.set( g_ClearScreenArray );
	};

	WebGlRenderSurface.prototype.render = function( mainboard ) {

		this._glContext.clear(this._glContext.COLOR_BUFFER_BIT);
		this._glContext.texSubImage2D( this._glContext.TEXTURE_2D, 0, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, this._glContext.RGBA, this._glContext.UNSIGNED_BYTE, this._offscreen8BitView );
		this._indexBuffer.draw();
	};

	WebGlRenderSurface.prototype._createCanvasWithScreenshotOn = function() {

		// create copy of offscreen buffer into a new canvas element
		var element = document.createElement('canvas');
		element.width = SCREEN_WIDTH;
		element.height = SCREEN_HEIGHT;
		var canvas = element.getContext( "2d" );
		var imgData = canvas.getImageData( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT );
		imgData.data.set( this._offscreen8BitView.subarray( 0, SCREEN_WIDTH * SCREEN_HEIGHT * 4 ) );
		canvas.putImageData( imgData, 0, 0 );
		return element;
	};

	WebGlRenderSurface.prototype.screenshotToFile = function() {

		var element = this._createCanvasWithScreenshotOn();
		element.toBlob( function( blob ) {
			saveAs( blob, "screenshot.png" );
		});
	};

	WebGlRenderSurface.prototype.screenshotToString = function() {

		var element = this._createCanvasWithScreenshotOn();
		return element.toDataURL("image/png");
	};

	Gui.WebGlSupported = function() {
		try {
			var canvas = document.createElement('canvas');
			var ctx = getGlContext( canvas );
			return ctx !== null;
		}
		catch (e) {
			return false;
		}
	};

	Gui.WebGlRenderSurface = WebGlRenderSurface;

}());
<|endoftext|>