<commit_before>// folder-view.js
/*
 * Copyright (c) 2014 3 Round Stones Inc., Some Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

(function($, jQuery){
    try {
        window.sessionStorage.setItem("LastFolder", location.href);
    } catch(e) {}
    try {
        window.localStorage.setItem("LastFolder", location.href);
    } catch(e) {}
    function createTextCell(text, url, type, src) {
        if (!src) {
            src = calli.getCallimachusUrl('images/rdf_flyer.png');
        }
        var td = $('');
        td.addClass('filecell');
        var a = $('');
        if (url) {
            a.attr('href', url + '?view');
            if (type) {
                a.attr('type', type);
            }
        }
        if (text) {
            a.text(text);
        } else if (url) {
            a.text(url.replace(/.*\/(.)/, '$1'));
        } else {
            a.text('.');
        }
        if (src) {
            var img = $('');
            img.addClass('icon');
            img.attr('src', src);
            a.prepend(img);
        }
        td.append(a);
        return td;
    }
    function createTimeCell(text) {
        var td = $('');
        td.addClass('timecell');
        if (text) {
            var time = $('<time/>');
            time.attr('datetime', text);
            time.text(calli.parseDateTime(text).toLocaleString());
            td.append(time);
        }
        return td;
    }
    function createPermissionCell(entry, local) {
        var td = $('');
        td.addClass('hidden-xs hidden-sm');
        if (!entry || !local)
            return td;
        var tags = entry.children('link[rel="http://callimachusproject.org/rdf/2009/framework#' + local + '"]');
        tags.each(function() {
            var uri = $(this).attr('href');
            var name = $(this).attr('title');
            var a = $('');
            if (uri) {
                a.attr('href', uri);
            }
            if (name) {
                a.text(name);
            } else if (uri) {
                a.text(uri);
            } else {
                a.text('.');
            }
            td.append(' ');
            td.append(a);
        });
        return td;
    }
    function reload() {
        var url = $('link[rel="contents"][type="application/atom+xml"]').attr('href');
        return calli.getXML(url).then(calli.ready).then(function(doc) {
            var feed = $(doc.documentElement);
            var totalResults = feed.children().filter(function(){return this.tagName=='openSearch:totalResults';}).text();
            $('#totalResults').text(totalResults);
            var tbody = $('
');
            tbody.attr('id', 'tfiles');
            var totalEntries = 0;
            feed.children('entry').each(function() {
                var entry = $(this);
                totalEntries++;
                if (!entry.children('link[rel="contents"]').length) {
                    var tr = $('
');
                    var icon = entry.children('icon').text();
                    var title = entry.children('title').text();
                    var content = entry.children('content');
                    var src = content.attr('src');
                    var type = content.attr('type');
                    tr.append(createTextCell(title, src, type, icon));
                    tr.append(createTimeCell(entry.children('updated').text()));
                    tr.append(createPermissionCell(entry, 'reader'));
                    tr.append(createPermissionCell(entry, 'subscriber'));
                    tr.append(createPermissionCell(entry, 'contributor'));
                    tr.append(createPermissionCell(entry, 'editor'));
                    tr.append(createPermissionCell(entry, 'administrator'));
                    tbody.append(tr);
                }
            });
            var box = $('#folder-box')[0];
            var bottom = box.scrollTop > 0 && box.scrollTop >= box.scrollHeight - box.clientHeight;
            $('#tfiles').replaceWith(tbody);
            $('#totalEntries').text(totalEntries);
            if (bottom) {
                box.scrollTop = box.scrollHeight - box.clientHeight;
            }
            var checkForCompleteImg = function() {
                if ($(tbody).find('img').filter(function() { return !this.complete; }).length) {
                    return calli.sleep(500).then(checkForCompleteImg);
                }
            };
            return calli.sleep(100).then(checkForCompleteImg);
        }).catch(calli.error);
    }
    reload();
    var queueStarted = null;
    var queueTotalSize = 0;
    var queueCompleteSize = 0;
    var upload_queue = 0;
    var uploading = calli.resolve();
    function queue(file) {
        if (!queueStarted) {
            queueStarted = new Date();
        }
        upload_queue++;
        queueTotalSize += file.size;
        var next = function(){
            upload_queue.shift();
            queueCompleteSize += file.size;
            uploadProgress(0);
            if (upload_queue < 1) {
                queueStarted = null;
                queueTotalSize = 0;
                queueCompleteSize = 0;
                notifyProgressComplete();
            }
        };
        var slug = calli.slugify(file.name.replace(/[\-\s]+/g, '-'));
        var xhr = $.ajax({
            type: 'HEAD',
            url: slug,
            dataType: "text"
        });
        return calli.resolve(xhr).then(function(){
            if (confirm(slug + " already exists. Do you want to replace it?")) {
                var contentType = file.type;
                if (!contentType || contentType.indexOf('/x-') > 0) {
                    contentType = xhr.getResponseHeader('Content-Type');
                }
                return uploading.then(function() {
                    return putFile(file, contentType, slug).then(next);
                });
            }
        }, function() {
            return uploading.then(function() {
                return postCreate(file, slug).then(next);
            });
        });
    }
    function postCreate(file, slug) {
        var classFile = $('#file-class-link').attr('href');
        var formData = new FormData();
        formData.append(file.name, file);
        return calli.resolve($.ajax({
            type:'POST',
            url:'?create=' + classFile + '&resource=' + encodeURIComponent(slug),
            contentType:"multipart/form-data",
            processData:false,
            data:formData,
            xhrFields: {withCredentials: true},
            xhr: function() {
                var xhr = $.ajaxSettings.xhr();
                if (xhr.upload && xhr.upload.addEventListener) {
                    xhr.upload.addEventListener("progress", function(event) {
                        uploadProgress(event.loaded);
                    }, false);
                } else if (console.log) {
                    console.log("Upload progress is not supported.");
                }
                return xhr;
            }
        })).then(reload, calli.error);
    }
    function putFile(file, contentType, slug) {
        var classFile = $('#file-class-link').attr('href');
        return calli.resolve($.ajax({
            type:'PUT',
            url:slug,
            contentType:contentType,
            processData:false,
            data:file,
            xhrFields: {withCredentials: true},
            xhr: function() {
                var xhr = $.ajaxSettings.xhr();
                if (xhr.upload && xhr.upload.addEventListener) {
                    xhr.upload.addEventListener("progress", function(event) {
                        uploadProgress(event.loaded);
                    }, false);
                } else if (console.log) {
                    console.log("Upload progress is not supported.");
                }
                return xhr;
            }
        })).then(reload, calli.error);
    }
    var uploadedSize = 0;
    function uploadProgress(complete, estimated) {
        var progressbar = $('#result-status').find('.progress-bar');
        if (!progressbar.length && queueCompleteSize + complete < queueTotalSize / 2) {
            var progress = $('
');
            progressbar = $('
');
            var span = $('');
            span.addClass("sr-only");
            progressbar.append(span);
            progressbar.addClass("progress-bar");
            progress.addClass("progress");
            progress.append(progressbar);
            $('#result-status').append(progress);
        }
        // Set aside 25% for server processing time
        var x = (queueCompleteSize + complete * 7/8) / queueTotalSize;
        // Early stage upload just fills up buffers and don't contribute as much
        var percent = Math.round(Math.pow(x+(1-x)*0.03, 2) * 100);
        if (percent >= 100)
            return false;
        progressbar.css('width', percent + '%');
        progressbar.find('span').text(percent + '%');
        if (estimated) {
            progressbar.closest('.progress').addClass('progress-striped active');
        } else {
            progressbar.closest('.progress').removeClass('progress-striped active');
            uploadedSize = queueCompleteSize + complete;
            if (console && console.log) {
                console.log(new Date().toTimeString() + ' processed ' + percent + '% uploaded ' + (queueCompleteSize + complete) + ' of ' + queueTotalSize);
            }
            estimateProgress();
        }
        return true;
    }
    function estimateProgress() {
        var estimate = function(lastSize, complete, rate){
            setTimeout(function(){
                if (lastSize == uploadedSize) {
                    if (uploadProgress(complete, true)) {
                        estimate(lastSize, complete + rate, rate);
                    }
                }
            }, 2000);
        };
        // animate progressbar every second while idle to simulate server processing
        if (queueStarted) {
            var rate = uploadedSize / (new Date().getTime() - queueStarted.getTime()) * 1000 / 5;
            estimate(uploadedSize, uploadedSize - queueCompleteSize + rate, rate);
        }
    }
    function notifyProgressComplete() {
        uploadedSize = 0;
        $('#result-status').find('.progress').remove();
        if (console && console.log) {
            console.log(new Date().toTimeString() + ' processed 100% of ' + queueTotalSize);
        }
    }
    jQuery(function(){
        if (window.FileReader) {
            $('#folder-box').bind('dragenter dragexit dragover', function(event) {
                $(this).addClass(event.type);
                event.stopPropagation();
                event.preventDefault();
            });
            $('#folder-box').bind('dragleave', function(event) {
                $(this).removeClass('dragenter dragover');
            });
            $('#folder-box').bind('drop', function(event) {
                $(this).removeClass('dragenter dragover');
                event.stopPropagation();
                event.preventDefault();
                var files = event.originalEvent.dataTransfer.files;
                calli.all(files.map(queue)).catch(calli.error);
            });
        }
    });
})(jQuery, jQuery);
jQuery(function($){
    if (!$('#tfolders').children().length) {
        $('#tfolders').remove();
    }
});

<commit_msg>Fix problime with DnD<commit_after>// folder-view.js
/*
 * Copyright (c) 2014 3 Round Stones Inc., Some Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

(function($, jQuery){
    try {
        window.sessionStorage.setItem("LastFolder", location.href);
    } catch(e) {}
    try {
        window.localStorage.setItem("LastFolder", location.href);
    } catch(e) {}
    function createTextCell(text, url, type, src) {
        if (!src) {
            src = calli.getCallimachusUrl('images/rdf_flyer.png');
        }
        var td = $('');
        td.addClass('filecell');
        var a = $('');
        if (url) {
            a.attr('href', url + '?view');
            if (type) {
                a.attr('type', type);
            }
        }
        if (text) {
            a.text(text);
        } else if (url) {
            a.text(url.replace(/.*\/(.)/, '$1'));
        } else {
            a.text('.');
        }
        if (src) {
            var img = $('');
            img.addClass('icon');
            img.attr('src', src);
            a.prepend(img);
        }
        td.append(a);
        return td;
    }
    function createTimeCell(text) {
        var td = $('');
        td.addClass('timecell');
        if (text) {
            var time = $('<time/>');
            time.attr('datetime', text);
            time.text(calli.parseDateTime(text).toLocaleString());
            td.append(time);
        }
        return td;
    }
    function createPermissionCell(entry, local) {
        var td = $('');
        td.addClass('hidden-xs hidden-sm');
        if (!entry || !local)
            return td;
        var tags = entry.children('link[rel="http://callimachusproject.org/rdf/2009/framework#' + local + '"]');
        tags.each(function() {
            var uri = $(this).attr('href');
            var name = $(this).attr('title');
            var a = $('');
            if (uri) {
                a.attr('href', uri);
            }
            if (name) {
                a.text(name);
            } else if (uri) {
                a.text(uri);
            } else {
                a.text('.');
            }
            td.append(' ');
            td.append(a);
        });
        return td;
    }
    function reload() {
        var url = $('link[rel="contents"][type="application/atom+xml"]').attr('href');
        return calli.getXML(url).then(calli.ready).then(function(doc) {
            var feed = $(doc.documentElement);
            var totalResults = feed.children().filter(function(){return this.tagName=='openSearch:totalResults';}).text();
            $('#totalResults').text(totalResults);
            var tbody = $('
');
            tbody.attr('id', 'tfiles');
            var totalEntries = 0;
            feed.children('entry').each(function() {
                var entry = $(this);
                totalEntries++;
                if (!entry.children('link[rel="contents"]').length) {
                    var tr = $('
');
                    var icon = entry.children('icon').text();
                    var title = entry.children('title').text();
                    var content = entry.children('content');
                    var src = content.attr('src');
                    var type = content.attr('type');
                    tr.append(createTextCell(title, src, type, icon));
                    tr.append(createTimeCell(entry.children('updated').text()));
                    tr.append(createPermissionCell(entry, 'reader'));
                    tr.append(createPermissionCell(entry, 'subscriber'));
                    tr.append(createPermissionCell(entry, 'contributor'));
                    tr.append(createPermissionCell(entry, 'editor'));
                    tr.append(createPermissionCell(entry, 'administrator'));
                    tbody.append(tr);
                }
            });
            var box = $('#folder-box')[0];
            var bottom = box.scrollTop > 0 && box.scrollTop >= box.scrollHeight - box.clientHeight;
            $('#tfiles').replaceWith(tbody);
            $('#totalEntries').text(totalEntries);
            if (bottom) {
                box.scrollTop = box.scrollHeight - box.clientHeight;
            }
            var checkForCompleteImg = function() {
                if ($(tbody).find('img').filter(function() { return !this.complete; }).length) {
                    return calli.sleep(500).then(checkForCompleteImg);
                }
            };
            return calli.sleep(100).then(checkForCompleteImg);
        }).catch(calli.error);
    }
    reload();
    var queueStarted = null;
    var queueTotalSize = 0;
    var queueCompleteSize = 0;
    var upload_queue = 0;
    var uploading = calli.resolve();
    function queue(file) {
        if (!queueStarted) {
            queueStarted = new Date();
        }
        upload_queue++;
        queueTotalSize += file.size;
        var next = function(){
            upload_queue--;
            queueCompleteSize += file.size;
            uploadProgress(0);
            if (upload_queue < 1) {
                queueStarted = null;
                queueTotalSize = 0;
                queueCompleteSize = 0;
                notifyProgressComplete();
            }
        };
        var slug = calli.slugify(file.name.replace(/[\-\s]+/g, '-'));
        var xhr = $.ajax({
            type: 'HEAD',
            url: slug,
            dataType: "text"
        });
        return calli.resolve(xhr).then(function(){
            if (confirm(slug + " already exists. Do you want to replace it?")) {
                var contentType = file.type;
                if (!contentType || contentType.indexOf('/x-') > 0) {
                    contentType = xhr.getResponseHeader('Content-Type');
                }
                return uploading.then(function() {
                    return putFile(file, contentType, slug).then(next);
                });
            }
        }, function() {
            return uploading.then(function() {
                return postCreate(file, slug).then(next);
            });
        });
    }
    function postCreate(file, slug) {
        var classFile = $('#file-class-link').attr('href');
        var formData = new FormData();
        formData.append(file.name, file);
        return calli.resolve($.ajax({
            type:'POST',
            url:'?create=' + classFile + '&resource=' + encodeURIComponent(slug),
            contentType:"multipart/form-data",
            processData:false,
            data:formData,
            xhrFields: {withCredentials: true},
            xhr: function() {
                var xhr = $.ajaxSettings.xhr();
                if (xhr.upload && xhr.upload.addEventListener) {
                    xhr.upload.addEventListener("progress", function(event) {
                        uploadProgress(event.loaded);
                    }, false);
                } else if (console.log) {
                    console.log("Upload progress is not supported.");
                }
                return xhr;
            }
        })).then(reload, calli.error);
    }
    function putFile(file, contentType, slug) {
        var classFile = $('#file-class-link').attr('href');
        return calli.resolve($.ajax({
            type:'PUT',
            url:slug,
            contentType:contentType,
            processData:false,
            data:file,
            xhrFields: {withCredentials: true},
            xhr: function() {
                var xhr = $.ajaxSettings.xhr();
                if (xhr.upload && xhr.upload.addEventListener) {
                    xhr.upload.addEventListener("progress", function(event) {
                        uploadProgress(event.loaded);
                    }, false);
                } else if (console.log) {
                    console.log("Upload progress is not supported.");
                }
                return xhr;
            }
        })).then(reload, calli.error);
    }
    var uploadedSize = 0;
    function uploadProgress(complete, estimated) {
        var progressbar = $('#result-status').find('.progress-bar');
        if (!progressbar.length && queueCompleteSize + complete < queueTotalSize / 2) {
            var progress = $('
');
            progressbar = $('
');
            var span = $('');
            span.addClass("sr-only");
            progressbar.append(span);
            progressbar.addClass("progress-bar");
            progress.addClass("progress");
            progress.append(progressbar);
            $('#result-status').append(progress);
        }
        // Set aside 25% for server processing time
        var x = (queueCompleteSize + complete * 7/8) / queueTotalSize;
        // Early stage upload just fills up buffers and don't contribute as much
        var percent = Math.round(Math.pow(x+(1-x)*0.03, 2) * 100);
        if (percent >= 100)
            return false;
        progressbar.css('width', percent + '%');
        progressbar.find('span').text(percent + '%');
        if (estimated) {
            progressbar.closest('.progress').addClass('progress-striped active');
        } else {
            progressbar.closest('.progress').removeClass('progress-striped active');
            uploadedSize = queueCompleteSize + complete;
            if (console && console.log) {
                console.log(new Date().toTimeString() + ' processed ' + percent + '% uploaded ' + (queueCompleteSize + complete) + ' of ' + queueTotalSize);
            }
            estimateProgress();
        }
        return true;
    }
    function estimateProgress() {
        var estimate = function(lastSize, complete, rate){
            setTimeout(function(){
                if (lastSize == uploadedSize) {
                    if (uploadProgress(complete, true)) {
                        estimate(lastSize, complete + rate, rate);
                    }
                }
            }, 2000);
        };
        // animate progressbar every second while idle to simulate server processing
        if (queueStarted) {
            var rate = uploadedSize / (new Date().getTime() - queueStarted.getTime()) * 1000 / 5;
            estimate(uploadedSize, uploadedSize - queueCompleteSize + rate, rate);
        }
    }
    function notifyProgressComplete() {
        uploadedSize = 0;
        $('#result-status').find('.progress').remove();
        if (console && console.log) {
            console.log(new Date().toTimeString() + ' processed 100% of ' + queueTotalSize);
        }
    }
    jQuery(function(){
        if (window.FileReader) {
            $('#folder-box').bind('dragenter dragexit dragover', function(event) {
                $(this).addClass(event.type);
                event.stopPropagation();
                event.preventDefault();
            });
            $('#folder-box').bind('dragleave', function(event) {
                $(this).removeClass('dragenter dragover');
            });
            $('#folder-box').bind('drop', function(event) {
                $(this).removeClass('dragenter dragover');
                event.stopPropagation();
                event.preventDefault();
                var files = event.originalEvent.dataTransfer.files;
                calli.resolve().then(function(){
                    return calli.all(Array.prototype.map.call(files, queue));
                }).catch(calli.error);
            });
        }
    });
})(jQuery, jQuery);
jQuery(function($){
    if (!$('#tfolders').children().length) {
        $('#tfolders').remove();
    }
});

<|endoftext|>


<commit_before>#!/usr/bin/env node
/**
 * @license
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const yargs = require('yargs');
const semver = require('semver');
const Printer = require('./printer');
const lighthouse = require('../lighthouse-core');

// node 5.x required due to use of ES2015 features, like spread operator
if (semver.lt(process.version, '5.0.0')) {
  console.warn('Compatibility error', 'Lighthouse requires node 5+ or 4 with --harmony');
}

const cli = yargs
  .help('help')
  .version()
  .showHelpOnFail(false, 'Specify --help for available options')

  .usage('$0 url')

  // List of options
  .group([
    'verbose',
    'quiet'
  ], 'Logging:')
  .describe({
    verbose: 'Displays verbose logging',
    quiet: 'Displays no progress or debug logs'
  })

  .group([
    'mobile',
    'load-page',
    'save-assets',
    'save-artifacts',
    'audit-whitelist',
    'list-all-audits',
    'list-trace-categories',
    'config-path'
  ], 'Configuration:')
  .describe({
    'mobile': 'Emulates a Nexus 5X',
    'load-page': 'Loads the page',
    'save-assets': 'Save the trace contents & screenshots to disk',
    'save-artifacts': 'Save all gathered artifacts to disk',
    'audit-whitelist': 'Comma separated list of audits to run',
    'list-all-audits': 'Prints a list of all available audits and exits',
    'list-trace-categories': 'Prints a list of all required trace categories and exits',
    'config-path': 'The absolute path to the config JSON.'
  })

  .group([
    'output',
    'output-path'
  ], 'Output:')
  .describe({
    'output': 'Reporter for the results',
    'output-path': `The file path to output the results
Example: --output-path=./lighthouse-results.html`
  })

  // boolean values
  .boolean([
    'save-assets',
    'save-artifacts',
    'list-all-audits',
    'list-trace-categories',
    'verbose',
    'quiet',
    'help',
    'version'
  ])

  .choices('output', Object.values(Printer.OUTPUT_MODE))

  // default values
  .default('mobile', true)
  .default('load-page', true)
  .default('audit-whitelist', 'all')
  .default('output', Printer.OUTPUT_MODE.pretty)
  .default('output-path', 'stdout')
  .check(argv => {
    // Make sure lighthouse has been passed a url, or at least one of --list-all-audits
    // or --list-trace-categories. If not, stop the program and ask for a url
    if (!argv.listAllAudits && !argv.listTraceCategories && argv._.length === 0) {
      throw new Error('Please provide a url');
    }

    return true;
  })
  .argv;

if (cli.listAllAudits) {
  const audits = lighthouse
      .getAuditList()
      .map(i => {
        return i.replace(/\.js$/, '');
      });

  process.stdout.write(JSON.stringify({audits}));
  process.exit(0);
}

if (cli.listTraceCategories) {
  const traceCategories = lighthouse.traceCategories;

  process.stdout.write(JSON.stringify({traceCategories}));
  process.exit(0);
}

const url = cli._[0];
const outputMode = cli.output;
const outputPath = cli.outputPath;
const flags = cli;
const config = (cli.configPath && require(cli.configPath)) || null;

// set logging preferences
flags.logLevel = 'info';
if (cli.verbose) {
  flags.logLevel = 'verbose';
} else if (cli.quiet) {
  flags.logLevel = 'error';
}

// Normalize audit whitelist.
if (!flags.auditWhitelist || flags.auditWhitelist === 'all') {
  flags.auditWhitelist = null;
} else {
  flags.auditWhitelist = new Set(flags.auditWhitelist.split(',').map(a => a.toLowerCase()));
}

// kick off a lighthouse run
lighthouse(url, flags, config)
  .then(results => Printer.write(results, outputMode, outputPath))
  .then(results => {
    if (outputMode !== 'html') {
      Printer.write(results, 'html', './last-run-results.html');
    }
    return;
  })
  .catch(err => {
    if (err.code === 'ECONNREFUSED') {
      console.error('Unable to connect to Chrome. Did you run ./scripts/launch-chrome.sh ?');
    } else {
      console.error('Runtime error encountered:', err);
      console.error(err.stack);
    }
    process.exit(1);
  });
<commit_msg>update error message on failed connection to use the npm explore npm run chrome cmd<commit_after>#!/usr/bin/env node
/**
 * @license
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const yargs = require('yargs');
const semver = require('semver');
const Printer = require('./printer');
const lighthouse = require('../lighthouse-core');

// node 5.x required due to use of ES2015 features, like spread operator
if (semver.lt(process.version, '5.0.0')) {
  console.warn('Compatibility error', 'Lighthouse requires node 5+ or 4 with --harmony');
}

const cli = yargs
  .help('help')
  .version()
  .showHelpOnFail(false, 'Specify --help for available options')

  .usage('$0 url')

  // List of options
  .group([
    'verbose',
    'quiet'
  ], 'Logging:')
  .describe({
    verbose: 'Displays verbose logging',
    quiet: 'Displays no progress or debug logs'
  })

  .group([
    'mobile',
    'load-page',
    'save-assets',
    'save-artifacts',
    'audit-whitelist',
    'list-all-audits',
    'list-trace-categories',
    'config-path'
  ], 'Configuration:')
  .describe({
    'mobile': 'Emulates a Nexus 5X',
    'load-page': 'Loads the page',
    'save-assets': 'Save the trace contents & screenshots to disk',
    'save-artifacts': 'Save all gathered artifacts to disk',
    'audit-whitelist': 'Comma separated list of audits to run',
    'list-all-audits': 'Prints a list of all available audits and exits',
    'list-trace-categories': 'Prints a list of all required trace categories and exits',
    'config-path': 'The absolute path to the config JSON.'
  })

  .group([
    'output',
    'output-path'
  ], 'Output:')
  .describe({
    'output': 'Reporter for the results',
    'output-path': `The file path to output the results
Example: --output-path=./lighthouse-results.html`
  })

  // boolean values
  .boolean([
    'save-assets',
    'save-artifacts',
    'list-all-audits',
    'list-trace-categories',
    'verbose',
    'quiet',
    'help',
    'version'
  ])

  .choices('output', Object.values(Printer.OUTPUT_MODE))

  // default values
  .default('mobile', true)
  .default('load-page', true)
  .default('audit-whitelist', 'all')
  .default('output', Printer.OUTPUT_MODE.pretty)
  .default('output-path', 'stdout')
  .check(argv => {
    // Make sure lighthouse has been passed a url, or at least one of --list-all-audits
    // or --list-trace-categories. If not, stop the program and ask for a url
    if (!argv.listAllAudits && !argv.listTraceCategories && argv._.length === 0) {
      throw new Error('Please provide a url');
    }

    return true;
  })
  .argv;

if (cli.listAllAudits) {
  const audits = lighthouse
      .getAuditList()
      .map(i => {
        return i.replace(/\.js$/, '');
      });

  process.stdout.write(JSON.stringify({audits}));
  process.exit(0);
}

if (cli.listTraceCategories) {
  const traceCategories = lighthouse.traceCategories;

  process.stdout.write(JSON.stringify({traceCategories}));
  process.exit(0);
}

const url = cli._[0];
const outputMode = cli.output;
const outputPath = cli.outputPath;
const flags = cli;
const config = (cli.configPath && require(cli.configPath)) || null;

// set logging preferences
flags.logLevel = 'info';
if (cli.verbose) {
  flags.logLevel = 'verbose';
} else if (cli.quiet) {
  flags.logLevel = 'error';
}

// Normalize audit whitelist.
if (!flags.auditWhitelist || flags.auditWhitelist === 'all') {
  flags.auditWhitelist = null;
} else {
  flags.auditWhitelist = new Set(flags.auditWhitelist.split(',').map(a => a.toLowerCase()));
}

// kick off a lighthouse run
lighthouse(url, flags, config)
  .then(results => Printer.write(results, outputMode, outputPath))
  .then(results => {
    if (outputMode !== 'html') {
      Printer.write(results, 'html', './last-run-results.html');
    }
    return;
  })
  .catch(err => {
    if (err.code === 'ECONNREFUSED') {
      console.error('Unable to connect to Chrome. Please run Chrome w/ debugging port 9222 open:');
      console.error('    npm explore -g lighthouse -- npm run chrome');
    } else {
      console.error('Runtime error encountered:', err);
      console.error(err.stack);
    }
    process.exit(1);
  });
<|endoftext|>


<commit_before>(function() {
    'use strict';
    angular.module('plotly', []).directive('plotly', [
        '$window',
        function($window) {
            return {
                restrict: 'E',
                template: '

',
                scope: {
                    plotlyData: '=',
                    plotlyLayout: '=',
                    plotlyOptions: '='
                },
                link: function(scope, element) {
                    var graph = element[0].children[0];
                    var initialized = false;

                    function onUpdate() {
                        //No data yet, or clearing out old data
                        if (!(scope.plotlyData)) {
                            if (initialized) {
                                Plotly.Plots.purge(graph);
                                graph.innerHTML = '';
                            }
                            return;
                        }
                        //If this is the first run with data, initialize
                        if (!initialized) {
                            initialized = true;
                            Plotly.newPlot(graph, scope.plotlyData, scope.plotlyLayout, scope.plotlyOptions);
                        }
                        graph.layout = scope.plotlyLayout;
                        graph.data = scope.plotlyData;
                        Plotly.redraw(graph);
                        Plotly.Plots.resize(graph);
                    }

                    function onResize() {
                        if (!(initialized && scope.plotlyData)) return;
                        Plotly.Plots.resize(graph);
                    }

                    scope.$watchGroup([
                        function() {
                            return scope.plotlyLayout;
                        },
                        function() {
                            return scope.plotlyData;
                        }
                    ], function(newValue, oldValue) {
                        if (angular.equals(newValue, oldValue)) return;
                        onUpdate();
                    }, true);

                    scope.$watch(function() {
                        return {
                            'h': element[0].offsetHeight,
                            'w': element[0].offsetWidth
                        };
                    }, function(newValue, oldValue) {
                        if (angular.equals(newValue, oldValue)) return;
                        onResize();
                    }, true);

                    angular.element($window).bind('resize', onResize);
                }
            };
        }
    ]);
})();
<commit_msg>fix init problems<commit_after>(function() {
    'use strict';
    angular.module('plotly', []).directive('plotly', [
        '$window',
        function($window) {
            return {
                restrict: 'E',
                template: '

',
                scope: {
                    plotlyData: '=',
                    plotlyLayout: '=',
                    plotlyOptions: '='
                },
                link: function(scope, element) {
                    var graph = element[0].children[0];
                    var initialized = false;

                    function onUpdate() {
                        //No data yet, or clearing out old data
                        if (!(scope.plotlyData)) {
                            if (initialized) {
                                Plotly.Plots.purge(graph);
                                graph.innerHTML = '';
                            }
                            return;
                        }
                        //If this is the first run with data, initialize
                        if (!initialized) {
                            initialized = true;
                            Plotly.newPlot(graph, scope.plotlyData, scope.plotlyLayout, scope.plotlyOptions);
                        }
                        graph.layout = scope.plotlyLayout;
                        graph.data = scope.plotlyData;
                        Plotly.redraw(graph);
                        Plotly.Plots.resize(graph);
                    }

                    function onResize() {
                        if (!(initialized && scope.plotlyData)) return;
                        Plotly.Plots.resize(graph);
                    }

                    scope.$watchGroup([
                        function(scope) {
                            return scope.plotlyLayout;
                        },
                        function(scope) {
                            return scope.plotlyData;
                        }
                    ], function(newValue, oldValue) {
                        if (angular.equals(newValue, oldValue) && initialized) return;
                        onUpdate();
                    }, true);

                    scope.$watch(function() {
                        return {
                            'h': element[0].offsetHeight,
                            'w': element[0].offsetWidth
                        };
                    }, function(newValue, oldValue) {
                        if (angular.equals(newValue, oldValue)) return;
                        onResize();
                    }, true);

                    angular.element($window).bind('resize', onResize);
                }
            };
        }
    ]);
})();
<|endoftext|>


<commit_before>/**
 * returns a localised string
 * @param {String} str
 * @param {String} locale (optional)
 */
