<commit_before>const dbCmds = require('../db/commands.js')
const moment = require('moment-timezone')
const defaultConfigs = require('../../util/checkConfig.js').defaultConfigs
const log = require('../../util/logger.js')
const storage = require('../../util/storage.js')
const ArticleIDResolver = require('../../structs/ArticleIDResolver.js')

module.exports = (data, callback) => {
  const { rssList, articleList, debugFeeds, link, shardId, config, feedData, scheduleName, runNum, useIdType } = data // feedData is only defined when config.database.uri is set to a databaseless folder path
  if (!scheduleName) throw new Error('Missing schedule name for shared logic')
  const RSSLIST_LENGTH = Object.keys(rssList).length
  let sourcesCompleted = 0
  const totalArticles = articleList.length
  const dbIds = new Set()
  const dbTitles = new Set()
  const dbCustomComparisons = {} // Object with comparison names as key, and array as values whose function is similar to how dbIds and dbTitles work
  const dbCustomComparisonsValid = {} // Object with comparison names as key, and only boolean true values as values
  const dbCustomComparisonsToDelete = []
  const customComparisonsToUpdate = []
  const toInsert = []
  const toUpdate = {} // Article's resolved IDs as key and the article as value
  const collectionId = storage.collectionId(link, shardId, scheduleName)
  const Feed = storage.models.FeedByCollectionId(collectionId)
  const feedCollectionId = feedData ? collectionId : undefined
  const feedCollection = feedData ? (feedData[feedCollectionId] || []) : undefined

  dbCmds.findAll(feedCollection || Feed)
    .then(docs => {
      for (var d = 0; d < docs.length; ++d) {
        const doc = docs[d]
        // Push the main data for built in comparisons
        dbIds.add(doc.id)
        dbTitles.add(doc.title)

        // Now deal with custom comparisons
        const docCustomComparisons = doc.customComparisons
        if (docCustomComparisons !== undefined && Object.keys(docCustomComparisons).length > 0) {
          for (var n in docCustomComparisons) { // n = customComparison's name (such as description, author, etc.)
            if (!dbCustomComparisons[n]) dbCustomComparisons[n] = []
            dbCustomComparisons[n].push(docCustomComparisons[n])
          }
        }
      }

      const checkCustomComparisons = Object.keys(dbCustomComparisons).length > 0
      for (var a = 0; a < articleList.length; ++a) {
        const article = articleList[a]
        article._id = ArticleIDResolver.getIdTypeValue(article, useIdType)
        if (checkCustomComparisons) {
        // Iterate over the values stored in the db, and see if the custom comparison names in the db exist in any of the articles. If they do, then it is marked valid
          for (var compName in dbCustomComparisons) {
            if (article[compName] !== undefined && (typeof article[compName] !== 'object' || article[compName] === null)) dbCustomComparisonsValid[compName] = true
          }
        }
        if (!dbIds.has(article._id)) toInsert.push(article)
      }

      // If any invalid custom comparisons are found, delete them
      if (checkCustomComparisons) {
        for (var q in dbCustomComparisons) {
          if (!dbCustomComparisonsValid[q]) {
            dbCustomComparisonsToDelete.push(q)
            delete dbCustomComparisons[q]
          }
        }
      }
      dbCmds.bulkInsert(feedCollection || Feed, toInsert).then(() => {
        if (dbIds.size > 0) for (var rssName in rssList) processSource(rssName)
        else callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
      })
        .catch(err => {
          if (err) return callback(new Error(`Database Error: Unable to bulk insert articles for link ${link}`, err.message || err), { status: 'failed', link: link, rssList: rssList })
        })
    })
    .catch(err => {
      return callback(err, { status: 'failed', link: link, rssList: rssList })
    })

  function processSource (rssName) {
    const source = rssList[rssName]
    const channelId = source.channel
    const customComparisons = rssList[rssName].customComparisons // Array of names
    const sentTitles = new Set()

    if (Array.isArray(customComparisons)) {
      for (var n = customComparisons.length - 1; n >= 0; --n) {
        const name = customComparisons[n]
        if (name === 'title' || name === 'guid' || name === 'pubdate') { // Forbidden custom comparisons since these are already used by the bot
          customComparisons.splice(n, 1)
          continue
        }
        if (!dbCustomComparisons[name] && !customComparisonsToUpdate.includes(name)) customComparisonsToUpdate.push(name) // Since this custom comparison wasn't found in the db, it might be uninitialized or not found in any articles (as checked previously)
      }
    }

    let processedArticles = 0
    if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Processing collection. Total article list length: ${articleList.length}`)

    const maxAge = config.feeds.cycleMaxAge
    const cutoffDay = moment().subtract(maxAge, 'days')

    const globalDateCheck = config.feeds.checkDates != null ? config.feeds.checkDates : defaultConfigs.feeds.checkDates.default
    const localDateCheck = source.checkDates
    const checkDate = typeof localDateCheck !== 'boolean' ? globalDateCheck : localDateCheck

    const globalTitleCheck = config.feeds.checkTitles != null ? config.feeds.checkTitles : defaultConfigs.feeds.checkTitles.default
    const localTitleCheck = source.checkTitles
    const checkTitle = typeof globalTitleCheck !== 'boolean' ? globalTitleCheck : localTitleCheck

    for (var a = articleList.length - 1; a >= 0; --a) { // Loop from oldest to newest so the queue that sends articleMessages work properly, sending the older ones first
      const article = articleList[a]
      if (dbIds.size === 0 && articleList.length !== 1) { // Only skip if the articleList length is !== 1, otherwise a feed with only 1 article to send since it may have been the first item added
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) due to empty collection.`)
        seenArticle(true, article)
      } else if (dbIds.has(article._id)) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), ID was matched.`)
        seenArticle(true, article)
      } else if (checkTitle && (dbTitles.has(article.title) || sentTitles.has(article.title))) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), Title was matched but not ID.`)
        seenArticle(true, article)
      } else if (checkDate && ((!article.pubdate || article.pubdate.toString() === 'Invalid Date') || (article.pubdate && article.pubdate.toString() !== 'Invalid Date' && article.pubdate < cutoffDay))) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), due to date check.`)
        seenArticle(true, article)
      } else {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Sending article (ID: ${article._id}, TITLE: ${article.title}) to queue for send`)
        if (checkTitle && article.title) sentTitles.add(article.title)
        seenArticle(false, article)
      }
    }

    function seenArticle (seen, article) {
      if (runNum === 0 && config.feeds.sendOldOnFirstCycle === false) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), config.feeds.sendOldOnFirstCycle is false`)
        return ++processedArticles === totalArticles ? finishSource() : null // Stops here if it already exists in table, AKA "seen"
      }

      // Check for extra user-specified comparisons
      if (seen) {
        if (!Array.isArray(customComparisons)) return ++processedArticles === totalArticles ? finishSource() : null // Stops here if it already exists in table, AKA "seen"
        for (var z = 0; z < customComparisons.length; ++z) {
          const comparisonName = customComparisons[z]
          const dbCustomComparisonValues = dbCustomComparisons[comparisonName]
          const articleCustomComparisonValue = article[comparisonName]
          if (!dbCustomComparisonValues || dbCustomComparisonValues.includes(articleCustomComparisonValue)) {
            if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}`)
            if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: (ID: ${article._id}, TITLE: ${article.title}) ${comparisonName} dbCustomComparisonValues: ${dbCustomComparisonValues ? JSON.stringify(dbCustomComparisonValues) : undefined} `)
            continue // The comparison must either be uninitialized or invalid (no such comparison exists in any articles from the request), handled by a previous function. OR it exists in the db
          }

          // Prepare it for update in the database
          if (!toUpdate[article._id]) {
            if (!article.customComparisons) article.customComparisons = {}
            article.customComparisons[comparisonName] = articleCustomComparisonValue
            toUpdate[article._id] = article
          }
          if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}`)

          return seenArticle(false, article)
        }
        return ++processedArticles === totalArticles ? finishSource() : null
      }

      // For ArticleMessage to access once ScheduleManager receives this article
      article._delivery = {
        guildId: source.guildId, // Originally set in source through FeedSchedule.js
        dateSettings: source.dateSettings, // Not the actual date, but settings concerning date format/language/timezone. Originally set in source through FeedSchedule.js
        rssName,
        channelId,
        source
      }

      callback(null, { status: 'article', article: article })
      return ++processedArticles === totalArticles ? finishSource() : null
    }
  }

  function finishSource () {
    if (++sourcesCompleted === RSSLIST_LENGTH) finishFeed()
  }

  function finishFeed () {
    // Add missing custom comparisons if needed
    if (customComparisonsToUpdate.length > 0) {
      const mustUpdate = customComparisonsToUpdate.length
      for (var l = 0; l < mustUpdate; ++l) {
        const customComparisonName = customComparisonsToUpdate[l]
        for (var a = 0; a < articleList.length; ++a) {
          const article = articleList[a]
          const articleCustomComparisonValue = article[customComparisonName]
          if (articleCustomComparisonValue === undefined || (typeof articleCustomComparisonValue === 'object' && articleCustomComparisonValue !== null)) continue // typeof null returns 'object' even though it's not supposed to
          if (!toUpdate[article._id]) {
            if (!article.customComparisons) article.customComparisons = {}
            article.customComparisons[customComparisonName] = articleCustomComparisonValue
            toUpdate[article._id] = article
          } else toUpdate[article._id].customComparisons[customComparisonName] = articleCustomComparisonValue
        }
      }
    }

    // Update anything if necessary
    const toUpdateLength = Object.keys(toUpdate).length
    if (toUpdateLength === 0) return callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
    let c = 0
    for (var id in toUpdate) {
      const article = toUpdate[id]
      dbCmds.update(feedCollection || Feed, article).then(() => {
        if (++c >= toUpdateLength) callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
      }).catch(err => {
        log.cycle.error(`Failed to update an article entry`, err)
        if (++c >= toUpdateLength) callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
      })
    }
  }
}
<commit_msg>Add check for property existence for custom comparisons<commit_after>const dbCmds = require('../db/commands.js')
const moment = require('moment-timezone')
const defaultConfigs = require('../../util/checkConfig.js').defaultConfigs
const log = require('../../util/logger.js')
const storage = require('../../util/storage.js')
const ArticleIDResolver = require('../../structs/ArticleIDResolver.js')