function _t() {
	var args = _t.arguments;
	var str = args[0] ? args[0] : "";
	var localestr = (args[1] && args[1].length > 0) ? args[1] : locale;
	return (localeStrings[localestr] && localeStrings[localestr][str]) ? localeStrings[localestr][str] : '???';
}

/**
 * Documents the error to the console
 * @param {Error} error
 */
function onError(error) {
	console.error(`a11y.css error: ${error}`);
}

/**
 * Documents what we got from a STORAGE.get()
 * @param {Object} item
 */
function onGot(item) {
	console.info(`a11y.css got`, item);
}
<commit_msg>Add fallback to English locale if translation not found<commit_after>/**
 * returns a localised string
 * @param {String} str
 * @param {String} locale (optional)
 */
function _t() {
	const args = _t.arguments;
	const str = args[0] || '';
	const localestr = (args[1] && args[1].length > 0) ? args[1] : locale;
	const defaultLocale = 'en';
	return (localeStrings[localestr] && localeStrings[localestr][str]) ? localeStrings[localestr][str] : localeStrings[defaultLocale][str];
}

/**
 * Documents the error to the console
 * @param {Error} error
 */
function onError(error) {
	console.error(`a11y.css error: ${error}`);
}

/**
 * Documents what we got from a STORAGE.get()
 * @param {Object} item
 */
function onGot(item) {
	console.info(`a11y.css got`, item);
}
<|endoftext|>


<commit_before>var mbaasApi = require('fh-mbaas-api');
var express = require('express');
var mbaasExpress = mbaasApi.mbaasExpress();
var cors = require('cors');

// list the endpoints which you want to make securable here
var securableEndpoints;
// fhlint-begin: securable-endpoints
securableEndpoints = ['/hello'];
// fhlint-end

var app = express();

// Enable CORS for all requests
app.use(cors());

// Note: add this first to override mbaas api
app.get('/mbaas/forms/:appId/:formId', function(req, res, next) {
  console.log("Lookup form: " + req.params.formId);
  if (req.params.formId == '560c2d68d1596bad10c427d3') {
    mbaasApi.forms.getForm({
     "_id": req.params.formId
    }, function (err, form) {
     if (err) return res.status(500).json(err);
     var options = {
        "act": "list",
        "type": "cliente", // Entity/Collection name
     };
     mbaasApi.db(options, function (err, data) {
        if (err) {
          console.error("Error " + err);
        } else {
          console.log(JSON.stringify(data));
        }
     });
     console.log("Retrieved form: " + JSON.stringify(form));
     return res.json(form);
    });
  } else {
    next();
  }
});

// Note: the order which we add middleware to Express here is important!
app.use('/sys', mbaasExpress.sys(securableEndpoints));
app.use('/mbaas', mbaasExpress.mbaas);