module.exports = (data, callback) => {
  const { rssList, articleList, debugFeeds, link, shardId, config, feedData, scheduleName, runNum, useIdType } = data // feedData is only defined when config.database.uri is set to a databaseless folder path
  if (!scheduleName) throw new Error('Missing schedule name for shared logic')
  const RSSLIST_LENGTH = Object.keys(rssList).length
  let sourcesCompleted = 0
  const totalArticles = articleList.length
  const dbIds = new Set()
  const dbTitles = new Set()
  const dbCustomComparisons = {} // Object with comparison names as key, and array as values whose function is similar to how dbIds and dbTitles work
  const dbCustomComparisonsValid = {} // Object with comparison names as key, and only boolean true values as values
  const dbCustomComparisonsToDelete = []
  const customComparisonsToUpdate = []
  const toInsert = []
  const toUpdate = {} // Article's resolved IDs as key and the article as value
  const collectionId = storage.collectionId(link, shardId, scheduleName)
  const Feed = storage.models.FeedByCollectionId(collectionId)
  const feedCollectionId = feedData ? collectionId : undefined
  const feedCollection = feedData ? (feedData[feedCollectionId] || []) : undefined

  dbCmds.findAll(feedCollection || Feed)
    .then(docs => {
      for (var d = 0; d < docs.length; ++d) {
        const doc = docs[d]
        // Push the main data for built in comparisons
        dbIds.add(doc.id)
        dbTitles.add(doc.title)

        // Now deal with custom comparisons
        const docCustomComparisons = doc.customComparisons
        if (docCustomComparisons !== undefined && Object.keys(docCustomComparisons).length > 0) {
          for (var n in docCustomComparisons) { // n = customComparison's name (such as description, author, etc.)
            if (!dbCustomComparisons[n]) dbCustomComparisons[n] = []
            dbCustomComparisons[n].push(docCustomComparisons[n])
          }
        }
      }

      const checkCustomComparisons = Object.keys(dbCustomComparisons).length > 0
      for (var a = 0; a < articleList.length; ++a) {
        const article = articleList[a]
        article._id = ArticleIDResolver.getIdTypeValue(article, useIdType)
        if (checkCustomComparisons) {
        // Iterate over the values stored in the db, and see if the custom comparison names in the db exist in any of the articles. If they do, then it is marked valid
          for (var compName in dbCustomComparisons) {
            if (article[compName] !== undefined && (typeof article[compName] !== 'object' || article[compName] === null)) dbCustomComparisonsValid[compName] = true
          }
        }
        if (!dbIds.has(article._id)) toInsert.push(article)
      }

      // If any invalid custom comparisons are found, delete them
      if (checkCustomComparisons) {
        for (var q in dbCustomComparisons) {
          if (!dbCustomComparisonsValid[q]) {
            dbCustomComparisonsToDelete.push(q)
            delete dbCustomComparisons[q]
          }
        }
      }
      dbCmds.bulkInsert(feedCollection || Feed, toInsert).then(() => {
        if (dbIds.size > 0) for (var rssName in rssList) processSource(rssName)
        else callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
      })
        .catch(err => {
          if (err) return callback(new Error(`Database Error: Unable to bulk insert articles for link ${link}`, err.message || err), { status: 'failed', link: link, rssList: rssList })
        })
    })
    .catch(err => {
      return callback(err, { status: 'failed', link: link, rssList: rssList })
    })

  function processSource (rssName) {
    const source = rssList[rssName]
    const channelId = source.channel
    const customComparisons = rssList[rssName].customComparisons // Array of names
    const sentTitles = new Set()

    if (Array.isArray(customComparisons)) {
      for (var n = customComparisons.length - 1; n >= 0; --n) {
        const name = customComparisons[n]
        if (name === 'title' || name === 'guid' || name === 'pubdate') { // Forbidden custom comparisons since these are already used by the bot
          customComparisons.splice(n, 1)
          continue
        }
        if (!dbCustomComparisons[name] && !customComparisonsToUpdate.includes(name)) customComparisonsToUpdate.push(name) // Since this custom comparison wasn't found in the db, it might be uninitialized or not found in any articles (as checked previously)
      }
    }

    let processedArticles = 0
    if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Processing collection. Total article list length: ${articleList.length}`)

    const maxAge = config.feeds.cycleMaxAge
    const cutoffDay = moment().subtract(maxAge, 'days')

    const globalDateCheck = config.feeds.checkDates != null ? config.feeds.checkDates : defaultConfigs.feeds.checkDates.default
    const localDateCheck = source.checkDates
    const checkDate = typeof localDateCheck !== 'boolean' ? globalDateCheck : localDateCheck

    const globalTitleCheck = config.feeds.checkTitles != null ? config.feeds.checkTitles : defaultConfigs.feeds.checkTitles.default
    const localTitleCheck = source.checkTitles
    const checkTitle = typeof globalTitleCheck !== 'boolean' ? globalTitleCheck : localTitleCheck

    for (var a = articleList.length - 1; a >= 0; --a) { // Loop from oldest to newest so the queue that sends articleMessages work properly, sending the older ones first
      const article = articleList[a]
      if (dbIds.size === 0 && articleList.length !== 1) { // Only skip if the articleList length is !== 1, otherwise a feed with only 1 article to send since it may have been the first item added
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) due to empty collection.`)
        seenArticle(true, article)
      } else if (dbIds.has(article._id)) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), ID was matched.`)
        seenArticle(true, article)
      } else if (checkTitle && (dbTitles.has(article.title) || sentTitles.has(article.title))) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), Title was matched but not ID.`)
        seenArticle(true, article)
      } else if (checkDate && ((!article.pubdate || article.pubdate.toString() === 'Invalid Date') || (article.pubdate && article.pubdate.toString() !== 'Invalid Date' && article.pubdate < cutoffDay))) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), due to date check.`)
        seenArticle(true, article)
      } else {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Sending article (ID: ${article._id}, TITLE: ${article.title}) to queue for send`)
        if (checkTitle && article.title) sentTitles.add(article.title)
        seenArticle(false, article)
      }
    }

    function seenArticle (seen, article) {
      if (runNum === 0 && config.feeds.sendOldOnFirstCycle === false) {
        if (debugFeeds && debugFeeds.includes(rssName)) log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), config.feeds.sendOldOnFirstCycle is false`)
        return ++processedArticles === totalArticles ? finishSource() : null // Stops here if it already exists in table, AKA "seen"
      }

      // Check for extra user-specified comparisons
      if (seen) {
        if (!Array.isArray(customComparisons)) return ++processedArticles === totalArticles ? finishSource() : null // Stops here if it already exists in table, AKA "seen"
        for (var z = 0; z < customComparisons.length; ++z) {
          const comparisonName = customComparisons[z]
          const dbCustomComparisonValues = dbCustomComparisons[comparisonName] // Might be an array of descriptions, authors, etc.
          const articleCustomComparisonValue = article[comparisonName]
          if (!dbCustomComparisonValues || dbCustomComparisonValues.includes(articleCustomComparisonValue) || !articleCustomComparisonValue) {
            if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}${!articleCustomComparisonValue ? ' (No article value for custom comparison field)' : ''}`)
            if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: (ID: ${article._id}, TITLE: ${article.title}) ${comparisonName} dbCustomComparisonValues: ${dbCustomComparisonValues ? JSON.stringify(dbCustomComparisonValues) : undefined} `)
            continue // The comparison must either be uninitialized or invalid (no such comparison exists in any articles from the request), handled by a previous function. OR it exists in the db
          }

          // Prepare it for update in the database
          if (!toUpdate[article._id]) {
            if (!article.customComparisons) article.customComparisons = {}
            article.customComparisons[comparisonName] = articleCustomComparisonValue
            toUpdate[article._id] = article
          }
          if (debugFeeds && debugFeeds.includes(rssName)) log.debug.info(`${rssName}: Sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}`)

          return seenArticle(false, article)
        }
        return ++processedArticles === totalArticles ? finishSource() : null
      }

      // For ArticleMessage to access once ScheduleManager receives this article
      article._delivery = {
        guildId: source.guildId, // Originally set in source through FeedSchedule.js
        dateSettings: source.dateSettings, // Not the actual date, but settings concerning date format/language/timezone. Originally set in source through FeedSchedule.js
        rssName,
        channelId,
        source
      }

      callback(null, { status: 'article', article: article })
      return ++processedArticles === totalArticles ? finishSource() : null
    }
  }

  function finishSource () {
    if (++sourcesCompleted === RSSLIST_LENGTH) finishFeed()
  }

  function finishFeed () {
    // Add missing custom comparisons if needed
    if (customComparisonsToUpdate.length > 0) {
      const mustUpdate = customComparisonsToUpdate.length
      for (var l = 0; l < mustUpdate; ++l) {
        const customComparisonName = customComparisonsToUpdate[l]
        for (var a = 0; a < articleList.length; ++a) {
          const article = articleList[a]
          const articleCustomComparisonValue = article[customComparisonName]
          if (articleCustomComparisonValue === undefined || (typeof articleCustomComparisonValue === 'object' && articleCustomComparisonValue !== null)) continue // typeof null returns 'object' even though it's not supposed to
          if (!toUpdate[article._id]) {
            if (!article.customComparisons) article.customComparisons = {}
            article.customComparisons[customComparisonName] = articleCustomComparisonValue
            toUpdate[article._id] = article
          } else toUpdate[article._id].customComparisons[customComparisonName] = articleCustomComparisonValue
        }
      }
    }

    // Update anything if necessary
    const toUpdateLength = Object.keys(toUpdate).length
    if (toUpdateLength === 0) return callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
    let c = 0
    for (var id in toUpdate) {
      const article = toUpdate[id]
      dbCmds.update(feedCollection || Feed, article).then(() => {
        if (++c >= toUpdateLength) callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
      }).catch(err => {
        log.cycle.error(`Failed to update an article entry`, err)
        if (++c >= toUpdateLength) callback(null, { status: 'success', link: link, feedCollection: feedCollection, feedCollectionId: feedCollectionId })
      })
    }
  }
}
<|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/. */

define(
  ["designer/utils", "ceci/ceci-designer", "l10n", "analytics"],
  function(Util, CeciDesigner, L10n, analytics) {
    "use strict";

    var resolvePath = function(tag, url) {
      try {
        return document.createElement(tag).resolvePath(url);
      }
      catch (e) {
        console.error('resolvePath failed for component ' + tag + '.');
        return url;
      }
    };

    var knownComponents = [];
    var tags = [];

    var categories = {
      "basic": {
        "description": L10n.get("Start with simple bricks useful for almost every app."),
        "bricks": [
          "ceci-button",
          "ceci-counter",
          "ceci-image",
          "ceci-header",
          "ceci-textbox",
          "ceci-metronome"
        ]
      },
      "utility": {
        "description": L10n.get("Add more advanced utilities for your app."),
        "bricks": [
          "ceci-button",
          "ceci-button-with-confirmation",
          "ceci-double-button",
          "ceci-header",
          "ceci-image",
          "ceci-metronome",
          "ceci-spacer",
          "ceci-textbox",
          "ceci-text-input",
        ]
      },
      "connection": {
        "description": L10n.get("Create complex data-driven connections between bricks."),
        "bricks": [
          "ceci-transformer",
          "ceci-channel-gate",
          "ceci-alternating-gate",
          "ceci-splitter",
          "ceci-combiner",
          "ceci-bool"
        ]
      },
      "audio": {
        "description": L10n.get("Record and play sounds, listen to music, and more."),
        "bricks": [
          "ceci-audio",
          "ceci-cowbell",
          "ceci-snaredrum",
          "ceci-kickdrum",
          "ceci-metronome",
          "ceci-microphone-button",
          "ceci-sequencer"
        ]
      },
      "number": {
        "description": L10n.get("Count objects and manipulate numbers."),
        "bricks": [
          "ceci-counter",
          "ceci-daily-counter",
          "ceci-random"
        ]
      },
      "media": {
        "description": L10n.get("Draw, take pictures, and chart progress with these more interactive bricks."),
        "bricks": [
          "ceci-chart",
          "ceci-component-canvas",
          "ceci-camera-button",
          "ceci-notebook",
          "ceci-pad-grid",
          "ceci-todo-list"
        ]
      },
      "fun": {
        "description": L10n.get("Your app needs more cowbell."),
        "bricks": [
          "ceci-fireworks",
          "ceci-hot-potato",
          "ceci-jazzhands",
          "ceci-metronome",
          "ceci-cowbell",
          "ceci-snaredrum",
          "ceci-kickdrum",
        ]
      },
      "chat": {
        "description": L10n.get("Chat with friends using text and images."),
        "bricks": [
          "ceci-chat-window",
          "ceci-meatspaces-messages",
          "ceci-meatspaces-input"
        ]
      }
    }

    var categoryNames = [];

    for (var key in categories) {
      if (categories.hasOwnProperty(key)) {
        categoryNames.push(key);
      }
    }

    var DesignerTray = {
      buildItem : function(name){
        var item = document.createElement('designer-component-tray-item');
        var ceciDefinition = CeciDesigner.getCeciDefinitionScript(name);

        item.setAttribute('name', name);
        item.setAttribute('thumbnail', resolvePath(name, ceciDefinition.thumbnail));
        item.setAttribute('label', ceciDefinition.name || Util.prettyName(name));
        item.setAttribute('description', L10n.get(name + "/description") || ceciDefinition.description);
        item.setAttribute('author', ceciDefinition.author);
        item.setAttribute('updatedat', ceciDefinition.updatedAt);

        var bricktags = ceciDefinition.tags;

        for(var k = 0; k < bricktags.length; k++){
          var tag = bricktags[k];
          if(tags.indexOf(tag) === -1) {
            tags.push(tag);
          }
        }

        item.addEventListener('click', function (e) {
          var card = document.querySelector('ceci-card[visible]');
          if (card) {
            var newElement = document.createElement(name);
            // wait until Polymer has prepared the element completely
            newElement.async(function() {
              card.appendChild(newElement);

              // Apply defaults here explicitly so that element doesn't
              // have to figure out whether or not it's new when it's
              // attached to the DOM.
              newElement.applyDefaults();
              analytics.event("Added Component", {label: name});
            });
          }
        }, false);
        item.label = L10n.get(name) || item.label;
        return item;
      },
      addComponentWithName: function(name) {
        if(!DesignerTray.isKnownComponent(name)) {
          var added = false;
          // See if it goes in any categories
          for(var i = 0; i < categoryNames.length; i++){
            var category = categoryNames[i].toLowerCase();

            var components = categories[category].bricks || [];
            for(var j = 0; j < components.length; j++){
              if(name === components[j]){
                var item = DesignerTray.buildItem(name);
                DesignerTray.showCategory(category);
                if (category === "basic") {
                  DesignerTray.filterCategory("basic");
                }
                document.querySelector(".category-container." + category).appendChild(item);
                added = true;
              }
            }
          }
          //Means it wasn't in any categories
          if(!added){
            var item = DesignerTray.buildItem(name);
            DesignerTray.showCategory("other");
            document.querySelector(".category-container.other").appendChild(item);
          }
          knownComponents.push(name);
        }

      },
      sortComponents: function (container) {
        // Sorts components in each category container
        var containers = document.querySelectorAll('.category-container');
        for(var i = 0; i < containers.length; i++){
          var container = containers[i];
          var components = container.querySelectorAll('designer-component-tray-item').array();
          components = components.sort(function (a, b) {
            if (a.label > b.label) return 1;
            if (a.label < b.label) return -1;
            return 0;
          });
          components.forEach(function (c) {
            container.appendChild(c);
          });
        }
      },
      showCategory : function(category){
        var container = document.querySelector('.' + category.toLowerCase());
        var tag = document.querySelector('.brick-category a[data-category="'+ category.toLowerCase() + '"]');
        container.classList.remove("hidden");
        tag.classList.remove("hidden");
      },
      hideCategory : function(category){
        var container = document.querySelector('.' + category.toLowerCase());
        var tag = document.querySelector('.brick-category a[data-category="'+ category.toLowerCase() + '"]');
        container.classList.add("hidden");
        tag.classList.add("hidden");
        if (tag.classList.contains("selected-category")) {
          this.filterCategory("all");
        }
      },
      buildCategory : function(category){
        var categoryContainer = document.querySelector("[data-category="+category.toLowerCase()+"]");

        //Check if this category container exists already
        if(!categoryContainer) {
          var that = this;

          if(category !== "all") {
            //Build Category Container
            var container = document.createElement("div");
            container.classList.add("hidden");
            var title = document.createElement("h2");

            title.textContent = L10n.get(category.charAt(0).toUpperCase() + category.slice(1) + " Bricks");

            container.appendChild(title);

            if (categories[category]) {
              var description = document.createElement('div');
              description.classList.add('category-description');
              description.textContent = L10n.get(categories[category].description);
              container.appendChild(description);
            }

            container.classList.add("category-container");
            container.classList.add(category.toLowerCase());
            document.querySelector("#components").appendChild(container);
          }

          //Build tag
          var tagContainer = document.querySelector(".brick-category");
          var option = document.createElement("a");

          if(category === "all") {
            option.textContent = L10n.get("All Bricks");
          } else {
            option.classList.add("hidden");
            option.textContent = category.charAt(0).toUpperCase() + category.slice(1);
          }

          option.setAttribute("data-category",category.toLowerCase());
          tagContainer.appendChild(option);

          option.addEventListener("click",function(e){
            that.filterCategory(e.target.getAttribute("data-category"));
          });
        }
      },
      addComponentsFromRegistry: function() {
        DesignerTray.buildCategory("all");
        for (var i = 0; i < categoryNames.length; i++) {
          DesignerTray.buildCategory(categoryNames[i]);
        }
        DesignerTray.buildCategory("other");
        DesignerTray.filterCategory("all");
        CeciDesigner.forEachComponent(this.addComponentWithName);
        DesignerTray.sortComponents();
      },
      filterCategory : function(category){
        var categoryLinks = document.querySelectorAll(".brick-category a");

        Array.prototype.forEach.call(categoryLinks, function(el, i){
          el.classList.remove("selected-category");
        });

        document.querySelector(".brick-category a[data-category="+category+"]").classList.add("selected-category");
        document.querySelector("#components").setAttribute("data-category",category);

        var categoryEls = document.querySelectorAll(".category-container");
        if(category === "all"){
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "";
          });
        } else {
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "none";
          });
          document.querySelector(".category-container." + category).style.display = "";
        }
      },
      isKnownComponent: function(name) {
        return knownComponents.indexOf(name) > -1;
      },
      forgetComponent: function(name) {
        var pos = knownComponents.indexOf(name);
        if (pos > -1) {
          knownComponents.splice(pos, 1);
          var componentTrayContainer = document.getElementById('components');
          var item = componentTrayContainer.querySelector("[name='" + name + "']");
          var parentNode = item.parentNode;
          parentNode.removeChild(item);
          if (parentNode.childNodes.length === 1) {
            var category = parentNode.className.replace("category-container", "").trim();
            this.hideCategory(category);
          }
        }
      }
    };

    window.addEventListener("polymer-ready", function () {
      DesignerTray.addComponentsFromRegistry();
      var categoryContainers = document.querySelectorAll(".category-container");
      var searchBox = document.querySelector('.component-search');
      searchBox.addEventListener('keyup', function (e) {
        var searchValue = searchBox.value.trim().toLowerCase();
        var componentsContainer = document.querySelector('#components');
        if (searchValue.length > 0) {
          DesignerTray.filterCategory("all");
          CeciDesigner.forEachComponent(function (componentTag) {
            var menuElement = componentsContainer.querySelector('designer-component-tray-item[name="' + componentTag + '"]');

            if (window.CeciDefinitions[componentTag] && menuElement) {
              var tags = window.CeciDefinitions[componentTag].tags || [];
              var found = false;

              var stringsToSearch = typeof tags === 'string' ? [tags] : tags;
              stringsToSearch.push(componentTag.toLowerCase());
              window.CeciDefinitions[componentTag].name && stringsToSearch.push(window.CeciDefinitions[componentTag].name.toLowerCase());
              window.CeciDefinitions[componentTag].description && stringsToSearch.push(window.CeciDefinitions[componentTag].description.toLowerCase());

              stringsToSearch.forEach(function (string) {
                found = found || string.toLowerCase().indexOf(searchValue) > -1;
              });

              if (!found) {
                menuElement.classList.add('hide');
              }
              else {
                menuElement.classList.remove('hide');
              }
            }
          });

          Array.prototype.forEach.call(categoryContainers, function(el, i){
            if(el.querySelectorAll("designer-component-tray-item:not(.hide)").length === 0) {
              el.classList.add("no-results");
            } else {
              el.classList.remove("no-results");
            }
          });

        }
        else {
          Array.prototype.forEach.call(categoryContainers, function(el, i){
            el.classList.remove("no-results");
          });
          Array.prototype.forEach.call(componentsContainer.querySelectorAll('designer-component-tray-item'), function (e) {
            e.classList.remove('hide');
          });
        }
      });
    });

    return DesignerTray;
  }
);
<commit_msg>fixes #1700 clear search box if new category is selected<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/. */

define(
  ["designer/utils", "ceci/ceci-designer", "l10n", "analytics"],
  function(Util, CeciDesigner, L10n, analytics) {
    "use strict";

    var resolvePath = function(tag, url) {
      try {
        return document.createElement(tag).resolvePath(url);
      }
      catch (e) {
        console.error('resolvePath failed for component ' + tag + '.');
        return url;
      }
    };

    var knownComponents = [];
    var tags = [];

    var categories = {
      "basic": {
        "description": L10n.get("Start with simple bricks useful for almost every app."),
        "bricks": [
          "ceci-button",
          "ceci-counter",
          "ceci-image",
          "ceci-header",
          "ceci-textbox",
          "ceci-metronome"
        ]
      },
      "utility": {
        "description": L10n.get("Add more advanced utilities for your app."),
        "bricks": [
          "ceci-button",
          "ceci-button-with-confirmation",
          "ceci-double-button",
          "ceci-header",
          "ceci-image",
          "ceci-metronome",
          "ceci-spacer",
          "ceci-textbox",
          "ceci-text-input",
        ]
      },
      "connection": {
        "description": L10n.get("Create complex data-driven connections between bricks."),
        "bricks": [
          "ceci-transformer",
          "ceci-channel-gate",
          "ceci-alternating-gate",
          "ceci-splitter",
          "ceci-combiner",
          "ceci-bool"
        ]
      },
      "audio": {
        "description": L10n.get("Record and play sounds, listen to music, and more."),
        "bricks": [
          "ceci-audio",
          "ceci-cowbell",
          "ceci-snaredrum",
          "ceci-kickdrum",
          "ceci-metronome",
          "ceci-microphone-button",
          "ceci-sequencer"
        ]
      },
      "number": {
        "description": L10n.get("Count objects and manipulate numbers."),
        "bricks": [
          "ceci-counter",
          "ceci-daily-counter",
          "ceci-random"
        ]
      },
      "media": {
        "description": L10n.get("Draw, take pictures, and chart progress with these more interactive bricks."),
        "bricks": [
          "ceci-chart",
          "ceci-component-canvas",
          "ceci-camera-button",
          "ceci-notebook",
          "ceci-pad-grid",
          "ceci-todo-list"
        ]
      },
      "fun": {
        "description": L10n.get("Your app needs more cowbell."),
        "bricks": [
          "ceci-fireworks",
          "ceci-hot-potato",
          "ceci-jazzhands",
          "ceci-metronome",
          "ceci-cowbell",
          "ceci-snaredrum",
          "ceci-kickdrum",
        ]
      },
      "chat": {
        "description": L10n.get("Chat with friends using text and images."),
        "bricks": [
          "ceci-chat-window",
          "ceci-meatspaces-messages",
          "ceci-meatspaces-input"
        ]
      }
    }

    var categoryNames = [];

    for (var key in categories) {
      if (categories.hasOwnProperty(key)) {
        categoryNames.push(key);
      }
    }

    var DesignerTray = {
      buildItem : function(name){
        var item = document.createElement('designer-component-tray-item');
        var ceciDefinition = CeciDesigner.getCeciDefinitionScript(name);

        item.setAttribute('name', name);
        item.setAttribute('thumbnail', resolvePath(name, ceciDefinition.thumbnail));
        item.setAttribute('label', ceciDefinition.name || Util.prettyName(name));
        item.setAttribute('description', L10n.get(name + "/description") || ceciDefinition.description);
        item.setAttribute('author', ceciDefinition.author);
        item.setAttribute('updatedat', ceciDefinition.updatedAt);

        var bricktags = ceciDefinition.tags;

        for(var k = 0; k < bricktags.length; k++){
          var tag = bricktags[k];
          if(tags.indexOf(tag) === -1) {
            tags.push(tag);
          }
        }

        item.addEventListener('click', function (e) {
          var card = document.querySelector('ceci-card[visible]');
          if (card) {
            var newElement = document.createElement(name);
            // wait until Polymer has prepared the element completely
            newElement.async(function() {
              card.appendChild(newElement);

              // Apply defaults here explicitly so that element doesn't
              // have to figure out whether or not it's new when it's
              // attached to the DOM.
              newElement.applyDefaults();
              analytics.event("Added Component", {label: name});
            });
          }
        }, false);
        item.label = L10n.get(name) || item.label;
        return item;
      },
      addComponentWithName: function(name) {
        if(!DesignerTray.isKnownComponent(name)) {
          var added = false;
          // See if it goes in any categories
          for(var i = 0; i < categoryNames.length; i++){
            var category = categoryNames[i].toLowerCase();

            var components = categories[category].bricks || [];
            for(var j = 0; j < components.length; j++){
              if(name === components[j]){
                var item = DesignerTray.buildItem(name);
                DesignerTray.showCategory(category);
                if (category === "basic") {
                  DesignerTray.filterCategory("basic");
                }
                document.querySelector(".category-container." + category).appendChild(item);
                added = true;
              }
            }
          }
          //Means it wasn't in any categories
          if(!added){
            var item = DesignerTray.buildItem(name);
            DesignerTray.showCategory("other");
            document.querySelector(".category-container.other").appendChild(item);
          }
          knownComponents.push(name);
        }

      },
      sortComponents: function (container) {
        // Sorts components in each category container
        var containers = document.querySelectorAll('.category-container');
        for(var i = 0; i < containers.length; i++){
          var container = containers[i];
          var components = container.querySelectorAll('designer-component-tray-item').array();
          components = components.sort(function (a, b) {
            if (a.label > b.label) return 1;
            if (a.label < b.label) return -1;
            return 0;
          });
          components.forEach(function (c) {
            container.appendChild(c);
          });
        }
      },
      showCategory : function(category){
        var container = document.querySelector('.' + category.toLowerCase());
        var tag = document.querySelector('.brick-category a[data-category="'+ category.toLowerCase() + '"]');
        container.classList.remove("hidden");
        tag.classList.remove("hidden");
      },
      hideCategory : function(category){
        var container = document.querySelector('.' + category.toLowerCase());
        var tag = document.querySelector('.brick-category a[data-category="'+ category.toLowerCase() + '"]');
        container.classList.add("hidden");
        tag.classList.add("hidden");
        if (tag.classList.contains("selected-category")) {
          this.filterCategory("all");
        }
      },
      buildCategory : function(category){
        var categoryContainer = document.querySelector("[data-category="+category.toLowerCase()+"]");

        //Check if this category container exists already
        if(!categoryContainer) {
          var that = this;

          if(category !== "all") {
            //Build Category Container
            var container = document.createElement("div");
            container.classList.add("hidden");
            var title = document.createElement("h2");

            title.textContent = L10n.get(category.charAt(0).toUpperCase() + category.slice(1) + " Bricks");

            container.appendChild(title);

            if (categories[category]) {
              var description = document.createElement('div');
              description.classList.add('category-description');
              description.textContent = L10n.get(categories[category].description);
              container.appendChild(description);
            }

            container.classList.add("category-container");
            container.classList.add(category.toLowerCase());
            document.querySelector("#components").appendChild(container);
          }

          //Build tag
          var tagContainer = document.querySelector(".brick-category");
          var option = document.createElement("a");

          if(category === "all") {
            option.textContent = L10n.get("All Bricks");
          } else {
            option.classList.add("hidden");
            option.textContent = category.charAt(0).toUpperCase() + category.slice(1);
          }

          option.setAttribute("data-category",category.toLowerCase());
          tagContainer.appendChild(option);

          option.addEventListener("click",function(e){
            document.querySelector('.component-search').value = "";
            doSearch("");
            that.filterCategory(e.target.getAttribute("data-category"));
          });
        }
      },
      addComponentsFromRegistry: function() {
        DesignerTray.buildCategory("all");
        for (var i = 0; i < categoryNames.length; i++) {
          DesignerTray.buildCategory(categoryNames[i]);
        }
        DesignerTray.buildCategory("other");
        DesignerTray.filterCategory("all");
        CeciDesigner.forEachComponent(this.addComponentWithName);
        DesignerTray.sortComponents();
      },
      filterCategory : function(category){
        var categoryLinks = document.querySelectorAll(".brick-category a");

        Array.prototype.forEach.call(categoryLinks, function(el, i){
          el.classList.remove("selected-category");
        });

        document.querySelector(".brick-category a[data-category="+category+"]").classList.add("selected-category");
        document.querySelector("#components").setAttribute("data-category",category);

        var categoryEls = document.querySelectorAll(".category-container");
        if(category === "all"){
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "";
          });
        } else {
          Array.prototype.forEach.call(categoryEls, function(el, i){
            el.style.display = "none";
          });
          document.querySelector(".category-container." + category).style.display = "";
        }
      },
      isKnownComponent: function(name) {
        return knownComponents.indexOf(name) > -1;
      },
      forgetComponent: function(name) {
        var pos = knownComponents.indexOf(name);
        if (pos > -1) {
          knownComponents.splice(pos, 1);
          var componentTrayContainer = document.getElementById('components');
          var item = componentTrayContainer.querySelector("[name='" + name + "']");
          var parentNode = item.parentNode;
          parentNode.removeChild(item);
          if (parentNode.childNodes.length === 1) {
            var category = parentNode.className.replace("category-container", "").trim();
            this.hideCategory(category);
          }
        }
      }
    };

    function doSearch(searchValue) {
      var categoryContainers = document.querySelectorAll(".category-container");
      searchValue = searchValue || "";
      var componentsContainer = document.querySelector('#components');
      if (searchValue.length > 0) {
        DesignerTray.filterCategory("all");
        CeciDesigner.forEachComponent(function (componentTag) {
          var menuElement = componentsContainer.querySelector('designer-component-tray-item[name="' + componentTag + '"]');

          if (window.CeciDefinitions[componentTag] && menuElement) {
            var tags = window.CeciDefinitions[componentTag].tags || [];
            var found = false;

            var stringsToSearch = typeof tags === 'string' ? [tags] : tags;
            stringsToSearch.push(componentTag.toLowerCase());
            window.CeciDefinitions[componentTag].name && stringsToSearch.push(window.CeciDefinitions[componentTag].name.toLowerCase());
            window.CeciDefinitions[componentTag].description && stringsToSearch.push(window.CeciDefinitions[componentTag].description.toLowerCase());

            stringsToSearch.forEach(function (string) {
              found = found || string.toLowerCase().indexOf(searchValue) > -1;
            });

            if (!found) {
              menuElement.classList.add('hide');
            }
            else {
              menuElement.classList.remove('hide');
            }
          }
        });

        Array.prototype.forEach.call(categoryContainers, function(el, i){
          if(el.querySelectorAll("designer-component-tray-item:not(.hide)").length === 0) {
            el.classList.add("no-results");
          } else {
            el.classList.remove("no-results");
          }
        });

      }
      else {
        Array.prototype.forEach.call(categoryContainers, function(el, i){
          el.classList.remove("no-results");
        });
        Array.prototype.forEach.call(componentsContainer.querySelectorAll('designer-component-tray-item'), function (e) {
          e.classList.remove('hide');
        });
      }
    }

    window.addEventListener("polymer-ready", function () {
      DesignerTray.addComponentsFromRegistry();
      var searchBox = document.querySelector('.component-search');
      searchBox.addEventListener('keyup', function() {
        doSearch(searchBox.value.trim().toLowerCase());
      });
    });

    return DesignerTray;
  }
);
<|endoftext|>


<commit_before>const React = require('react');

class Error extends React.Component {
    render() {
        const { message, type } = this.props;
        let error = format(message, type);

        return (

{error[1]}

                        {error[0]}

        )
    }
}

Error.propTypes = {
    message: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
    ]).isRequired,
    type: React.PropTypes.oneOf([
        'trending', 'trendingUsers', 'repos', 'users'
    ]).isRequired
};

function format(message, type) {
    let errorMessage = 'No results found, please try agaim!';
    let errorType;

    if (type === 'trending') {
        errorType = 'Trending Repository Error';
    } else if (type === 'trendingUsers') {
        errorType = 'Trending Developer Error';
    } else if (type === 'repos') {
        errorType = 'Repository Search Error';
    } else if (type === 'users') {
        errorType = 'User Search Error';
    } else {
        errorType = 'Unknown Error';
        errorMessage = message.toString();
    }

    return [errorMessage, errorType];
}

module.exports = Error;
<commit_msg>Updated panel color for error<commit_after>const React = require('react');

class Error extends React.Component {
    render() {
        const { message, type } = this.props;
        let error = format(message, type);

        return (

{error[1]}

                        {error[0]}

        )
    }
}

Error.propTypes = {
    message: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
    ]).isRequired,
    type: React.PropTypes.oneOf([
        'trending', 'trendingUsers', 'repos', 'users'
    ]).isRequired
};

function format(message, type) {
    let errorMessage = 'No results found, please try agaim!';
    let errorType;

    if (type === 'trending') {
        errorType = 'Trending Repository Error';
    } else if (type === 'trendingUsers') {
        errorType = 'Trending Developer Error';
    } else if (type === 'repos') {
        errorType = 'Repository Search Error';
    } else if (type === 'users') {
        errorType = 'User Search Error';
    } else {
        errorType = 'Unknown Error';
        errorMessage = message.toString();
    }

    return [errorMessage, errorType];
}

module.exports = Error;
<|endoftext|>


<commit_before><commit_msg>Add header docstring to main.js<commit_after><|endoftext|>


<commit_before>var socket = io();

var settings = {
	widthView : 600,
	heightView : 900,
	profile : null,
	url : null
};
var progressBar = {
	total : 0,
	done : 0,
	status : 0
};

var result = {};
var smartphoneSelectorHidden = 1;
var checkSelectorHidden = 1;
/*
*	functions
*/
function progress () {
	progressBar.status = (progressBar.done/progressBar.total)*100;
	//$('.progress-bar').attr("aria-valuenow", progressBar.status);
	//$('.progress-bar').attr("style", "width:" + progressBar.status + "%");
}

function stringifySourceCode () {
	result.source = result.source.replace('<', '<');
	result.source = result.source.replace('>', '>');
}

function loadHomePage () {
	$('#report').hide();
	$('#home').show();
}

function loadProgressPage () {
	$('#home').hide();
	$('#report').show();
	$('#sidebar').hide();
	$('#score').hide();
}

function loadResultPage () {
	$('#home').hide();
	$('#report').show();
	$('#sidebar').hide();
	$('#score').hide();
}

/*
*	protocol
*/

$('.device-selector').click(function (){
	var id = this.id;
	settings.profile = id;
	$('#device-selected').text(id);
});

loadHomePage();

socket.on('start', function (data){
	//progressBar.total = data;
	loadProgressPage();
});
socket.on('done', function (data){
	progressBar.done++;
	//progress();
});
socket.on('ok', function (data){
});
socket.on('warning', function (data){
	$('#issues-feed').append($('

#'+progressBar.done+'

'+data.title+'

'+data.what+'

why ?

'+data.why+'

how to fix it ?

'+data.how+'

'));
});
socket.on('err', function (data){
	$('#issues-feed').append($('

#'+progressBar.done+'

'+data.title+'

'+data.what+'

why ?

'+data.why+'

how fix it ?

'+data.how+'

'));
	$('#issues-feed').append($(data));
});
socket.on('end', function (data){
	//result.source = data.sources.html.content[0];
	$('#smartphone').append($(''));
	//$('#htmlFile').text(result.source);
	//stringifySourceCode();
	loadResultPage();
});

$('form').submit(function (){
	settings.url = $('#url').val();
	//settings.profile = $('input[name="smartphone"]:radio:checked').val();
    $('#url').val('');
	socket.emit('check', settings);
	return false;
});

<commit_msg>adapt client script to use of issue template<commit_after>var socket = io();

var settings = {
	widthView : 600,
	heightView : 900,
	profile : null,
	url : null
};
var progressBar = {
	total : 0,
	done : 0,
	status : 0
};

var result = {};
var smartphoneSelectorHidden = 1;
var checkSelectorHidden = 1;
/*
*	functions
*/
function progress () {
	progressBar.status = (progressBar.done/progressBar.total)*100;
	//$('.progress-bar').attr("aria-valuenow", progressBar.status);
	//$('.progress-bar').attr("style", "width:" + progressBar.status + "%");
}

function stringifySourceCode () {
	result.source = result.source.replace('<', '<');
	result.source = result.source.replace('>', '>');
}

function loadHomePage () {
	$('#report').hide();
	$('#home').show();
}

function loadProgressPage () {
	$('#home').hide();
	$('#report').show();
	$('#sidebar').hide();
	$('#score').hide();
}

function loadResultPage () {
	$('#home').hide();
	$('#report').show();
	$('#sidebar').hide();
	$('#score').hide();
}

/*
*	protocol
*/

$('.device-selector').click(function (){
	var id = this.id;
	settings.profile = id;
	$('#device-selected').text(id);
});

loadHomePage();

socket.on('start', function (data){
	//progressBar.total = data;
	loadProgressPage();
});
socket.on('done', function (data){
	progressBar.done++;
	//progress();
});
socket.on('ok', function (data){
});
socket.on('err', function (data){
	$('#issues-feed').append($(data));
});
socket.on('end', function (data){
	//result.source = data.sources.html.content[0];
	$('#smartphone').append($(''));
	//$('#htmlFile').text(result.source);
	//stringifySourceCode();
	loadResultPage();
});

$('form').submit(function (){
	settings.url = $('#url').val();
	//settings.profile = $('input[name="smartphone"]:radio:checked').val();
    $('#url').val('');
	socket.emit('check', settings);
	return false;
});

<|endoftext|>


<commit_before>/**
 * This file provided by Facebook is for non-commercial testing and evaluation
 * purposes only. Facebook reserves all rights not expressly granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var router = express.Router();

if(process.argv[2]!==undefined){
	app.set('port',  process.argv[2]);
}
else{
	app.set('port',  3000);
}

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.use(express.static('../Frontend'))

/*
// Additional middleware which will set headers that we need on each request.
app.use(function(req, res, next) {
    // Set permissive CORS header - this allows this server to be used only as
    // an API server in conjunction with something like webpack-dev-server.
    res.setHeader('Access-Control-Allow-Origin', '*');

    // Disable caching so we'll always get the latest comments.
    res.setHeader('Cache-Control', 'no-cache');
    next();
});
*/

var aSong={id:1232141,name:'supersong',artistName:'Joe Cool',albumName:'blablabla'};
app.get('/current', function (req, res) {
	console.log('allsongs called');
	res.send(aSong);
})

var dummySongList=[
	{id:1232141,name:'supersong',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232143,name:'supersong1',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232144,name:'supersong2',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232145,name:'supersong3',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922046/256x256'},
	{id:1232146,name:'supersong4',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232147,name:'supersong5',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232148,name:'supersong6',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232149,name:'supersong7',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232150,name:'supersong8',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922646/256x256'}
];
app.get('/playlist', function (req, res) {
	console.log('allsongs called');
	res.send(dummySongList);
})

app.listen(app.get('port'), function() {
  console.log('Server started: http://localhost:' + app.get('port') + '/');
});
<commit_msg>automatic redirect for test server, add debugging output<commit_after>/**
 * This file provided by Facebook is for non-commercial testing and evaluation
 * purposes only. Facebook reserves all rights not expressly granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var router = express.Router();

if(process.argv[2]!==undefined){
	app.set('port',  process.argv[2]);
}
else{
	app.set('port',  3000);
}

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(function(req, res, next){
	console.log(Date.now(), req.url);
	next();
});

app.use(express.static('../Frontend'))

app.get("/", function(req, res) {
	res.redirect(301, "/index.html");
});

/*
// Additional middleware which will set headers that we need on each request.
app.use(function(req, res, next) {
    // Set permissive CORS header - this allows this server to be used only as
    // an API server in conjunction with something like webpack-dev-server.
    res.setHeader('Access-Control-Allow-Origin', '*');

    // Disable caching so we'll always get the latest comments.
    res.setHeader('Cache-Control', 'no-cache');
    next();
});
*/

var aSong={id:1232141,name:'supersong',artistName:'Joe Cool',albumName:'blablabla'};
app.get('/current', function (req, res) {
	res.send(aSong);
})

var dummySongList=[
	{id:1232141,name:'supersong',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232143,name:'supersong1',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232144,name:'supersong2',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232145,name:'supersong3',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922046/256x256'},
	{id:1232146,name:'supersong4',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232147,name:'supersong5',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232148,name:'supersong6',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232149,name:'supersong7',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922746/256x256'},
	{id:1232150,name:'supersong8',artistName:'Joe Cool',albumName:'blablabla',imageUrl:'https://artwork.cdn.247e.com/covers/3922646/256x256'}
];
app.get('/playlist', function (req, res) {
	res.send(dummySongList);
})

app.listen(app.get('port'), function() {
  console.log('Server started: http://localhost:' + app.get('port') + '/');
});
<|endoftext|>


<commit_before>/*
 * angular-error
 *
 * Captures errors and reports them back to a server for inspection
 *
 * (c) 2014 Wes Cruver
 * License: MIT
 */

(function() {

'use strict';

angular.module('cfp.error', [])

  /**
   * Decorate Angular's $exceptionHandler service to easily hook into the log
   * and post the error to an external service
   */
  .config(function ($provide) {

    /**
     * Error object to standardize everything
     * @param  {[type]} exception [description]
     * @param  {[type]} cause     [description]
     * @param  {[type]} opts      [description]
     * @return {[type]}           [description]
     */
    function ErrorReport (exception, cause, opts) {
      this.exception = exception;
      this.cause = cause;
      this.opts = opts;
    }

    $provide.decorator('$exceptionHandler', ['$delegate', '$log', 'errorService', function ($delegate, $log, errorService) {

      return function (exception, cause) {
        var errorReport = new ErrorReport(exception, cause);
        errorService.post(errorReport);
        $log.error.apply($log, arguments);
      };

    }]);
  })

  /**
   * This service handles the posting of errors, and can be decorated by devs
   * to customize the post payload
   */
  .provider('errorService', function() {

    this.postUri = null;
    this.throttle = 5000;

    /**
     * Keep a cache of recent errors so we can do things like debouncing and
     * aggregating errors into a single post
     *
     * @type {Array}
     */
    var errors = [];

    this.$get = ['$injector', function ($injector) {
      var $http, $q, $timeout;
      var isThrottled = false;

      var resolveDependencies = function() {
        $http = $http || $injector.get('$http');
        $q = $q || $injector.get('$q');
        $timeout = $timeout || $injector.get('$timeout');
      };

      return {
        post: function (errorReport) {
          var uri = this.postUri;
          resolveDependencies();
          errors.push(errorReport);

          if (isThrottled) {
            return;
          }

          $timeout(function(){
            $http.post(uri, errors);
            errors = [];
            isThrottled = false;
          }, this.throttle);

          isThrottled = true;

        },
        postUri: this.postUri,
        throttle: this.throttle
      };
    }];

  });
})();
<commit_msg>did not annotate a DI req<commit_after>/*
 * angular-error
 *
 * Captures errors and reports them back to a server for inspection
 *
 * (c) 2014 Wes Cruver
 * License: MIT
 */

(function() {

'use strict';

angular.module('cfp.error', [])

  /**
   * Decorate Angular's $exceptionHandler service to easily hook into the log
   * and post the error to an external service
   */
  .config(['$provide', function ($provide) {

    /**
     * Error object to standardize everything
     * @param  {[type]} exception [description]
     * @param  {[type]} cause     [description]
     * @param  {[type]} opts      [description]
     * @return {[type]}           [description]
     */
    function ErrorReport (exception, cause, opts) {
      this.exception = exception;
      this.cause = cause;
      this.opts = opts;
    }

    $provide.decorator('$exceptionHandler', ['$delegate', '$log', 'errorService', function ($delegate, $log, errorService) {

      return function (exception, cause) {
        var errorReport = new ErrorReport(exception, cause);
        errorService.post(errorReport);
        $log.error.apply($log, arguments);
      };

    }]);
  }])

  /**
   * This service handles the posting of errors, and can be decorated by devs
   * to customize the post payload
   */
  .provider('errorService', function() {

    this.postUri = null;
    this.throttle = 5000;

    /**
     * Keep a cache of recent errors so we can do things like debouncing and
     * aggregating errors into a single post
     *
     * @type {Array}
     */
    var errors = [];

    this.$get = ['$injector', function ($injector) {
      var $http, $q, $timeout;
      var isThrottled = false;

      var resolveDependencies = function() {
        $http = $http || $injector.get('$http');
        $q = $q || $injector.get('$q');
        $timeout = $timeout || $injector.get('$timeout');
      };

      return {
        post: function (errorReport) {
          var uri = this.postUri;
          resolveDependencies();
          errors.push(errorReport);

          if (isThrottled) {
            return;
          }

          $timeout(function(){
            $http.post(uri, errors);
            errors = [];
            isThrottled = false;
          }, this.throttle);

          isThrottled = true;

        },
        postUri: this.postUri,
        throttle: this.throttle
      };
    }];

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


<commit_before>angular.module('quassel')
.filter('decoratenick', ['stripnickFilter', function(stripnick) {
    var MT = require('message').Type;

    return function(message) {
        var sender;
        switch(message.type) {
            case MT.Nick:
                sender = '<->';
                break;
            case MT.Mode:
                sender = '***';
                break;
            case MT.Join:
                sender = '-->';
                break;
            case MT.Part:
                sender = '<--';
                break;
            case MT.Quit:
                sender = '<--';
                break;
            case MT.Kick:
                sender = '<-*';
                break;
            case MT.Server:
                sender = stripnick(message.sender) || "*";
                break;
            case MT.Topic:
                sender = '*';
                break;
            case MT.NetsplitJoin:
                sender = '=>';
                break;
            case MT.NetsplitQuit:
                sender = '<=';
                break;
            case MT.DayChange:
                sender = '-';
                break;
            default:
                sender = stripnick(message.sender);
        }
        return sender;
    };
}])
.filter('decoratecontent', ['stripnickFilter', 'linkyFilter', function(stripnick, linky) {
    var MT = require('message').Type;
    var dateFormat;
    if (Intl && Intl.DateTimeFormat) {
        dateFormat = new Intl.DateTimeFormat(undefined, {weekday: "long", year: "numeric", month: "long", day: "numeric"});
    } else {
        dateFormat = {
            format: function(date) {
                return date.toDateString();
            }
        };
    }

    return function(message) {
        var content, arr, servers;
        switch(message.type) {
            case MT.Plain:
                content = linky(message.content, '_blank');
                break;
            case MT.Nick:
                content = stripnick(message.sender) + " is now known as " + message.content;
                break;
            case MT.Mode:
                content = "Mode " + message.content + " by " + stripnick(message.sender);
                break;
            case MT.Join:
                content = stripnick(message.sender) + " has joined";
                break;
            case MT.Part:
                content = stripnick(message.sender) + " has left";
                break;
            case MT.Quit:
                content = stripnick(message.sender) + " has quit";
                break;
            case MT.Kick:
                var ind = message.content.indexOf(" ");
                content = stripnick(message.sender) + " has kicked " + message.content.slice(0, ind) + " (" + message.content.slice(ind+1) + ")";
                break;
            case MT.NetsplitJoin:
                arr = message.content.split("#:#");
                servers = arr.pop().split(" ");
                content = "Netsplit between " + servers[0] + " and " + servers[1] + " ended. Users joined: " + arr.map(stripnick).join(', ');
                break;
            case MT.NetsplitQuit:
                arr = message.content.split("#:#");
                servers = arr.pop().split(" ");
                content = "Netsplit between " + servers[0] + " and " + servers[1] + ". Users quit: " + arr.map(stripnick).join(', ');
                break;
            case MT.DayChange:
                content = "{Day changed to " + dateFormat.format(message.datetime) + "}";
                break;
            default:
                content = message.content;
        }
        return content;
    };
}])
.filter('channelsFilter', function() {
    return function(input) {
        input = input || [];
        var out = input.filter(function(elt){
            return !elt._isStatusBuffer;
        });
        out.sort(function(a, b){
            return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
        });

        return out;
    };
})
.filter('stripnick', function() {
    return function(input) {
        if (typeof input === 'string') {
            var ind = input.indexOf('!');
            if (ind !== -1) {
                return input.slice(0, ind);
            }
            return input;
        }
        return '';
    };
})
.filter('duration', function() {
    return function(input) {
        var dateObject = null;
        if (input instanceof Date) {
            dateObject = input;
        } else {
            dateObject = new Date(input);
        }
        var h = dateObject.getHours(), m = dateObject.getMinutes(), s = dateObject.getSeconds();
        if (h < 10) h = '0'+h;
        if (m < 10) m = '0'+m;
        if (s < 10) s = '0'+s;
        return [h, m, s].join(':');
    };
})
.filter('color', function() {
    var COLOR = new RegExp("^" + String.fromCharCode(3) + "(([0-9]{1,2})(,([0-9]{1,2}))?)");
    return function(input) {
        var out = '',
        nbSpan = 0,
        nbSpanColor = 0,
        spanAttributes = {
            bold: false,
            italic: false,
            underline: false
        },
        i = 0,
        match;

        var openSpan = function (classes) {
            nbSpan += 1;
            return '';
        };

        var openColorSpan = function (fgclass, bgclass) {
            nbSpanColor += 1;
            var classes = fgclass;
            if (bgclass) {
                classes += ' ' + bgclass;
            }
            return openSpan(classes);
        };

        var closeSpan = function () {
            nbSpan -= 1;
            return '';
        };

        var closeColorSpan = function () {
            nbSpanColor -= 1;
            return closeSpan();
        };

        var unescapeColorTags = function(str) {
            var tagsToReplace = {
                '': '\x02',
                '': '\x1D',
                '': '\x1F',
                '': '\x03',
                '': '\x0F'
            };
            var re = /&#(2|29|3|31|15);/g;
            return str.replace(re, function (tag) {
                return tagsToReplace[tag] || tag;
            });
        };

        input = unescapeColorTags(input);

        for (i = 0; i < input.length; i++) {
            switch (input[i]) {
                case '\x02':
                    if (spanAttributes.bold) {
                        out += closeSpan();
                    } else {
                        out += openSpan('mirc-bold');
                    }
                    spanAttributes.bold = !spanAttributes.bold;
                    break;
                case '\x1D':
                    if (spanAttributes.italic) {
                        out += closeSpan();
                    } else {
                        out += openSpan('mirc-italic');
                    }
                    spanAttributes.italic = !spanAttributes.italic;
                    break;
                case '\x1F':
                    if (spanAttributes.underline) {
                        out += closeSpan();
                    } else {
                        out += openSpan('mirc-underline');
                    }
                    spanAttributes.underline = !spanAttributes.underline;
                    break;
                case '\x03':
                    match = input.substr(i, 6).match(COLOR);
                    var classfg = false,
                        classbg = false;
                    if (match) {
                        i += match[1].length;
                        // 2 & 4
                        classfg = "mirc-fg-" + parseInt(match[2], 10);
                        if (match[4]) {
                            classbg = "mirc-bg-" + parseInt(match[4], 10);
                        }
                        out += openColorSpan(classfg, classbg);
                    } else {
                        while (nbSpanColor > 0) {
                            out += closeColorSpan();
                        }
                    }
                    break;
                case '\x0F':
                    while (nbSpan > 0) {
                        out += closeSpan();
                    }
                    spanAttributes.bold = spanAttributes.italic = spanAttributes.underline = spanAttributes.colour = false;
                    break;
                default:
                    out += input[i];
                    break;
            }
        }
        while (nbSpan > 0) {
            out += closeSpan();
        }
        return out;
    };
})
.filter('escape', function() {
    var tagsToReplace = {
        '&': '&',
        '<': '<',
        '>': '>',
        '"': '"',
        '\'': ''',
        '/': '/'
    }, re = /[&<>]/g;
    return function(input) {
        if (typeof input === 'string') {
            return input.replace(re, function (tag) {
                return tagsToReplace[tag] || tag;
            });
        }
        return '';
    };
})
.filter('hash', function() {
    return function(input) {
        if (!input) return null;
        var hash = 5381, i, chr, len;
        if (input.length === 0) return hash;
        for (i = 0, len = input.length; i < len; i++) {
            chr   = input.charCodeAt(i);
            hash  = ((hash << 5) + hash) + chr;
        }
        return Math.abs(hash) % 16;
    };
})
.filter('ordernicks', function() {
    return function(users, buffer) {
        if (!users || buffer === null) return users;
        var op = [], voiced = [], other = [];

        angular.forEach(users, function(value) {
            if (buffer.isOp(value.nick)) op.push(value);
            else if (buffer.isVoiced(value.nick)) voiced.push(value);
            else other.push(value);
        });

        function sortNicks(a, b){
            return a.nick.toLowerCase().localeCompare(b.nick.toLowerCase());
        }

        op.sort(sortNicks);
        voiced.sort(sortNicks);
        other.sort(sortNicks);
        return op.concat(voiced, other);
    };
});<commit_msg>Slighty different hash method for nicks, seems to give better results<commit_after>angular.module('quassel')
.filter('decoratenick', ['stripnickFilter', function(stripnick) {
    var MT = require('message').Type;

    return function(message) {
        var sender;
        switch(message.type) {
            case MT.Nick:
                sender = '<->';
                break;
            case MT.Mode:
                sender = '***';
                break;
            case MT.Join:
                sender = '-->';
                break;
            case MT.Part:
                sender = '<--';
                break;
            case MT.Quit:
                sender = '<--';
                break;
            case MT.Kick:
                sender = '<-*';
                break;
            case MT.Server:
                sender = stripnick(message.sender) || "*";
                break;
            case MT.Topic:
                sender = '*';
                break;
            case MT.NetsplitJoin:
                sender = '=>';
                break;
            case MT.NetsplitQuit:
                sender = '<=';
                break;
            case MT.DayChange:
                sender = '-';
                break;
            default:
                sender = stripnick(message.sender);
        }
        return sender;
    };
}])
.filter('decoratecontent', ['stripnickFilter', 'linkyFilter', function(stripnick, linky) {
    var MT = require('message').Type;
    var dateFormat;
    if (Intl && Intl.DateTimeFormat) {
        dateFormat = new Intl.DateTimeFormat(undefined, {weekday: "long", year: "numeric", month: "long", day: "numeric"});
    } else {
        dateFormat = {
            format: function(date) {
                return date.toDateString();
            }
        };
    }

    return function(message) {
        var content, arr, servers;
        switch(message.type) {
            case MT.Plain:
                content = linky(message.content, '_blank');
                break;
            case MT.Nick:
                content = stripnick(message.sender) + " is now known as " + message.content;
                break;
            case MT.Mode:
                content = "Mode " + message.content + " by " + stripnick(message.sender);
                break;
            case MT.Join:
                content = stripnick(message.sender) + " has joined";
                break;
            case MT.Part:
                content = stripnick(message.sender) + " has left";
                break;
            case MT.Quit:
                content = stripnick(message.sender) + " has quit";
                break;
            case MT.Kick:
                var ind = message.content.indexOf(" ");
                content = stripnick(message.sender) + " has kicked " + message.content.slice(0, ind) + " (" + message.content.slice(ind+1) + ")";
                break;
            case MT.NetsplitJoin:
                arr = message.content.split("#:#");
                servers = arr.pop().split(" ");
                content = "Netsplit between " + servers[0] + " and " + servers[1] + " ended. Users joined: " + arr.map(stripnick).join(', ');
                break;
            case MT.NetsplitQuit:
                arr = message.content.split("#:#");
                servers = arr.pop().split(" ");
                content = "Netsplit between " + servers[0] + " and " + servers[1] + ". Users quit: " + arr.map(stripnick).join(', ');
                break;
            case MT.DayChange:
                content = "{Day changed to " + dateFormat.format(message.datetime) + "}";
                break;
            default:
                content = message.content;
        }
        return content;
    };
}])
.filter('channelsFilter', function() {
    return function(input) {
        input = input || [];
        var out = input.filter(function(elt){
            return !elt._isStatusBuffer;
        });
        out.sort(function(a, b){
            return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
        });

        return out;
    };
})
.filter('stripnick', function() {
    return function(input) {
        if (typeof input === 'string') {
            var ind = input.indexOf('!');
            if (ind !== -1) {
                return input.slice(0, ind);
            }
            return input;
        }
        return '';
    };
})
.filter('duration', function() {
    return function(input) {
        var dateObject = null;
        if (input instanceof Date) {
            dateObject = input;
        } else {
            dateObject = new Date(input);
        }
        var h = dateObject.getHours(), m = dateObject.getMinutes(), s = dateObject.getSeconds();
        if (h < 10) h = '0'+h;
        if (m < 10) m = '0'+m;
        if (s < 10) s = '0'+s;
        return [h, m, s].join(':');
    };
})
.filter('color', function() {
    var COLOR = new RegExp("^" + String.fromCharCode(3) + "(([0-9]{1,2})(,([0-9]{1,2}))?)");
    return function(input) {
        var out = '',
        nbSpan = 0,
        nbSpanColor = 0,
        spanAttributes = {
            bold: false,
            italic: false,
            underline: false
        },
        i = 0,
        match;

        var openSpan = function (classes) {
            nbSpan += 1;
            return '';
        };

        var openColorSpan = function (fgclass, bgclass) {
            nbSpanColor += 1;
            var classes = fgclass;
            if (bgclass) {
                classes += ' ' + bgclass;
            }
            return openSpan(classes);
        };

        var closeSpan = function () {
            nbSpan -= 1;
            return '';
        };

        var closeColorSpan = function () {
            nbSpanColor -= 1;
            return closeSpan();
        };

        var unescapeColorTags = function(str) {
            var tagsToReplace = {
                '': '\x02',
                '': '\x1D',
                '': '\x1F',
                '': '\x03',
                '': '\x0F'
            };
            var re = /&#(2|29|3|31|15);/g;
            return str.replace(re, function (tag) {
                return tagsToReplace[tag] || tag;
            });
        };

        input = unescapeColorTags(input);

        for (i = 0; i < input.length; i++) {
            switch (input[i]) {
                case '\x02':
                    if (spanAttributes.bold) {
                        out += closeSpan();
                    } else {
                        out += openSpan('mirc-bold');
                    }
                    spanAttributes.bold = !spanAttributes.bold;
                    break;
                case '\x1D':
                    if (spanAttributes.italic) {
                        out += closeSpan();
                    } else {
                        out += openSpan('mirc-italic');
                    }
                    spanAttributes.italic = !spanAttributes.italic;
                    break;
                case '\x1F':
                    if (spanAttributes.underline) {
                        out += closeSpan();
                    } else {
                        out += openSpan('mirc-underline');
                    }
                    spanAttributes.underline = !spanAttributes.underline;
                    break;
                case '\x03':
                    match = input.substr(i, 6).match(COLOR);
                    var classfg = false,
                        classbg = false;
                    if (match) {
                        i += match[1].length;
                        // 2 & 4
                        classfg = "mirc-fg-" + parseInt(match[2], 10);
                        if (match[4]) {
                            classbg = "mirc-bg-" + parseInt(match[4], 10);
                        }
                        out += openColorSpan(classfg, classbg);
                    } else {
                        while (nbSpanColor > 0) {
                            out += closeColorSpan();
                        }
                    }
                    break;
                case '\x0F':
                    while (nbSpan > 0) {
                        out += closeSpan();
                    }
                    spanAttributes.bold = spanAttributes.italic = spanAttributes.underline = spanAttributes.colour = false;
                    break;
                default:
                    out += input[i];
                    break;
            }
        }
        while (nbSpan > 0) {
            out += closeSpan();
        }
        return out;
    };
})
.filter('escape', function() {
    var tagsToReplace = {
        '&': '&',
        '<': '<',
        '>': '>',
        '"': '"',
        '\'': ''',
        '/': '/'
    }, re = /[&<>]/g;
    return function(input) {
        if (typeof input === 'string') {
            return input.replace(re, function (tag) {
                return tagsToReplace[tag] || tag;
            });
        }
        return '';
    };
})
.filter('hash', function() {
    return function(input) {
        if (!input) return null;
        var hash = 5381, i, chr, len;
        for (i = 0, len = input.length; i < len; i++) {
            chr   = input.charCodeAt(i);
            hash  = ((hash << 5) + hash) + chr;
        }
        return Math.abs(hash) % 16;
    };
})
.filter('ordernicks', function() {
    return function(users, buffer) {
        if (!users || buffer === null) return users;
        var op = [], voiced = [], other = [];

        angular.forEach(users, function(value) {
            if (buffer.isOp(value.nick)) op.push(value);
            else if (buffer.isVoiced(value.nick)) voiced.push(value);
            else other.push(value);
        });

        function sortNicks(a, b){
            return a.nick.toLowerCase().localeCompare(b.nick.toLowerCase());
        }

        op.sort(sortNicks);
        voiced.sort(sortNicks);
        other.sort(sortNicks);
        return op.concat(voiced, other);
    };
});<|endoftext|>