// allow serving of static files from the public directory
app.use(express.static(__dirname + '/public'));

// Note: important that this is added just before your own Routes
app.use(mbaasExpress.fhmiddleware());

// fhlint-begin: custom-routes
//app.use('/hello', require('./lib/overrider.js')());
// fhlint-end

// Important that this is last!
app.use(mbaasExpress.errorHandler());

var port = process.env.FH_PORT || process.env.OPENSHIFT_NODEJS_PORT || 8001;
var host = process.env.OPENSHIFT_NODEJS_IP || '0.0.0.0';
var server = app.listen(port, host, function() {
  console.log("App started at: " + new Date() + " on port: " + port);
});
<commit_msg>update file /application.js<commit_after>var mbaasApi = require('fh-mbaas-api');
var express = require('express');
var mbaasExpress = mbaasApi.mbaasExpress();
var cors = require('cors');

// list the endpoints which you want to make securable here
var securableEndpoints;
// fhlint-begin: securable-endpoints
securableEndpoints = ['/hello'];
// fhlint-end

var app = express();

// Enable CORS for all requests
app.use(cors());

// Note: add this first to override mbaas api
app.get('/mbaas/forms/:appId/:formId', function(req, res, next) {
  console.log("Lookup form: " + req.params.formId);
  if (req.params.formId == '560c2d68d1596bad10c427d3') {
    mbaasApi.forms.getForm({
     "_id": req.params.formId
    }, function (err, form) {
     if (err) return res.status(500).json(err);
     console.log("Retrieved form: " + form.name);
     var options = {
        "act": "list",
        "type": "cliente", // Entity/Collection name
     };
     mbaasApi.db(options, function (err, data) {
        if (err) return res.status(500).json(err);
        console.log(JSON.stringify(data));
     });
     return res.json(form);
    });
  } else {
    next();
  }
});

// Note: the order which we add middleware to Express here is important!
app.use('/sys', mbaasExpress.sys(securableEndpoints));
app.use('/mbaas', mbaasExpress.mbaas);

// allow serving of static files from the public directory
app.use(express.static(__dirname + '/public'));

// Note: important that this is added just before your own Routes
app.use(mbaasExpress.fhmiddleware());

// fhlint-begin: custom-routes
//app.use('/hello', require('./lib/overrider.js')());
// fhlint-end

// Important that this is last!
app.use(mbaasExpress.errorHandler());

var port = process.env.FH_PORT || process.env.OPENSHIFT_NODEJS_PORT || 8001;
var host = process.env.OPENSHIFT_NODEJS_IP || '0.0.0.0';
var server = app.listen(port, host, function() {
  console.log("App started at: " + new Date() + " on port: " + port);
});
<|endoftext|>


<commit_before>/**
 * Types for things in the Zulip data model, as seen in the API.
 *
 * @flow strict
 */

//
//
//
// ===================================================================
// Data attached to the realm or the server.
//
//

export type ImageEmojiType = $ReadOnly<{|
  author?: $ReadOnly<{|
    email: string,
    full_name: string,
    id: number,
  |}>,
  deactivated: boolean,
  id?: number,
  code: string,
  name: string,
  source_url: string,
|}>;

export type RealmEmojiById = $ReadOnly<{
  [id: string]: ImageEmojiType,
}>;

export type RealmFilter = [string, string, number];

//
//
//
// ===================================================================
// Users and data about them.
//
//

export type DevUser = {|
  realm_uri: string,
  email: string,
|};

/**
 * A Zulip user.
 *
 * This is a user object as found in properties `realm_users` and
 * `realm_non_active_users` of a `/register` response.
 *
 * For details on the properties, see the Zulip API docs on `/users`:
 *   https://zulipchat.com/api/get-all-users#response
 * which returns almost the same set of properties.
 *
 * See also the comments on `UserProfile` in the server (lineno is approx.):
 *   https://github.com/zulip/zulip/blob/master/zerver/models.py#L734
 * Most properties correspond to fields on `UserProfile`, and many are
 * described most usefully there.
 *
 * For authoritative results, consult how `raw_users`, and then
 * `realm_users` and `realm_non_active_users`, are computed in
 * `zulip/zulip:zerver/lib/events.py` .
 *
 * Properties are listed below in the order they appear on `UserProfile`,
 * because that's the most logically-organized and also the most helpful
 * of the references above.
 */
export type User = {|
  user_id: number,
  email: string,

  full_name: string,

  // date_joined included since commit 372e9740a (in 1.9.0)
  date_joined?: string,

  // is_active doesn't appear in `/register` responses -- instead,
  // users where is_active is true go in `realm_users`, and where false
  // go in `realm_non_active_users`.  Shrug.

  // is_admin corresponds to is_realm_admin in server code.
  is_admin: boolean,

  // is_guest included since commit d5df0377c (in 1.9.0); before that,
  // there's no such concept, so effectively it's implicitly false.
  is_guest?: boolean,

  is_bot: boolean,
  bot_type?: number,
  bot_owner?: string,

  timezone: string,

  // avatar_url is synthesized on the server by `get_avatar_field`.
  avatar_url: string | null,

  // profile_data added in commit 02b845336 (in 1.8.0);
  // see also e3aed0f7b (in 2.0.0)
  // (This one doesn't appear in `/users` responses.)
  profile_data?: empty, // TODO describe actual type
|};

export type CrossRealmBot = {|
  // avatar_url included since commit 58ee3fa8c (in 1.9.0)
  // TODO(crunchy): convert missing -> null
  avatar_url?: string | null,

  // date_joined included since commit 58ee3fa8c (in 1.9.0)
  date_joined?: string,

  email: string,
  full_name: string,
  is_admin: boolean,
  is_bot: true,
  user_id: number,

  // timezone included since commit 58ee3fa8c (in 1.9.0)
  timezone?: string,
|};

export type UserGroup = {|
  description: string,
  id: number,
  members: number[],
  name: string,
|};

/**
 * Specifies user status related properties
 * @prop away - present if we are to override user's presence status
 *   * This is the "user status" / "unavailable" feature added in early 2019.
 *     (At time of writing, there are no docs to link to.)
 * @prop status_text - a string representing information the user decided to
 *   manually set as their 'current status'
 */
export type UserStatus = {|
  away?: true,
  status_text?: string,
|};

export type UserStatusMapObject = {|
  [userId: number]: UserStatus,
|};

/** See ClientPresence, and the doc linked there. */
export type PresenceStatus = 'active' | 'idle' | 'offline';

/**
 * A user's presence status, as reported by a specific client.
 *
 * For an explanation of the Zulip presence model and how to interpret
 * `status` and `timestamp`, see the subsystem doc:
 *   https://zulip.readthedocs.io/en/latest/subsystems/presence.html
 *
 * @prop timestamp - When the server last heard from this client.
 * @prop status - See the presence subsystem doc.
 * @prop client
 * @prop pushable - Legacy; unused.
 */
export type ClientPresence = {|
  status: PresenceStatus,
  timestamp: number,
  client: string,

  /**
   * Indicates if the client can receive push notifications. This property
   * was intended for showing a user's presence status as "on mobile" if
   * they are inactive on all devices but can receive push notifications
   * (see zulip/zulip bd20a756f9). However, this property doesn't seem to be
   * used anywhere on the web app or the mobile client, and can be
   * considered legacy.
   *
   * Empirically this is `boolean` on at least some clients, and absent on
   * `aggregated`.  By writing `empty` we make it an error to actually use it.
   */
  pushable?: empty,
|};

/**
 * A user's presence status, including all information from all their clients.
 *
 * The `aggregated` property equals one of the others.  For details, see:
 *   https://zulipchat.com/api/get-presence
 *
 * See also the app's `getAggregatedPresence`, which reimplements a version
 * of the logic to compute `aggregated`.
 */
export type UserPresence = {|
  aggregated: ClientPresence,
  [client: string]: ClientPresence,
|};

//
//
//
// ===================================================================
// Streams, topics, and stuff about them.
//
//

export type Stream = {|
  stream_id: number,
  description: string,
  name: string,
  invite_only: boolean,
  is_announcement_only: boolean,
  history_public_to_subscribers: boolean,
|};

export type Subscription = {|
  ...$Exact<Stream>,
  color: string,
  in_home_view: boolean,
  pin_to_top: boolean,
  audible_notifications: boolean,
  desktop_notifications: boolean,
  email_address: string,
  is_old_stream: boolean,
  push_notifications: boolean,
  stream_weekly_traffic: number,
|};

export type Topic = {|
  name: string,
  max_id: number,
|};

//
//
//
// ===================================================================
// Narrows.
//
//

export type NarrowOperator =
  | 'is'
  | 'in'
  | 'near'
  | 'id'
  | 'stream'
  | 'topic'
  | 'sender'
  | 'pm-with'
  | 'search';

export type NarrowElement = $ReadOnly<{|
  operand: string,
  operator?: NarrowOperator, // TODO type: this shouldn't be absent.
|}>;

export type Narrow = $ReadOnlyArray<NarrowElement>;

//
//
//
// ===================================================================
// Messages and things attached to them.
//
//

/**
 * Type of an emoji reaction to a message.
 *
 * These correspond to the values allowed for Reaction.reaction_type in the
 * server's models.  The values are:
 *  * unicode_emoji: An emoji found in Unicode, corresponding to a sequence
 *    of Unicode code points.  The list of these depends on the Zulip
 *    server's version.
 *  * realm_emoji: A custom emoji uploaded by some user on a given realm.
 *  * zulip_extra_emoji: An emoji distributed with Zulip, like :zulip:.
 *
 * See `Reaction` which uses this.
 */
export type ReactionType = 'unicode_emoji' | 'realm_emoji' | 'zulip_extra_emoji';

/** An emoji reaction to a message. */
export type Reaction = $ReadOnly<{|
  user: $ReadOnly<{|
    email: string,
    full_name: string,
    user_id: number,
  |}>,
  emoji_name: string,
  reaction_type: ReactionType,

  /**
   * A string that uniquely identifies a particular emoji.
   *
   * The format varies with `reaction_type`, and can be subtle.
   * See the comment on Reaction.emoji_code here:
   *   https://github.com/zulip/zulip/blob/master/zerver/models.py
   */
  emoji_code: string,
|}>;

/**
 * "Snapshot" objects from https://zulipchat.com/api/get-message-history .
 *
 * See also `MessageEdit`.
 */
export type MessageSnapshot = $ReadOnly<{|
  user_id: number,
  timestamp: number,

  /** Docs unclear but suggest absent if only content edited. */
  topic?: string,

  /**
   * Docs unclear, but suggest these five absent if only topic edited.
   * They definitely say "prev"/"diff" properties absent on the first snapshot.
   */
  content?: string,
  rendered_content?: string,
  prev_content?: string,
  prev_rendered_content?: string,
  content_html_diff?: string,
|}>;

/**
 * Found in the history within a `Message` object.
 *
 * See also `MessageSnapshot`.
 */
export type MessageEdit = $ReadOnly<{|
  prev_content?: string,
  prev_rendered_content?: string,
  prev_rendered_content_version?: number,
  prev_subject?: string,
  timestamp: number,
  user_id: number,
|}>;

/** A user, as seen in the `display_recipient` of a PM `Message`. */
export type PmRecipientUser = {|
  email: string,
  full_name: string,
  id: number,
  is_mirror_dummy: boolean,
  short_name: string,
|};

/**
 * Submessages are items containing extra data that can be added to a
 * message. Despite what their name might suggest, they are not a subtype
 * of the `Message` type, nor do they share almost any fields with it.
 *
 * Submessages are used by Widgets:
 * https://zulip.readthedocs.io/en/latest/subsystems/widgets.html
 *
 * Normally a message contains an empty array of these. We differentiate
 * between a normal message and a widget by the length of `submessages`
 * array property. Widgets will have 1 or more submessages.
 */
export type Submessage = $ReadOnly<{|
  id: number,
  message_id: number,
  sender_id: number,
  msg_type: 'widget', // only this type is currently available
  content: string, // JSON string
|}>;

/**
 * A Zulip message.
 *
 * This type is mainly intended to represent the data the server sends as
 * the `message` property of an event of type `message`.  Caveat lector: we
 * pass these around to a lot of places, and do a lot of further munging, so
 * this type may not quite represent that.  Any differences should
 * definitely be commented, and perhaps refactored.
 *
 * The server's behavior here is undocumented and the source is very
 * complex; this is naturally a place where a large fraction of all the
 * features of Zulip have to appear.
 *
 * Major appearances of this type include
 *  * `message: Message` on a server event of type `message`, and our
 *    `EVENT_NEW_MESSAGE` Redux action for the event;
 *  * `messages: Message[]` in a `/messages` (our `getMessages`) response,
 *    and our resulting `MESSAGE_FETCH_COMPLETE` Redux action;
 *  * `messages: { [id]: Message }` in our global Redux state.
 *
 * References include:
 *  * the two example events at https://zulipchat.com/api/get-events-from-queue
 *  * `process_message_event` in zerver/tornado/event_queue.py; the call
 *    `client.add_event(user_event)` makes the final determination of what
 *    goes into the event, so `message_dict` is the final value of `message`
 *  * `MessageDict.wide_dict` and its helpers in zerver/lib/message.py;
 *    via `do_send_messages` in `zerver/lib/actions.py`, these supply most
 *    of the data ultimately handled by `process_message_event`
 *  * the `Message` and `AbstractMessage` models in zerver/models.py, but
 *    with caution; many fields are adjusted between the DB row and the event
 *  * empirical study looking at Redux events logged [to the
 *    console](docs/howto/debugging.md).
 *
 * See also `Outbox`, which is deliberately similar so that we can use
 * the type `Message | Outbox` in many places.
 *
 * See also `MessagesState` for discussion of how we fetch and store message
 * data.
 */
export type Message = $ReadOnly<{
  /** Our own flag; if true, really type `Outbox`. */
  isOutbox: false,

  /**
   * These don't appear in `message` events, but they appear in a `/message`
   * response when a search is involved.
   */
  match_content?: string,
  match_subject?: string,

  /** Obsolete? Gone in server commit 1.6.0~1758 . */
  sender_domain: string,

  /**
   * The `flags` story is a bit complicated:
   *  * Absent in `event.message` for a `message` event... but instead
   *    (confusingly) there is an `event.flags`.
   *  * Present under `EVENT_NEW_MESSAGE`.
   *  * Present under `MESSAGE_FETCH_COMPLETE`, and in the server `/message`
   *    response that action is based on.
   *  * Absent in the Redux `state.messages`; we move the information to a
   *    separate subtree `state.flags`.
   */
  flags?: $ReadOnlyArray<string>,

  /** The rest are believed to really appear in `message` events. */
  avatar_url: string | null,
  client: string,
  content: string,
  content_type: 'text/html' | 'text/markdown',
  display_recipient: $FlowFixMe, // `string` for type stream, else PmRecipientUser[].
  edit_history: $ReadOnlyArray<MessageEdit>,
  gravatar_hash: string,
  id: number,
  is_me_message: boolean,
  last_edit_timestamp?: number,
  reactions: $ReadOnlyArray<Reaction>,
  recipient_id: number,
  sender_email: string,
  sender_full_name: string,
  sender_id: number,
  sender_realm_str: string,
  sender_short_name: string,
  stream_id: number, // FixMe: actually only for type `stream`, else absent.
  subject: string,
  subject_links: $ReadOnlyArray<string>,

  /** Older servers omit this; when omitted, equivalent to empty array. */
  submessages?: $ReadOnlyArray<Submessage>,

  timestamp: number,
  type: 'stream' | 'private',
}>;
<commit_msg>api: Adjust Reaction type to describe complexity in API.<commit_after>/**
 * Types for things in the Zulip data model, as seen in the API.
 *
 * @flow strict
 */

//
//
//
// ===================================================================
// Data attached to the realm or the server.
//
//

export type ImageEmojiType = $ReadOnly<{|
  author?: $ReadOnly<{|
    email: string,
    full_name: string,
    id: number,
  |}>,
  deactivated: boolean,
  id?: number,
  code: string,
  name: string,
  source_url: string,
|}>;

export type RealmEmojiById = $ReadOnly<{
  [id: string]: ImageEmojiType,
}>;

export type RealmFilter = [string, string, number];

//
//
//
// ===================================================================
// Users and data about them.
//
//

export type DevUser = {|
  realm_uri: string,
  email: string,
|};

/**
 * A Zulip user.
 *
 * This is a user object as found in properties `realm_users` and
 * `realm_non_active_users` of a `/register` response.
 *
 * For details on the properties, see the Zulip API docs on `/users`:
 *   https://zulipchat.com/api/get-all-users#response
 * which returns almost the same set of properties.
 *
 * See also the comments on `UserProfile` in the server (lineno is approx.):
 *   https://github.com/zulip/zulip/blob/master/zerver/models.py#L734
 * Most properties correspond to fields on `UserProfile`, and many are
 * described most usefully there.
 *
 * For authoritative results, consult how `raw_users`, and then
 * `realm_users` and `realm_non_active_users`, are computed in
 * `zulip/zulip:zerver/lib/events.py` .
 *
 * Properties are listed below in the order they appear on `UserProfile`,
 * because that's the most logically-organized and also the most helpful
 * of the references above.
 */
export type User = {|
  user_id: number,
  email: string,

  full_name: string,

  // date_joined included since commit 372e9740a (in 1.9.0)
  date_joined?: string,

  // is_active doesn't appear in `/register` responses -- instead,
  // users where is_active is true go in `realm_users`, and where false
  // go in `realm_non_active_users`.  Shrug.

  // is_admin corresponds to is_realm_admin in server code.
  is_admin: boolean,

  // is_guest included since commit d5df0377c (in 1.9.0); before that,
  // there's no such concept, so effectively it's implicitly false.
  is_guest?: boolean,

  is_bot: boolean,
  bot_type?: number,
  bot_owner?: string,

  timezone: string,

  // avatar_url is synthesized on the server by `get_avatar_field`.
  avatar_url: string | null,

  // profile_data added in commit 02b845336 (in 1.8.0);
  // see also e3aed0f7b (in 2.0.0)
  // (This one doesn't appear in `/users` responses.)
  profile_data?: empty, // TODO describe actual type
|};

export type CrossRealmBot = {|
  // avatar_url included since commit 58ee3fa8c (in 1.9.0)
  // TODO(crunchy): convert missing -> null
  avatar_url?: string | null,

  // date_joined included since commit 58ee3fa8c (in 1.9.0)
  date_joined?: string,

  email: string,
  full_name: string,
  is_admin: boolean,
  is_bot: true,
  user_id: number,

  // timezone included since commit 58ee3fa8c (in 1.9.0)
  timezone?: string,
|};

export type UserGroup = {|
  description: string,
  id: number,
  members: number[],
  name: string,
|};

/**
 * Specifies user status related properties
 * @prop away - present if we are to override user's presence status
 *   * This is the "user status" / "unavailable" feature added in early 2019.
 *     (At time of writing, there are no docs to link to.)
 * @prop status_text - a string representing information the user decided to
 *   manually set as their 'current status'
 */
export type UserStatus = {|
  away?: true,
  status_text?: string,
|};

export type UserStatusMapObject = {|
  [userId: number]: UserStatus,
|};

/** See ClientPresence, and the doc linked there. */
export type PresenceStatus = 'active' | 'idle' | 'offline';

/**
 * A user's presence status, as reported by a specific client.
 *
 * For an explanation of the Zulip presence model and how to interpret
 * `status` and `timestamp`, see the subsystem doc:
 *   https://zulip.readthedocs.io/en/latest/subsystems/presence.html
 *
 * @prop timestamp - When the server last heard from this client.
 * @prop status - See the presence subsystem doc.
 * @prop client
 * @prop pushable - Legacy; unused.
 */
export type ClientPresence = {|
  status: PresenceStatus,
  timestamp: number,
  client: string,

  /**
   * Indicates if the client can receive push notifications. This property
   * was intended for showing a user's presence status as "on mobile" if
   * they are inactive on all devices but can receive push notifications
   * (see zulip/zulip bd20a756f9). However, this property doesn't seem to be
   * used anywhere on the web app or the mobile client, and can be
   * considered legacy.
   *
   * Empirically this is `boolean` on at least some clients, and absent on
   * `aggregated`.  By writing `empty` we make it an error to actually use it.
   */
  pushable?: empty,
|};

/**
 * A user's presence status, including all information from all their clients.
 *
 * The `aggregated` property equals one of the others.  For details, see:
 *   https://zulipchat.com/api/get-presence
 *
 * See also the app's `getAggregatedPresence`, which reimplements a version
 * of the logic to compute `aggregated`.
 */
export type UserPresence = {|
  aggregated: ClientPresence,
  [client: string]: ClientPresence,
|};

//
//
//
// ===================================================================
// Streams, topics, and stuff about them.
//
//

export type Stream = {|
  stream_id: number,
  description: string,
  name: string,
  invite_only: boolean,
  is_announcement_only: boolean,
  history_public_to_subscribers: boolean,
|};

export type Subscription = {|
  ...$Exact<Stream>,
  color: string,
  in_home_view: boolean,
  pin_to_top: boolean,
  audible_notifications: boolean,
  desktop_notifications: boolean,
  email_address: string,
  is_old_stream: boolean,
  push_notifications: boolean,
  stream_weekly_traffic: number,
|};

export type Topic = {|
  name: string,
  max_id: number,
|};

//
//
//
// ===================================================================
// Narrows.
//
//

export type NarrowOperator =
  | 'is'
  | 'in'
  | 'near'
  | 'id'
  | 'stream'
  | 'topic'
  | 'sender'
  | 'pm-with'
  | 'search';

export type NarrowElement = $ReadOnly<{|
  operand: string,
  operator?: NarrowOperator, // TODO type: this shouldn't be absent.
|}>;

export type Narrow = $ReadOnlyArray<NarrowElement>;

//
//
//
// ===================================================================
// Messages and things attached to them.
//
//

/**
 * Type of an emoji reaction to a message.
 *
 * These correspond to the values allowed for Reaction.reaction_type in the
 * server's models.  The values are:
 *  * unicode_emoji: An emoji found in Unicode, corresponding to a sequence
 *    of Unicode code points.  The list of these depends on the Zulip
 *    server's version.
 *  * realm_emoji: A custom emoji uploaded by some user on a given realm.
 *  * zulip_extra_emoji: An emoji distributed with Zulip, like :zulip:.
 *
 * See `Reaction` which uses this.
 */
export type ReactionType = 'unicode_emoji' | 'realm_emoji' | 'zulip_extra_emoji';

/** An emoji reaction to a message. */
export type Reaction = $ReadOnly<{|
  user: $ReadOnly<{|
    email: string,
    full_name: string,
    user_id?: number, //  present in reaction add event
    id?: number, // present when messages are fetched via `getMessages` API
  |}>,
  emoji_name: string,
  reaction_type: ReactionType,

  /**
   * A string that uniquely identifies a particular emoji.
   *
   * The format varies with `reaction_type`, and can be subtle.
   * See the comment on Reaction.emoji_code here:
   *   https://github.com/zulip/zulip/blob/master/zerver/models.py
   */
  emoji_code: string,
|}>;

/**
 * "Snapshot" objects from https://zulipchat.com/api/get-message-history .
 *
 * See also `MessageEdit`.
 */
export type MessageSnapshot = $ReadOnly<{|
  user_id: number,
  timestamp: number,

  /** Docs unclear but suggest absent if only content edited. */
  topic?: string,

  /**
   * Docs unclear, but suggest these five absent if only topic edited.
   * They definitely say "prev"/"diff" properties absent on the first snapshot.
   */
  content?: string,
  rendered_content?: string,
  prev_content?: string,
  prev_rendered_content?: string,
  content_html_diff?: string,
|}>;

/**
 * Found in the history within a `Message` object.
 *
 * See also `MessageSnapshot`.
 */
export type MessageEdit = $ReadOnly<{|
  prev_content?: string,
  prev_rendered_content?: string,
  prev_rendered_content_version?: number,
  prev_subject?: string,
  timestamp: number,
  user_id: number,
|}>;

/** A user, as seen in the `display_recipient` of a PM `Message`. */
export type PmRecipientUser = {|
  email: string,
  full_name: string,
  id: number,
  is_mirror_dummy: boolean,
  short_name: string,
|};

/**
 * Submessages are items containing extra data that can be added to a
 * message. Despite what their name might suggest, they are not a subtype
 * of the `Message` type, nor do they share almost any fields with it.
 *
 * Submessages are used by Widgets:
 * https://zulip.readthedocs.io/en/latest/subsystems/widgets.html
 *
 * Normally a message contains an empty array of these. We differentiate
 * between a normal message and a widget by the length of `submessages`
 * array property. Widgets will have 1 or more submessages.
 */
export type Submessage = $ReadOnly<{|
  id: number,
  message_id: number,
  sender_id: number,
  msg_type: 'widget', // only this type is currently available
  content: string, // JSON string
|}>;

/**
 * A Zulip message.
 *
 * This type is mainly intended to represent the data the server sends as
 * the `message` property of an event of type `message`.  Caveat lector: we
 * pass these around to a lot of places, and do a lot of further munging, so
 * this type may not quite represent that.  Any differences should
 * definitely be commented, and perhaps refactored.
 *
 * The server's behavior here is undocumented and the source is very
 * complex; this is naturally a place where a large fraction of all the
 * features of Zulip have to appear.
 *
 * Major appearances of this type include
 *  * `message: Message` on a server event of type `message`, and our
 *    `EVENT_NEW_MESSAGE` Redux action for the event;
 *  * `messages: Message[]` in a `/messages` (our `getMessages`) response,
 *    and our resulting `MESSAGE_FETCH_COMPLETE` Redux action;
 *  * `messages: { [id]: Message }` in our global Redux state.
 *
 * References include:
 *  * the two example events at https://zulipchat.com/api/get-events-from-queue
 *  * `process_message_event` in zerver/tornado/event_queue.py; the call
 *    `client.add_event(user_event)` makes the final determination of what
 *    goes into the event, so `message_dict` is the final value of `message`
 *  * `MessageDict.wide_dict` and its helpers in zerver/lib/message.py;
 *    via `do_send_messages` in `zerver/lib/actions.py`, these supply most
 *    of the data ultimately handled by `process_message_event`
 *  * the `Message` and `AbstractMessage` models in zerver/models.py, but
 *    with caution; many fields are adjusted between the DB row and the event
 *  * empirical study looking at Redux events logged [to the
 *    console](docs/howto/debugging.md).
 *
 * See also `Outbox`, which is deliberately similar so that we can use
 * the type `Message | Outbox` in many places.
 *
 * See also `MessagesState` for discussion of how we fetch and store message
 * data.
 */
export type Message = $ReadOnly<{
  /** Our own flag; if true, really type `Outbox`. */
  isOutbox: false,

  /**
   * These don't appear in `message` events, but they appear in a `/message`
   * response when a search is involved.
   */
  match_content?: string,
  match_subject?: string,

  /** Obsolete? Gone in server commit 1.6.0~1758 . */
  sender_domain: string,

  /**
   * The `flags` story is a bit complicated:
   *  * Absent in `event.message` for a `message` event... but instead
   *    (confusingly) there is an `event.flags`.
   *  * Present under `EVENT_NEW_MESSAGE`.
   *  * Present under `MESSAGE_FETCH_COMPLETE`, and in the server `/message`
   *    response that action is based on.
   *  * Absent in the Redux `state.messages`; we move the information to a
   *    separate subtree `state.flags`.
   */
  flags?: $ReadOnlyArray<string>,

  /** The rest are believed to really appear in `message` events. */
  avatar_url: string | null,
  client: string,
  content: string,
  content_type: 'text/html' | 'text/markdown',
  display_recipient: $FlowFixMe, // `string` for type stream, else PmRecipientUser[].
  edit_history: $ReadOnlyArray<MessageEdit>,
  gravatar_hash: string,
  id: number,
  is_me_message: boolean,
  last_edit_timestamp?: number,
  reactions: $ReadOnlyArray<Reaction>,
  recipient_id: number,
  sender_email: string,
  sender_full_name: string,
  sender_id: number,
  sender_realm_str: string,
  sender_short_name: string,
  stream_id: number, // FixMe: actually only for type `stream`, else absent.
  subject: string,
  subject_links: $ReadOnlyArray<string>,

  /** Older servers omit this; when omitted, equivalent to empty array. */
  submessages?: $ReadOnlyArray<Submessage>,

  timestamp: number,
  type: 'stream' | 'private',
}>;
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import { FluentBundle, FluentResource } from '@fluent/bundle';
import {
  PSEUDO_STRATEGIES,
  PSEUDO_STRATEGIES_DIRECTION,
} from 'firefox-profiler/utils/l10n-pseudo';

// This contains the locales we support in the production. Don't forget to update
// the array RTL_LOCALES when adding a RTL locale, if necessary.
// AVAILABLE_STAGING_LOCALES is replaced, using webpack's DefinePlugin, by all
// available locales, when running with L10N=1, using the "l10n" versions of the
// yarn scripts. Especially this is done when building and deploying the l10n
// branch in netlify.
export const AVAILABLE_LOCALES: Array<string> = AVAILABLE_STAGING_LOCALES || [
  'de',
  'el',
  'en-GB',
  'en-US',
  'es-CL',
  'ia',
  'it',
  'pt-BR',
  'zh-CN',
  'zh-TW',
];
export const DEFAULT_LOCALE = 'en-US';

// This array contains only the locales that are RTL (Right-To-Left).
// This list has been copied from the l20n library in the Firefox OS project
// (ancestor of Fluent):
// https://github.com/mozilla-b2g/gaia/blob/975a35c0f5010df341e96d6c5ec60217f5347412/shared/js/intl/l20n-client.js#L31-L35
const RTL_LOCALES = ['ar', 'he', 'fa', 'ps', 'ur'];

/**
 * Fetches ftl file of different locales.
 * Returns the locale and the ftl string grouped as an Array.
 */
export async function fetchMessages(locale: string): Promise<[string, string]> {
  const response = await fetch(`/locales/${locale}/app.ftl`);
  const messages = await response.text();
  return [locale, messages];
}

/**
 * A generator function responsible for building the sequence
 * of FluentBundle instances in the order of user's language
 * preferences.
 */
export function* lazilyParsedBundles(
  fetchedMessages: Array<[string, string]>,
  pseudoStrategy?: 'accented' | 'bidi'
): Generator<FluentBundle, void, void> {
  const transform = pseudoStrategy
    ? PSEUDO_STRATEGIES[pseudoStrategy]
    : undefined;
  for (const [locale, messages] of fetchedMessages) {
    const resource = new FluentResource(messages);
    const bundle = new FluentBundle(locale, {
      transform,
    });
    bundle.addResource(resource);
    yield bundle;
  }
}

/**
 * From a language string and a pseudoStrategy, this returns which direction the
 * document should be in.
 */
export function getLocaleDirection(
  language: string,
  pseudoStrategy?: 'accented' | 'bidi'
): 'ltr' | 'rtl' {
  if (pseudoStrategy && pseudoStrategy in PSEUDO_STRATEGIES_DIRECTION) {
    return PSEUDO_STRATEGIES_DIRECTION[pseudoStrategy];
  }

  // The language can be simple like "fr" but also more precise like "fr-FR".
  const tag = language.split('-')[0];
  return RTL_LOCALES.includes(tag) ? 'rtl' : 'ltr';
}
<commit_msg>Enable 3 more locales that are above the threshold (PR #3568)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow

import { FluentBundle, FluentResource } from '@fluent/bundle';
import {
  PSEUDO_STRATEGIES,
  PSEUDO_STRATEGIES_DIRECTION,
} from 'firefox-profiler/utils/l10n-pseudo';

// This contains the locales we support in the production. Don't forget to update
// the array RTL_LOCALES when adding a RTL locale, if necessary.
// AVAILABLE_STAGING_LOCALES is replaced, using webpack's DefinePlugin, by all
// available locales, when running with L10N=1, using the "l10n" versions of the
// yarn scripts. Especially this is done when building and deploying the l10n
// branch in netlify.
export const AVAILABLE_LOCALES: Array<string> = AVAILABLE_STAGING_LOCALES || [
  'de',
  'el',
  'en-GB',
  'en-US',
  'es-CL',
  'ia',
  'it',
  'nl',
  'pt-BR',
  'sv-SE',
  'uk',
  'zh-CN',
  'zh-TW',
];
export const DEFAULT_LOCALE = 'en-US';

// This array contains only the locales that are RTL (Right-To-Left).
// This list has been copied from the l20n library in the Firefox OS project
// (ancestor of Fluent):
// https://github.com/mozilla-b2g/gaia/blob/975a35c0f5010df341e96d6c5ec60217f5347412/shared/js/intl/l20n-client.js#L31-L35
const RTL_LOCALES = ['ar', 'he', 'fa', 'ps', 'ur'];

/**
 * Fetches ftl file of different locales.
 * Returns the locale and the ftl string grouped as an Array.
 */
export async function fetchMessages(locale: string): Promise<[string, string]> {
  const response = await fetch(`/locales/${locale}/app.ftl`);
  const messages = await response.text();
  return [locale, messages];
}

/**
 * A generator function responsible for building the sequence
 * of FluentBundle instances in the order of user's language
 * preferences.
 */
export function* lazilyParsedBundles(
  fetchedMessages: Array<[string, string]>,
  pseudoStrategy?: 'accented' | 'bidi'
): Generator<FluentBundle, void, void> {
  const transform = pseudoStrategy
    ? PSEUDO_STRATEGIES[pseudoStrategy]
    : undefined;
  for (const [locale, messages] of fetchedMessages) {
    const resource = new FluentResource(messages);
    const bundle = new FluentBundle(locale, {
      transform,
    });
    bundle.addResource(resource);
    yield bundle;
  }
}

/**
 * From a language string and a pseudoStrategy, this returns which direction the
 * document should be in.
 */
export function getLocaleDirection(
  language: string,
  pseudoStrategy?: 'accented' | 'bidi'
): 'ltr' | 'rtl' {
  if (pseudoStrategy && pseudoStrategy in PSEUDO_STRATEGIES_DIRECTION) {
    return PSEUDO_STRATEGIES_DIRECTION[pseudoStrategy];
  }

  // The language can be simple like "fr" but also more precise like "fr-FR".
  const tag = language.split('-')[0];
  return RTL_LOCALES.includes(tag) ? 'rtl' : 'ltr';
}
<|endoftext|>


<commit_before>// Filter functions
// ----------------

const utils = require("./utils");

var artist = function(player, args, callback) {
    // Songs from current artist
    saveFreemodeIndex(player);

    var query = {
        "sql": "SELECT id FROM items WHERE artist = ?",
        "param": player.currentData.artist
    };
    sqlfilter(query, "artist", player, callback);
};

var album = function(player, args, callback) {
    // Songs from current album
    saveFreemodeIndex(player);

    var query = {
        "sql": "SELECT id FROM items WHERE album = ?",
        "param": player.currentData.album
    };
    sqlfilter(query, "album", player, callback);
};

var search = function(player, args, callback) {
    // Search results
    saveFreemodeIndex(player);

    // Generate query
    // + is union
    // - is exception
    // others are intersected

    // Clean up continuous repetitions
    var cleaned = args.filter(function(item, index, arr) {
        return (index == 0) || (item != arr[index-1]);
    });

    // Remove first +
    if (cleaned[0] == "+") {
        cleaned.shift();
    }
    // Remove ending operators +/-
    while (["+", "-"].indexOf(cleaned[cleaned.length-1]) > -1) {
        cleaned.pop();
    }
    console.log(cleaned);

    var sql = [],
        param = [],
        inWords = false;
    // Wrap around
    sql.push("SELECT id FROM");
    cleaned.forEach(function(item, index) {
        if (item == "+") {
            if ((!inWords) && (index != 0)) {
                // Override previous operator
                sql.pop();
            }
            // Apply operator
            sql.push(") UNION SELECT id FROM (");
            inWords = false;
        }
        else if (item == "-") {
            if (index == 0) {
                // First -
                sql.push("( SELECT id FROM items");
            }
            else if (!inWords) {
                // Override previous operator
                sql.pop();
            }
            // Apply operator
            sql.push(") EXCEPT SELECT id FROM (");
            inWords = false;
        }
        else {
            if (inWords) {
                sql.push("INTERSECT");
            }
            else if (index == 0 ){
                sql.push("(");
            }
            sql.push("SELECT id FROM items WHERE (artist || ' ' || title || ' ' || album) like '%' || ? || '%'");
            param.push(item);
            inWords = true;
        }
    });
    // Close
    sql.push(")");

    var query = {
        "sql": sql.join(" "),
        "param": param
    };

    sqlfilter(query, "search", player, callback);
};

var similar = function(player, args, callback) {
    // Sort sequence in order of similarity with current song
    saveFreemodeIndex(player);

    var distances = [];
    var anchorPoint = player.coords[player.sequence[player.currentIndex]],
        currentPoint;

    for (var i = 0; i < player.sequence.length; i++) {
        currentPoint = player.coords[player.sequence[i]];
        console.log(i);
        distances.push(Math.abs(anchorPoint.x - currentPoint.x) +
                       Math.abs(anchorPoint.y - currentPoint.y));
    }

    var similarIndices = utils.argsort(distances);
    seq = [];

    for (i = 0; i < similarIndices.length; i++) {
        seq.push(player.sequence[similarIndices[i]]);
    }
    player.sequence = seq;
    player.currentIndex = 0;

    callback(["m", "similar"]);
};

var artistCap = function(player, args, callback) {
    // Put an upper limit on song count by artist
    saveFreemodeIndex(player);

    if (isNaN(args[0])) {
        callback("nf");
    }
    else {
        var cap = parseInt(args[0]);
        if (cap <= 0) {
            callback("nf");
        }
        else {
            var query = {
                "sql": "SELECT id FROM items WHERE artist IN (SELECT artist FROM items GROUP BY artist HAVING count(*) <= ?)",
                "param": cap
            };
            sqlfilter(query, "cap", player, callback);
        }
    }
};

// Common functions

var sqlfilter = function(query, name, player, callback) {
    // General sql filter on beetsDb
    var seq = [];
    player.beetsDb.all(query.sql, query.param, function(err, rows) {
        if (err) {
            console.log(err);
            callback("nf");
        }
        else {
            rows.forEach(function(row) {
                seq.push(row.id);
            });
            if (seq.length == 0) {
                callback("nf");
            }
            else {
                player.seqCount = seq.length;
                seq = utils.shuffle(seq);
                player.sequence = seq;
                player.currentIndex = 0;

                callback(["m", name]);
            }
        }
    });
};

var saveFreemodeIndex = function(player) {
    // Save state for free mode if not already saved
    if (!player.freemodeSave) {
        player.freemodeSave = player.currentIndex;
    }
};

exports.artist = artist;
exports.album = album;
exports.search = search;
exports.similar = similar;
exports.artistCap = artistCap;
<commit_msg>Remove log<commit_after>// Filter functions
// ----------------

const utils = require("./utils");

var artist = function(player, args, callback) {
    // Songs from current artist
    saveFreemodeIndex(player);

    var query = {
        "sql": "SELECT id FROM items WHERE artist = ?",
        "param": player.currentData.artist
    };
    sqlfilter(query, "artist", player, callback);
};

var album = function(player, args, callback) {
    // Songs from current album
    saveFreemodeIndex(player);

    var query = {
        "sql": "SELECT id FROM items WHERE album = ?",
        "param": player.currentData.album
    };
    sqlfilter(query, "album", player, callback);
};

var search = function(player, args, callback) {
    // Search results
    saveFreemodeIndex(player);

    // Generate query
    // + is union
    // - is exception
    // others are intersected

    // Clean up continuous repetitions
    var cleaned = args.filter(function(item, index, arr) {
        return (index == 0) || (item != arr[index-1]);
    });

    // Remove first +
    if (cleaned[0] == "+") {
        cleaned.shift();
    }
    // Remove ending operators +/-
    while (["+", "-"].indexOf(cleaned[cleaned.length-1]) > -1) {
        cleaned.pop();
    }

    var sql = [],
        param = [],
        inWords = false;
    // Wrap around
    sql.push("SELECT id FROM");
    cleaned.forEach(function(item, index) {
        if (item == "+") {
            if ((!inWords) && (index != 0)) {
                // Override previous operator
                sql.pop();
            }
            // Apply operator
            sql.push(") UNION SELECT id FROM (");
            inWords = false;
        }
        else if (item == "-") {
            if (index == 0) {
                // First -
                sql.push("( SELECT id FROM items");
            }
            else if (!inWords) {
                // Override previous operator
                sql.pop();
            }
            // Apply operator
            sql.push(") EXCEPT SELECT id FROM (");
            inWords = false;
        }
        else {
            if (inWords) {
                sql.push("INTERSECT");
            }
            else if (index == 0 ){
                sql.push("(");
            }
            sql.push("SELECT id FROM items WHERE (artist || ' ' || title || ' ' || album) like '%' || ? || '%'");
            param.push(item);
            inWords = true;
        }
    });
    // Close
    sql.push(")");

    var query = {
        "sql": sql.join(" "),
        "param": param
    };

    sqlfilter(query, "search", player, callback);
};

var similar = function(player, args, callback) {
    // Sort sequence in order of similarity with current song
    saveFreemodeIndex(player);

    var distances = [];
    var anchorPoint = player.coords[player.sequence[player.currentIndex]],
        currentPoint;

    for (var i = 0; i < player.sequence.length; i++) {
        currentPoint = player.coords[player.sequence[i]];
        distances.push(Math.abs(anchorPoint.x - currentPoint.x) +
                       Math.abs(anchorPoint.y - currentPoint.y));
    }

    var similarIndices = utils.argsort(distances);
    seq = [];

    for (i = 0; i < similarIndices.length; i++) {
        seq.push(player.sequence[similarIndices[i]]);
    }
    player.sequence = seq;
    player.currentIndex = 0;

    callback(["m", "similar"]);
};

var artistCap = function(player, args, callback) {
    // Put an upper limit on song count by artist
    saveFreemodeIndex(player);

    if (isNaN(args[0])) {
        callback("nf");
    }
    else {
        var cap = parseInt(args[0]);
        if (cap <= 0) {
            callback("nf");
        }
        else {
            var query = {
                "sql": "SELECT id FROM items WHERE artist IN (SELECT artist FROM items GROUP BY artist HAVING count(*) <= ?)",
                "param": cap
            };
            sqlfilter(query, "cap", player, callback);
        }
    }
};

// Common functions

var sqlfilter = function(query, name, player, callback) {
    // General sql filter on beetsDb
    var seq = [];
    player.beetsDb.all(query.sql, query.param, function(err, rows) {
        if (err) {
            console.log(err);
            callback("nf");
        }
        else {
            rows.forEach(function(row) {
                seq.push(row.id);
            });
            if (seq.length == 0) {
                callback("nf");
            }
            else {
                player.seqCount = seq.length;
                seq = utils.shuffle(seq);
                player.sequence = seq;
                player.currentIndex = 0;

                callback(["m", name]);
            }
        }
    });
};

var saveFreemodeIndex = function(player) {
    // Save state for free mode if not already saved
    if (!player.freemodeSave) {
        player.freemodeSave = player.currentIndex;
    }
};

exports.artist = artist;
exports.album = album;
exports.search = search;
exports.similar = similar;
exports.artistCap = artistCap;
<|endoftext|>


<commit_before>$(function() {

    var monitors = new Monitors();

    var cleanData = function(data) {
        return _.map(data, function(data) {
            return {
                _id: ragiosHelper.linkTo(data._id, "/admin/monitors/" + data._id),
                created_at_: ragiosHelper.formatDateSmall(data.created_at_),
                monitor: data.monitor,
                every: data.every,
                via: data.via,
                plugin: data.plugin,
                status_: ragiosHelper.formatStatus(data.status_)
            };
        });
    };

    var createTable = function( _, data ) {
        var monitorsList =
        $('#monitors-list-datatable').dataTable({
            "order": [ 0, 'desc' ],
            "data": cleanData(data),
            "columns": [
                { "data": "created_at_", "width": "16%" },
                { "data": "_id", "width": "31%" },
                { "data": "monitor", "width": "31%" },
                { "data": "every", "width": "1%" },
                { "data": "via", "width": "12%" },
                { "data": "plugin", "width": "8%" },
                { "data": "status_", "width": "1%" }
            ]
        });
    };

    monitors.fetch({
        success: createTable,
        error: function ( _, xhr ) { alert(xhr.responseText); }
    });
});
<commit_msg>save state for monitors table<commit_after>$(function() {

    var monitors = new Monitors();

    var cleanData = function(data) {
        return _.map(data, function(data) {
            return {
                _id: ragiosHelper.linkTo(data._id, "/admin/monitors/" + data._id),
                created_at_: ragiosHelper.formatDateSmall(data.created_at_),
                monitor: data.monitor,
                every: data.every,
                via: data.via,
                plugin: data.plugin,
                status_: ragiosHelper.formatStatus(data.status_)
            };
        });
    };

    var createTable = function( _, data ) {
        var monitorsList =
        $('#monitors-list-datatable').dataTable({
            "bStateSave": true,
            "order": [ 0, 'desc' ],
            "data": cleanData(data),
            "columns": [
                { "data": "created_at_", "width": "16%" },
                { "data": "_id", "width": "31%" },
                { "data": "monitor", "width": "31%" },
                { "data": "every", "width": "1%" },
                { "data": "via", "width": "12%" },
                { "data": "plugin", "width": "8%" },
                { "data": "status_", "width": "1%" }
            ]
        });
    };

    monitors.fetch({
        success: createTable,
        error: function ( _, xhr ) { alert(xhr.responseText); }
    });
});
<|endoftext|>